From bb79b621c30f530f1832a0e50e4457290674fcdc Mon Sep 17 00:00:00 2001 From: Arubik <102335860+ArubikU@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:32:40 -0500 Subject: [PATCH 001/226] Added container and analog --- .../UnsafeCompositeBlockBehavior.java | 38 +++++- .../plugin/injector/BlockGenerator.java | 113 +++++++++++------- .../reflection/minecraft/CoreReflections.java | 95 +++++++++++---- .../craftengine/core/block/BlockBehavior.java | 17 ++- 4 files changed, 198 insertions(+), 65 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 4542f71d2..81eb1ad79 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -74,6 +74,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { return previous; } + + @Override + public Object getContainer(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + Object container = behavior.getContainer(thisBlock, args); + if (container != null) { + return container; + } + } + return null; + } + @Override public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { for (AbstractBlockBehavior behavior : this.behaviors) { @@ -264,6 +276,30 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { return false; } + @Override + public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + if (behavior.hasAnalogOutputSignal(thisBlock, args)) { + return true; + } + } + return false; + } + + @Override + public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { + int signal = 0; + int count = 0; + for (AbstractBlockBehavior behavior : this.behaviors) { + int s = behavior.getAnalogOutputSignal(thisBlock, args); + if (s != 0) { + signal += s; + count++; + } + } + return count == 0 ? 0 : signal / count; + } + @Override public Object playerWillDestroy(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Object previous = args[0]; @@ -282,4 +318,4 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { behavior.spawnAfterBreak(thisBlock, args, superMethod); } } -} +} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index 83b94ad9f..5b6b21096 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -15,6 +15,7 @@ import net.bytebuddy.implementation.bind.annotation.This; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.block.BukkitBlockShape; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator.*; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils; @@ -34,7 +35,6 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Field; import java.util.concurrent.Callable; -import java.util.function.BiConsumer; import java.util.function.Function; public final class BlockGenerator { @@ -63,6 +63,7 @@ public final class BlockGenerator { .implement(CoreReflections.clazz$Fallable) .implement(CoreReflections.clazz$BonemealableBlock) .implement(CoreReflections.clazz$SimpleWaterloggedBlock) + .implement(CoreReflections.clazz$WorldlyContainerHolder) // internal interfaces .method(ElementMatchers.named("behaviorDelegate")) .intercept(FieldAccessor.ofField("behaviorHolder")) @@ -90,12 +91,21 @@ public final class BlockGenerator { // rotate .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$rotate)) .intercept(MethodDelegation.to(RotateInterceptor.INSTANCE)) + // hasAnalogOutputSignal + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$hasAnalogOutputSignal)) + .intercept(MethodDelegation.to(HasAnalogOutputSignalInterceptor.INSTANCE)) + // getAnalogOutputSignal + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$getAnalogOutputSignal)) + .intercept(MethodDelegation.to(GetAnalogOutputSignalInterceptor.INSTANCE)) // tick .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$tick)) .intercept(MethodDelegation.to(TickInterceptor.INSTANCE)) // isValidBoneMealTarget .method(ElementMatchers.is(CoreReflections.method$BonemealableBlock$isValidBonemealTarget)) .intercept(MethodDelegation.to(IsValidBoneMealTargetInterceptor.INSTANCE)) + // getContainer + .method(ElementMatchers.is(CoreReflections.method$WorldlyContainerHolder$getContainer)) + .intercept(MethodDelegation.to(GetContainerInterceptor.INSTANCE)) // isBoneMealSuccess .method(ElementMatchers.is(CoreReflections.method$BonemealableBlock$isBonemealSuccess)) .intercept(MethodDelegation.to(IsBoneMealSuccessInterceptor.INSTANCE)) @@ -106,54 +116,21 @@ public final class BlockGenerator { .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$randomTick)) .intercept(MethodDelegation.to(RandomTickInterceptor.INSTANCE)) // onPlace - .method(ElementMatchers.takesArguments(5) - .and(ElementMatchers.takesArgument(0, CoreReflections.clazz$BlockState)) - .and(ElementMatchers.takesArgument(1, CoreReflections.clazz$Level)) - .and(ElementMatchers.takesArgument(2, CoreReflections.clazz$BlockPos)) - .and(ElementMatchers.takesArgument(3, CoreReflections.clazz$BlockState)) - .and(ElementMatchers.takesArgument(4, boolean.class)) - .and(ElementMatchers.named("onPlace").or(ElementMatchers.named("a"))) - ) + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$onPlace)) .intercept(MethodDelegation.to(OnPlaceInterceptor.INSTANCE)) // onBrokenAfterFall - .method(ElementMatchers.takesArguments(3) - .and(ElementMatchers.takesArgument(0, CoreReflections.clazz$Level)) - .and(ElementMatchers.takesArgument(1, CoreReflections.clazz$BlockPos)) - .and(ElementMatchers.takesArgument(2, CoreReflections.clazz$FallingBlockEntity)) - ) + .method(ElementMatchers.is(CoreReflections.method$Fallable$onBrokenAfterFall)) .intercept(MethodDelegation.to(OnBrokenAfterFallInterceptor.INSTANCE)) // onLand - .method(ElementMatchers.takesArguments(5) - .and(ElementMatchers.takesArgument(0, CoreReflections.clazz$Level)) - .and(ElementMatchers.takesArgument(1, CoreReflections.clazz$BlockPos)) - .and(ElementMatchers.takesArgument(2, CoreReflections.clazz$BlockState)) - .and(ElementMatchers.takesArgument(3, CoreReflections.clazz$BlockState)) - .and(ElementMatchers.takesArgument(4, CoreReflections.clazz$FallingBlockEntity)) - ) + .method(ElementMatchers.is(CoreReflections.method$Fallable$onLand)) .intercept(MethodDelegation.to(OnLandInterceptor.INSTANCE)) // canSurvive - .method(ElementMatchers.takesArguments(3) - .and(ElementMatchers.takesArgument(0, CoreReflections.clazz$BlockState)) - .and(ElementMatchers.takesArgument(1, CoreReflections.clazz$LevelReader)) - .and(ElementMatchers.takesArgument(2, CoreReflections.clazz$BlockPos)) + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$canSurvive) ) .intercept(MethodDelegation.to(CanSurviveInterceptor.INSTANCE)) // updateShape - .method(ElementMatchers.returns(CoreReflections.clazz$BlockState) - .and(ElementMatchers.takesArgument(0, CoreReflections.clazz$BlockState)) - // LevelReader 1.21.3+ // 1.20-1.12.2 - .and(ElementMatchers.takesArgument(1, CoreReflections.clazz$LevelReader).or(ElementMatchers.takesArgument(1, CoreReflections.clazz$Direction))) - .and(ElementMatchers.named("updateShape").or(ElementMatchers.named("a")))) + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$updateShape)) .intercept(MethodDelegation.to(UpdateShapeInterceptor.INSTANCE)) - // onExplosionHit 1.21+ - .method(ElementMatchers.returns(void.class) - .and(ElementMatchers.takesArgument(0, CoreReflections.clazz$BlockState)) - .and(ElementMatchers.takesArgument(1, VersionHelper.isOrAbove1_21_2() ? CoreReflections.clazz$ServerLevel : CoreReflections.clazz$Level)) - .and(ElementMatchers.takesArgument(2, CoreReflections.clazz$BlockPos)) - .and(ElementMatchers.takesArgument(3, CoreReflections.clazz$Explosion)) - .and(ElementMatchers.takesArgument(4, BiConsumer.class)) - ) - .intercept(MethodDelegation.to(OnExplosionHitInterceptor.INSTANCE)) // neighborChanged .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$neighborChanged)) .intercept(MethodDelegation.to(NeighborChangedInterceptor.INSTANCE)) @@ -184,15 +161,21 @@ public final class BlockGenerator { // spawnAfterBreak .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$spawnAfterBreak)) .intercept(MethodDelegation.to(SpawnAfterBreakInterceptor.INSTANCE)); + // 1.21.5+ if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) { - builder.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval)) + builder = builder.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval)) .intercept(MethodDelegation.to(AffectNeighborsAfterRemovalInterceptor.INSTANCE)); } + // 1.20-1.21.4 if (CoreReflections.method$BlockBehaviour$onRemove != null) { - builder.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$onRemove)) + builder = builder.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$onRemove)) .intercept(MethodDelegation.to(OnRemoveInterceptor.INSTANCE)); } - + // 1.21+ + if (CoreReflections.method$BlockBehaviour$onExplosionHit != null) { + builder = builder.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$onExplosionHit)) + .intercept(MethodDelegation.to(OnExplosionHitInterceptor.INSTANCE)); + } Class clazz$CraftEngineBlock = builder.make().load(BlockGenerator.class.getClassLoader()).getLoaded(); constructor$CraftEngineBlock = MethodHandles.publicLookup().in(clazz$CraftEngineBlock) .findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$BlockBehaviour$Properties)) @@ -469,6 +452,50 @@ public final class BlockGenerator { } } + public static class GetContainerInterceptor { + public static final GetContainerInterceptor INSTANCE = new GetContainerInterceptor(); + + @RuntimeType + public Object intercept(@This Object thisObj, @AllArguments Object[] args) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + return holder.value().getContainer(thisObj, args); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run getContainer", e); + return null; + } + } + } + + public static class HasAnalogOutputSignalInterceptor { + public static final HasAnalogOutputSignalInterceptor INSTANCE = new HasAnalogOutputSignalInterceptor(); + + @RuntimeType + public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + return holder.value().hasAnalogOutputSignal(thisObj, args); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run hasAnalogOutputSignal", e); + return false; + } + } + } + + public static class GetAnalogOutputSignalInterceptor { + public static final GetAnalogOutputSignalInterceptor INSTANCE = new GetAnalogOutputSignalInterceptor(); + + @RuntimeType + public int intercept(@This Object thisObj, @AllArguments Object[] args) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + return holder.value().getAnalogOutputSignal(thisObj, args); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run getAnalogOutputSignal", e); + return 0; + } + } + } public static class PerformBoneMealInterceptor { public static final PerformBoneMealInterceptor INSTANCE = new PerformBoneMealInterceptor(); @@ -672,4 +699,4 @@ public final class BlockGenerator { } } } -} +} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index bff6a601b..4126b0ac6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -1181,6 +1181,17 @@ public final class CoreReflections { ) ); + public static final Class clazz$ServerLevel = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "server.level.WorldServer", + "server.level.ServerLevel" + ) + ); + + public static final Class clazz$Explosion = requireNonNull( + ReflectionUtils.getClazz(BukkitReflectionUtils.assembleMCClass("world.level.Explosion")) + ); + public static final Constructor constructor$StateDefinition$Builder = requireNonNull( ReflectionUtils.getTheOnlyConstructor(clazz$StateDefinition$Builder) ); @@ -1451,6 +1462,18 @@ public final class CoreReflections { ReflectionUtils.getInstanceDeclaredField(clazz$BlockBehaviour, float.class, 0) ); + public static final Field field$BlockBehaviour$friction = requireNonNull( + ReflectionUtils.getInstanceDeclaredField(clazz$BlockBehaviour, float.class, 1) + ); + + public static final Field field$BlockBehaviour$speedFactor = requireNonNull( + ReflectionUtils.getInstanceDeclaredField(clazz$BlockBehaviour, float.class, 2) + ); + + public static final Field field$BlockBehaviour$jumpFactor = requireNonNull( + ReflectionUtils.getInstanceDeclaredField(clazz$BlockBehaviour, float.class, 3) + ); + public static final Field field$BlockBehaviour$soundType = requireNonNull( ReflectionUtils.getInstanceDeclaredField(clazz$BlockBehaviour, clazz$SoundType, 0) ); @@ -1547,6 +1570,14 @@ public final class CoreReflections { ) ); + public static final Method method$BlockBehaviour$hasAnalogOutputSignal = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"hasAnalogOutputSignal", "c"}, clazz$BlockState) + ); + + public static final Method method$BlockBehaviour$getAnalogOutputSignal = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, int.class, new String[]{"getAnalogOutputSignal", "a"}, clazz$BlockState, clazz$Level, clazz$BlockPos) + ); + public static final Method method$Entity$level = requireNonNull( ReflectionUtils.getMethod(clazz$Entity, clazz$Level) ); @@ -1651,6 +1682,15 @@ public final class CoreReflections { ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, clazz$BlockState, clazz$BlockState, clazz$Direction, clazz$BlockState, clazz$LevelAccessor, clazz$BlockPos, clazz$BlockPos) ); + public static final Method method$BlockBehaviour$canSurvive = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, clazz$BlockState, clazz$LevelReader, clazz$BlockPos) + ); + + public static final Method method$BlockBehaviour$onExplosionHit = MiscUtils.requireNonNullIf( + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, clazz$BlockState, VersionHelper.isOrAbove1_21_2() ? clazz$ServerLevel : clazz$Level, clazz$BlockPos, clazz$Explosion, BiConsumer.class), + VersionHelper.isOrAbove1_21() + ); + public static final Class clazz$Fallable = requireNonNull( ReflectionUtils.getClazz(BukkitReflectionUtils.assembleMCClass("world.level.block.Fallable")) ); @@ -1673,6 +1713,14 @@ public final class CoreReflections { ) ); + public static final Method method$Fallable$onLand = requireNonNull( + ReflectionUtils.getMethod(clazz$Fallable, void.class, clazz$Level, clazz$BlockPos, clazz$BlockState, clazz$BlockState, clazz$FallingBlockEntity) + ); + + public static final Method method$Fallable$onBrokenAfterFall = requireNonNull( + ReflectionUtils.getMethod(clazz$Fallable, void.class, clazz$Level, clazz$BlockPos, clazz$FallingBlockEntity) + ); + public static final Method method$FallingBlockEntity$fall = requireNonNull( ReflectionUtils.getStaticMethod(clazz$FallingBlockEntity, clazz$FallingBlockEntity, clazz$Level, clazz$BlockPos, clazz$BlockState) ); @@ -2274,12 +2322,30 @@ public final class CoreReflections { ) ); + public static final Class clazz$WorldlyContainerHolder = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.IInventoryHolder", + "world.WorldlyContainerHolder" + ) + ); + public static final Method method$BonemealableBlock$isValidBonemealTarget = requireNonNull( VersionHelper.isOrAbove1_20_2() ? ReflectionUtils.getInstanceMethod(clazz$BonemealableBlock, boolean.class, clazz$LevelReader, clazz$BlockPos, clazz$BlockState) : ReflectionUtils.getInstanceMethod(clazz$BonemealableBlock, boolean.class, clazz$LevelReader, clazz$BlockPos, clazz$BlockState, boolean.class) ); + public static final Class clazz$WorldlyContainer = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.IWorldInventory", + "world.WorldlyContainer" + ) + ); + + + public static final Method method$WorldlyContainerHolder$getContainer = requireNonNull( + ReflectionUtils.getMethod(clazz$WorldlyContainerHolder, clazz$WorldlyContainer, clazz$BlockState, clazz$LevelAccessor, clazz$BlockPos) + ); public static final Method method$BonemealableBlock$isBonemealSuccess = requireNonNull( ReflectionUtils.getMethod(clazz$BonemealableBlock, boolean.class, clazz$Level, clazz$RandomSource, clazz$BlockPos, clazz$BlockState) ); @@ -2957,13 +3023,6 @@ public final class CoreReflections { ReflectionUtils.getInstanceDeclaredField(clazz$ServerPlayer, clazz$ServerGamePacketListenerImpl, 0) ); - public static final Class clazz$ServerLevel = requireNonNull( - BukkitReflectionUtils.findReobfOrMojmapClass( - "server.level.WorldServer", - "server.level.ServerLevel" - ) - ); - public static final Method method$ServerLevel$getNoiseBiome = requireNonNull( ReflectionUtils.getMethod(clazz$ServerLevel, clazz$Holder, int.class, int.class, int.class) ); @@ -3446,6 +3505,11 @@ public final class CoreReflections { ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, new String[]{"randomTick", "b"}, clazz$BlockState, clazz$ServerLevel, clazz$BlockPos, clazz$RandomSource) ); + public static final Method method$BlockBehaviour$onPlace = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, new String[]{"onPlace", VersionHelper.isOrAbove1_21_5() ? "a" : "b"}, + clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$BlockState, boolean.class) + ); + public static final Class clazz$InsideBlockEffectApplier = BukkitReflectionUtils.findReobfOrMojmapClass( "world.entity.InsideBlockEffectApplier", "world.entity.InsideBlockEffectApplier" @@ -3603,12 +3667,6 @@ public final class CoreReflections { ) ); - public static final Class clazz$Explosion = requireNonNull( - ReflectionUtils.getClazz( - BukkitReflectionUtils.assembleMCClass("world.level.Explosion") - ) - ); - // 1.20.5+ public static final Field field$ItemStack$CODEC = ReflectionUtils.getDeclaredField(clazz$ItemStack, "CODEC", "b"); @@ -3993,18 +4051,15 @@ public final class CoreReflections { ReflectionUtils.getStaticMethod(clazz$ArmorTrim, Optional.class, clazz$RegistryAccess, clazz$ItemStack); public static final Method method$BlockBehaviour$spawnAfterBreak = requireNonNull( - ReflectionUtils.getDeclaredMethod( - clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$ServerLevel, clazz$BlockPos, clazz$ItemStack, boolean.class - ) + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$ServerLevel, clazz$BlockPos, clazz$ItemStack, boolean.class) ); // 1.20~1.21.4 public static final Method method$BlockBehaviour$onRemove = MiscUtils.requireNonNullIf( - ReflectionUtils.getDeclaredMethod( - clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$BlockState, boolean.class - ), + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, new String[]{"a", "onRemove"}, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$BlockState, boolean.class), !VersionHelper.isOrAbove1_21_5() ); + public static final Object instance$CollisionContext$empty; static { @@ -4148,4 +4203,4 @@ public final class CoreReflections { "world.level.storage.loot.entries.LootPoolEntryType" ) ); -} +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 4af326d66..e9bcb1550 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -85,6 +85,21 @@ public abstract class BlockBehavior { return false; } + //BlockState state + public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { + return false; + } + + //BlockState state Level level BlockPos pos + public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { + return 0; + } + + // BlockState state, LevelReader world, BlockPos pos + public Object getContainer(Object thisBlock, Object[] args) throws Exception { + return null; + } + // Level level, RandomSource random, BlockPos pos, BlockState state public boolean isBoneMealSuccess(Object thisBlock, Object[] args) throws Exception { return false; @@ -186,4 +201,4 @@ public abstract class BlockBehavior { } public abstract CustomBlock block(); -} +} \ No newline at end of file From 4f864b9d9da2aa26d73fd85d72f11ccaa5accfed Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 1 Sep 2025 01:46:38 +0800 Subject: [PATCH 002/226] =?UTF-8?q?feat(network):=20=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=A8=A1=E7=BB=84=E7=9A=84=E5=8F=98?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 2 + .../plugin/network/PacketConsumers.java | 39 +++-------- .../bukkit/plugin/network/payload/Data.java | 18 +++++ .../plugin/network/payload/NetWorkCodec.java | 22 ------ .../network/payload/NetWorkDataTypes.java | 28 -------- .../network/payload/NetWorkDecoder.java | 7 -- .../network/payload/NetWorkEncoder.java | 7 -- .../plugin/network/payload/PayloadHelper.java | 69 +++++++++++++++++++ .../network/payload/UnknownPayload.java | 4 +- .../network/payload/codec/NetworkCodec.java | 32 +++++++++ .../NetworkCodecs.java} | 48 ++++++------- .../network/payload/codec/NetworkDecoder.java | 5 ++ .../network/payload/codec/NetworkEncoder.java | 5 ++ .../payload/codec/NetworkMemberEncoder.java | 6 ++ .../protocol/CancelBlockUpdateData.java | 28 ++++++++ .../protocol/ClientBlockStateSizeData.java | 29 ++++++++ .../protocol/ClientCustomBlockData.java | 42 +++++++++++ .../minecraft/NetworkReflections.java | 8 +-- .../plugin/user/BukkitServerPlayer.java | 16 ++++- .../main/resources/additional-real-blocks.yml | 2 +- .../core/block/AbstractBlockManager.java | 1 + .../core/plugin/network/NetWorkUser.java | 5 ++ 22 files changed, 296 insertions(+), 127 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/{NetWorkCodecs.java => codec/NetworkCodecs.java} (88%) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 8a3f5d23f..9df95e2ff 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIdFinder; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5; +import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; import net.momirealms.craftengine.bukkit.plugin.reflection.leaves.LeavesReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.LibraryReflections; @@ -105,6 +106,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes this.packetIds = VersionHelper.isOrAbove1_20_5() ? new PacketIds1_20_5() : new PacketIds1_20(); // register packet handlers this.registerPacketHandlers(); + PayloadHelper.registerDataTypes(); // set up packet senders this.packetConsumer = FastNMS.INSTANCE::method$Connection$send; this.packetsConsumer = ((connection, packets, sendListener) -> { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index f3897953b..e5cf08ac7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -11,7 +11,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TranslationArgument; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; @@ -29,8 +28,8 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.ProtectedFieldVisitor; import net.momirealms.craftengine.bukkit.plugin.network.handler.*; import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; -import net.momirealms.craftengine.bukkit.plugin.network.payload.NetWorkDataTypes; import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload; +import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; import net.momirealms.craftengine.bukkit.plugin.network.payload.UnknownPayload; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; @@ -235,7 +234,7 @@ public class PacketConsumers { int[] newMappingsMOD = Arrays.copyOf(newMappings, registrySize); for (Map.Entry entry : map.entrySet()) { newMappings[entry.getKey()] = entry.getValue(); - if (BlockStateUtils.isVanillaBlock(entry.getKey())) { + if (BlockStateUtils.isVanillaBlock((int) entry.getKey())) { newMappingsMOD[entry.getKey()] = entry.getValue(); } } @@ -325,7 +324,7 @@ public class PacketConsumers { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf); FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) { - MCSection mcSection = new MCSection(SERVER_BLOCK_LIST, SERVER_BLOCK_LIST, BIOME_LIST); + MCSection mcSection = new MCSection(user.clientBlockList(), SERVER_BLOCK_LIST, BIOME_LIST); mcSection.readPacket(friendlyByteBuf); PalettedContainer container = mcSection.blockStateContainer(); Palette palette = container.data().palette(); @@ -348,7 +347,7 @@ public class PacketConsumers { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf); FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) { - MCSection mcSection = new MCSection(CLIENT_BLOCK_LIST, SERVER_BLOCK_LIST, BIOME_LIST); + MCSection mcSection = new MCSection(user.clientBlockList(), SERVER_BLOCK_LIST, BIOME_LIST); mcSection.readPacket(friendlyByteBuf); PalettedContainer container = mcSection.blockStateContainer(); Palette palette = container.data().palette(); @@ -1523,7 +1522,7 @@ public class PacketConsumers { int entityId = FastNMS.INSTANCE.method$ClientboundEntityPositionSyncPacket$id(packet); EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); if (handler != null) { - handler.handleSyncEntityPosition((BukkitServerPlayer) user, event, packet); + handler.handleSyncEntityPosition(user, event, packet); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundEntityPositionSyncPacket", e); @@ -1925,35 +1924,13 @@ public class PacketConsumers { Payload clientPayload; if (NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) { clientPayload = DiscardedPayload.from(payload); - } else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$UnknownPayload.isInstance(payload)) { + } else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$ServerboundCustomPayloadPacket$UnknownPayload.isInstance(payload)) { clientPayload = UnknownPayload.from(payload); } else { return; } - if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY)) - return; - FriendlyByteBuf buf = clientPayload.toBuffer(); - NetWorkDataTypes dataType = buf.readEnumConstant(NetWorkDataTypes.class); - if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) { - int clientBlockRegistrySize = dataType.decode(buf); - int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize(); - if (clientBlockRegistrySize != serverBlockRegistrySize) { - user.kick(Component.translatable( - "disconnect.craftengine.block_registry_mismatch", - TranslationArgument.numeric(clientBlockRegistrySize), - TranslationArgument.numeric(serverBlockRegistrySize) - )); - return; - } - user.setClientModState(true); - } else if (dataType == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) { - if (dataType.decode(buf)) { - FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer()); - bufPayload.writeEnumConstant(dataType); - dataType.encode(bufPayload, true); - user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, bufPayload.array()); - } - } + if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY)) return; + PayloadHelper.handleReceiver(clientPayload, user); } catch (Throwable e) { CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java new file mode 100644 index 000000000..5c3831807 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java @@ -0,0 +1,18 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.ByteBuf; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkDecoder; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkMemberEncoder; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; + +public interface Data { + + default void handle(NetWorkUser user) { + } + + static NetworkCodec codec(NetworkMemberEncoder networkMemberEncoder, NetworkDecoder networkDecoder) { + return NetworkCodec.ofMember(networkMemberEncoder, networkDecoder); + } + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java deleted file mode 100644 index 7fa1d4131..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload; - -import io.netty.buffer.ByteBuf; - -import java.util.function.Function; - -public interface NetWorkCodec extends NetWorkEncoder, NetWorkDecoder { - - default NetWorkCodec map(Function factory, Function getter) { - return new NetWorkCodec<>() { - @Override - public O decode(ByteBuf in) { - return factory.apply(NetWorkCodec.this.decode(in)); - } - - @Override - public void encode(ByteBuf out, O value) { - NetWorkCodec.this.encode(out, getter.apply(value)); - } - }; - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java deleted file mode 100644 index 37da1618d..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload; - -import io.netty.buffer.ByteBuf; - -public enum NetWorkDataTypes { - CLIENT_CUSTOM_BLOCK(NetWorkCodecs.INTEGER), - CANCEL_BLOCK_UPDATE(NetWorkCodecs.BOOLEAN); - - private final NetWorkCodec codec; - - NetWorkDataTypes(NetWorkCodec codec) { - this.codec = codec; - } - - public NetWorkCodec codec() { - return codec; - } - - @SuppressWarnings("unchecked") - public V decode(ByteBuf buf) { - return (V) codec.decode(buf); - } - - @SuppressWarnings("unchecked") - public void encode(ByteBuf buf, V value) { - ((NetWorkCodec) codec).encode(buf, value); - } -} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java deleted file mode 100644 index fc3024db0..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload; - -import io.netty.buffer.ByteBuf; - -public interface NetWorkDecoder { - T decode(ByteBuf in); -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java deleted file mode 100644 index 3c346115a..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload; - -import io.netty.buffer.ByteBuf; - -public interface NetWorkEncoder { - void encode(ByteBuf out, T value); -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java new file mode 100644 index 000000000..b3cb840f3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java @@ -0,0 +1,69 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.Unpooled; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; +import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.CancelBlockUpdateData; +import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientBlockStateSizeData; +import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientCustomBlockData; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.network.NetworkManager; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class PayloadHelper { + private static final Map, Byte> classToType = new HashMap<>(); + private static final Map> typeToCodec = new HashMap<>(); + private static final AtomicInteger typeCounter = new AtomicInteger(0); + + public static void registerDataTypes() { + registerDataType(ClientCustomBlockData.class, ClientCustomBlockData.CODEC); + registerDataType(CancelBlockUpdateData.class, CancelBlockUpdateData.CODEC); + registerDataType(ClientBlockStateSizeData.class, ClientBlockStateSizeData.CODEC); + } + + @SuppressWarnings("unchecked") + private static void registerDataType(Class dataClass, NetworkCodec codec) { + if (classToType.containsKey(dataClass)) { + CraftEngine.instance().logger().warn("Duplicate data type class: " + dataClass.getName()); + return; + } + int next = typeCounter.getAndIncrement(); + if (next > 255) { + throw new IllegalStateException("Too many data types registered, byte index overflow (max 256)"); + } + byte type = (byte) next; + classToType.put((Class) dataClass, type); + typeToCodec.put(type, (NetworkCodec) codec); + } + + public static void sendData(NetWorkUser user, Data data) { + Class dataClass = data.getClass(); + Byte type = classToType.get(dataClass); + if (type == null) { + CraftEngine.instance().logger().warn("Unknown data type class: " + dataClass.getName()); + return; + } + NetworkCodec codec = typeToCodec.get(type); + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeByte(type); + codec.encode(buf, data); + user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, buf.array()); + } + + public static void handleReceiver(Payload payload, NetWorkUser user) { + FriendlyByteBuf buf = payload.toBuffer(); + byte type = buf.readByte(); + NetworkCodec codec = typeToCodec.get(type); + if (codec == null) { + CraftEngine.instance().logger().warn("Unknown data type received: " + type); + return; + } + + Data networkData = codec.decode(buf); + networkData.handle(user); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/UnknownPayload.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/UnknownPayload.java index 4143eea27..7eece0691 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/UnknownPayload.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/UnknownPayload.java @@ -11,8 +11,8 @@ public record UnknownPayload(Key channel, ByteBuf rawPayload) implements Payload public static UnknownPayload from(Object payload) { try { - Object id = NetworkReflections.field$UnknownPayload$id.get(payload); - ByteBuf data = (ByteBuf) NetworkReflections.field$UnknownPayload$data.get(payload); + Object id = NetworkReflections.field$ServerboundCustomPayloadPacket$UnknownPayload$id.get(payload); + ByteBuf data = (ByteBuf) NetworkReflections.field$ServerboundCustomPayloadPacket$UnknownPayload$data.get(payload); Key channel = KeyUtils.resourceLocationToKey(id); return new UnknownPayload(channel, data); } catch (Exception e) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java new file mode 100644 index 000000000..3b60ae459 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; + +import java.util.function.Function; + +public interface NetworkCodec extends NetworkEncoder, NetworkDecoder { + + default NetworkCodec map(Function factory, Function getter) { + return new NetworkCodec<>() { + @Override + public V decode(B in) { + return factory.apply(NetworkCodec.this.decode(in)); + } + + @Override + public void encode(B out, V value) { + NetworkCodec.this.encode(out, getter.apply(value)); + } + }; + } + + static NetworkCodec ofMember(final NetworkMemberEncoder networkMemberEncoder, final NetworkDecoder networkDecoder) { + return new NetworkCodec<>() { + public V decode(B in) { + return networkDecoder.decode(in); + } + + public void encode(B out, V value) { + networkMemberEncoder.encode(value, out); + } + }; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodecs.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodecs.java similarity index 88% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodecs.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodecs.java index 4642578bc..0ff08bc1d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodecs.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodecs.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload; +package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; @@ -21,9 +21,9 @@ import java.util.OptionalInt; /** * 随便写了点方便后面重构和客户端通讯 */ -public interface NetWorkCodecs { +public interface NetworkCodecs { - NetWorkCodec BOOLEAN = new NetWorkCodec<>() { + NetworkCodec BOOLEAN = new NetworkCodec<>() { @Override public Boolean decode(ByteBuf in) { return in.readBoolean(); @@ -35,7 +35,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec BYTE = new NetWorkCodec<>() { + NetworkCodec BYTE = new NetworkCodec<>() { @Override public Byte decode(ByteBuf in) { return in.readByte(); @@ -47,9 +47,9 @@ public interface NetWorkCodecs { } }; - NetWorkCodec ROTATION_BYTE = BYTE.map(MCUtils::unpackDegrees, MCUtils::packDegrees); + NetworkCodec ROTATION_BYTE = BYTE.map(MCUtils::unpackDegrees, MCUtils::packDegrees); - NetWorkCodec SHORT = new NetWorkCodec<>() { + NetworkCodec SHORT = new NetworkCodec<>() { @Override public Short decode(ByteBuf in) { return in.readShort(); @@ -61,7 +61,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec UNSIGNED_SHORT = new NetWorkCodec<>() { + NetworkCodec UNSIGNED_SHORT = new NetworkCodec<>() { @Override public Integer decode(ByteBuf in) { return in.readUnsignedShort(); @@ -73,7 +73,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec INTEGER = new NetWorkCodec<>() { + NetworkCodec INTEGER = new NetworkCodec<>() { @Override public Integer decode(ByteBuf in) { return in.readInt(); @@ -85,7 +85,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec VAR_INTEGER = new NetWorkCodec<>() { + NetworkCodec VAR_INTEGER = new NetworkCodec<>() { @Override public Integer decode(ByteBuf in) { int result = 0; @@ -111,12 +111,12 @@ public interface NetWorkCodecs { } }; - NetWorkCodec OPTIONAL_VAR_INTEGER = VAR_INTEGER.map( + NetworkCodec OPTIONAL_VAR_INTEGER = VAR_INTEGER.map( integer -> integer == 0 ? OptionalInt.empty() : OptionalInt.of(integer - 1), optionalInt -> optionalInt.isPresent() ? optionalInt.getAsInt() + 1 : 0 ); - NetWorkCodec LONG = new NetWorkCodec<>() { + NetworkCodec LONG = new NetworkCodec<>() { @Override public Long decode(ByteBuf in) { return in.readLong(); @@ -128,7 +128,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec VAR_LONG = new NetWorkCodec<>() { + NetworkCodec VAR_LONG = new NetworkCodec<>() { @Override public Long decode(ByteBuf in) { long result = 0L; @@ -154,7 +154,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec FLOAT = new NetWorkCodec<>() { + NetworkCodec FLOAT = new NetworkCodec<>() { @Override public Float decode(ByteBuf in) { return in.readFloat(); @@ -166,7 +166,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec DOUBLE = new NetWorkCodec<>() { + NetworkCodec DOUBLE = new NetworkCodec<>() { @Override public Double decode(ByteBuf in) { return in.readDouble(); @@ -178,7 +178,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec BYTE_ARRAY = new NetWorkCodec<>() { + NetworkCodec BYTE_ARRAY = new NetworkCodec<>() { @Override public byte[] decode(ByteBuf in) { int maxSize = in.readableBytes(); @@ -199,7 +199,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec LONG_ARRAY = new NetWorkCodec<>() { + NetworkCodec LONG_ARRAY = new NetworkCodec<>() { @Override public long[] decode(ByteBuf in) { int arrayLength = VAR_INTEGER.decode(in); @@ -224,7 +224,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec STRING_UTF8 = new NetWorkCodec<>() { + NetworkCodec STRING_UTF8 = new NetworkCodec<>() { private static final int MAX_STRING_LENGTH = 32767; @Override @@ -273,7 +273,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec TAG = new NetWorkCodec<>() { + NetworkCodec TAG = new NetworkCodec<>() { @Override public Tag decode(ByteBuf in) { int initialIndex = in.readerIndex(); @@ -304,7 +304,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec COMPOUND_TAG = TAG.map(tag -> { + NetworkCodec COMPOUND_TAG = TAG.map(tag -> { if (tag instanceof CompoundTag compoundTag) { return compoundTag; } else { @@ -312,7 +312,7 @@ public interface NetWorkCodecs { } }, tag -> tag); - NetWorkCodec> OPTIONAL_COMPOUND_TAG = new NetWorkCodec<>() { + NetworkCodec> OPTIONAL_COMPOUND_TAG = new NetworkCodec<>() { @Override public Optional decode(ByteBuf in) { int initialIndex = in.readerIndex(); @@ -347,7 +347,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec VECTOR3F = new NetWorkCodec<>() { + NetworkCodec VECTOR3F = new NetworkCodec<>() { @Override public Vector3f decode(ByteBuf in) { return new Vector3f(in.readFloat(), in.readFloat(), in.readFloat()); @@ -361,7 +361,7 @@ public interface NetWorkCodecs { } }; - NetWorkCodec QUATERNIONF = new NetWorkCodec<>() { + NetworkCodec QUATERNIONF = new NetworkCodec<>() { @Override public Quaternionf decode(ByteBuf in) { return new Quaternionf(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat()); @@ -376,9 +376,9 @@ public interface NetWorkCodecs { } }; - NetWorkCodec CONTAINER_ID = VAR_INTEGER; + NetworkCodec CONTAINER_ID = VAR_INTEGER; - NetWorkCodec RGB_COLOR = new NetWorkCodec<>() { + NetworkCodec RGB_COLOR = new NetworkCodec<>() { @Override public Integer decode(ByteBuf in) { return 255 << 24 | in.readByte() & 0xFF << 16 | in.readByte() & 0xFF << 8 | in.readByte() & 0xFF; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java new file mode 100644 index 000000000..6116b6f62 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; + +public interface NetworkDecoder { + T decode(I in); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java new file mode 100644 index 000000000..eb17f0476 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; + +public interface NetworkEncoder { + void encode(O out, T value); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java new file mode 100644 index 000000000..49a60707f --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; + +@FunctionalInterface +public interface NetworkMemberEncoder { + void encode(T object, O object2); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java new file mode 100644 index 000000000..96fddb2c1 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java @@ -0,0 +1,28 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; + +import net.momirealms.craftengine.bukkit.plugin.network.payload.Data; +import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; + +public record CancelBlockUpdateData(boolean enabled) implements Data { + public static final NetworkCodec CODEC = Data.codec( + CancelBlockUpdateData::encode, + CancelBlockUpdateData::new + ); + + private CancelBlockUpdateData(FriendlyByteBuf buf) { + this(buf.readBoolean()); + } + + private void encode(FriendlyByteBuf buf) { + buf.writeBoolean(this.enabled); + } + + @Override + public void handle(NetWorkUser user) { + if (!this.enabled) return; + PayloadHelper.sendData(user, this); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java new file mode 100644 index 000000000..ff20e81e9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java @@ -0,0 +1,29 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; + + +import net.momirealms.craftengine.bukkit.plugin.network.payload.Data; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.IntIdentityList; + +public record ClientBlockStateSizeData(int blockStateSize) implements Data { + public static final NetworkCodec CODEC = Data.codec( + ClientBlockStateSizeData::encode, + ClientBlockStateSizeData::new + ); + + private ClientBlockStateSizeData(FriendlyByteBuf buf) { + this(buf.readInt()); + } + + private void encode(FriendlyByteBuf buf) { + buf.writeInt(this.blockStateSize); + } + + @Override + public void handle(NetWorkUser user) { + user.setClientBlockList(new IntIdentityList(this.blockStateSize)); + } + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java new file mode 100644 index 000000000..c24d0f403 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java @@ -0,0 +1,42 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; + + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslationArgument; +import net.momirealms.craftengine.bukkit.plugin.network.payload.Data; +import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; +import net.momirealms.craftengine.bukkit.util.RegistryUtils; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.IntIdentityList; + +public record ClientCustomBlockData(int size) implements Data { + public static final NetworkCodec CODEC = Data.codec( + ClientCustomBlockData::encode, + ClientCustomBlockData::new + ); + + private ClientCustomBlockData(FriendlyByteBuf buf) { + this(buf.readInt()); + } + + private void encode(FriendlyByteBuf buf) { + buf.writeInt(this.size); + } + + @Override + public void handle(NetWorkUser user) { + int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize(); + if (this.size != serverBlockRegistrySize) { + user.kick(Component.translatable( + "disconnect.craftengine.block_registry_mismatch", + TranslationArgument.numeric(this.size), + TranslationArgument.numeric(serverBlockRegistrySize) + )); + return; + } + user.setClientModState(true); + user.setClientBlockList(new IntIdentityList(this.size)); + } + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java index 4e421cff8..0da437e2a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java @@ -1635,7 +1635,7 @@ public final class NetworkReflections { } // 1.20.2~1.20.4 - public static final Class clazz$UnknownPayload = MiscUtils.requireNonNullIf( + public static final Class clazz$ServerboundCustomPayloadPacket$UnknownPayload = MiscUtils.requireNonNullIf( ReflectionUtils.getClazz( BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundCustomPayloadPacket$UnknownPayload") ), @@ -1643,17 +1643,17 @@ public final class NetworkReflections { ); // 1.20.2~1.20.4 - public static final Field field$UnknownPayload$id = Optional.ofNullable(clazz$UnknownPayload) + public static final Field field$ServerboundCustomPayloadPacket$UnknownPayload$id = Optional.ofNullable(clazz$ServerboundCustomPayloadPacket$UnknownPayload) .map(it -> ReflectionUtils.getDeclaredField(it, CoreReflections.clazz$ResourceLocation, 0)) .orElse(null); // 1.20.2~1.20.4 - public static final Field field$UnknownPayload$data = Optional.ofNullable(clazz$UnknownPayload) + public static final Field field$ServerboundCustomPayloadPacket$UnknownPayload$data = Optional.ofNullable(clazz$ServerboundCustomPayloadPacket$UnknownPayload) .map(it -> ReflectionUtils.getDeclaredField(it, ByteBuf.class, 0)) .orElse(null); // 1.20.2~1.20.4 - public static final Constructor constructor$UnknownPayload = Optional.ofNullable(clazz$UnknownPayload) + public static final Constructor constructor$ServerboundCustomPayloadPacket$UnknownPayload = Optional.ofNullable(clazz$ServerboundCustomPayloadPacket$UnknownPayload) .map(it -> ReflectionUtils.getConstructor(it, CoreReflections.clazz$ResourceLocation, ByteBuf.class)) .orElse(null); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 9d4ebc8ee..04927c89c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -31,6 +31,7 @@ import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; @@ -97,6 +98,7 @@ public class BukkitServerPlayer extends Player { private int resentSwingTick; // has fabric client mod or not private boolean hasClientMod = false; + private IntIdentityList blockList = new IntIdentityList(BlockStateUtils.vanillaStateSize()); // cache if player can break blocks private boolean clientSideCanBreak = true; // prevent AFK players from consuming too much CPU resource on predicting @@ -378,7 +380,7 @@ public class BukkitServerPlayer extends Player { if (VersionHelper.isOrAbove1_20_2()) { Object dataPayload; if (!VersionHelper.isOrAbove1_20_5()) { - dataPayload = NetworkReflections.constructor$UnknownPayload.newInstance(channelResourceLocation, Unpooled.wrappedBuffer(data)); + dataPayload = NetworkReflections.constructor$ServerboundCustomPayloadPacket$UnknownPayload.newInstance(channelResourceLocation, Unpooled.wrappedBuffer(data)); } else if (DiscardedPayload.useNewMethod) { dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelResourceLocation, data); } else { @@ -909,14 +911,26 @@ public class BukkitServerPlayer extends Player { return resentSwingTick == gameTicks(); } + @Override public boolean clientModEnabled() { return this.hasClientMod; } + @Override public void setClientModState(boolean enable) { this.hasClientMod = enable; } + @Override + public void setClientBlockList(IntIdentityList blockList) { + this.blockList = blockList; + } + + @Override + public IntIdentityList clientBlockList() { + return this.blockList; + } + @Override public void addResourcePackUUID(UUID uuid) { if (VersionHelper.isOrAbove1_20_3()) { diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 1573d2fbf..58279f8a7 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -26,7 +26,7 @@ minecraft:cherry_sapling: 1 minecraft:anvil: 2 minecraft:chipped_anvil: 2 minecraft:damaged_anvil: 2 -minecraft:sugarcane: 14 +minecraft:sugar_cane: 14 minecraft:iron_trapdoor: 32 minecraft:acacia_trapdoor: 32 minecraft:oak_trapdoor: 32 diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index ba6327f9d..ce78d8bf1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -324,6 +324,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 结合variants JsonElement combinedVariant = GsonHelper.combine(variants); Map overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()); + AbstractBlockManager.this.tempVanillaBlockStateModels.put(registryId, combinedVariant); JsonElement previous = overrideMap.get(propertyNBT); if (previous != null && !previous.equals(combinedVariant)) { throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 83661fd2f..32a50d188 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -4,6 +4,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; +import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.ChunkPos; import org.jetbrains.annotations.ApiStatus; @@ -83,4 +84,8 @@ public interface NetWorkUser { void setChunkTrackStatus(ChunkPos chunkPos, boolean tracked); void clearTrackedChunks(); + + void setClientBlockList(IntIdentityList blockList); + + IntIdentityList clientBlockList(); } From 581810d90f4318bf52ddbb3bf9ddc9ce3e9aff56 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 1 Sep 2025 05:07:53 +0800 Subject: [PATCH 003/226] =?UTF-8?q?refactor(network):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E5=88=B0=E6=B3=A8=E5=86=8C=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/network/payload/Data.java | 18 ------ .../plugin/network/payload/PayloadHelper.java | 60 ++++++++----------- .../network/payload/codec/NetworkDecoder.java | 5 -- .../protocol/CancelBlockUpdateData.java | 28 --------- .../protocol/CancelBlockUpdatePacket.java | 39 ++++++++++++ .../protocol/ClientBlockStateSizeData.java | 29 --------- .../protocol/ClientBlockStateSizePacket.java | 40 +++++++++++++ ...Data.java => ClientCustomBlockPacket.java} | 25 +++++--- .../core/plugin/network/ModPacket.java | 21 +++++++ .../plugin/network}/codec/NetworkCodec.java | 2 +- .../plugin/network}/codec/NetworkCodecs.java | 2 +- .../plugin/network/codec/NetworkDecoder.java | 5 ++ .../plugin/network}/codec/NetworkEncoder.java | 2 +- .../network}/codec/NetworkMemberEncoder.java | 2 +- .../core/registry/BuiltInRegistries.java | 4 ++ .../craftengine/core/registry/Registries.java | 4 ++ 16 files changed, 160 insertions(+), 126 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/{ClientCustomBlockData.java => ClientCustomBlockPacket.java} (56%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/network/ModPacket.java rename {bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload => core/src/main/java/net/momirealms/craftengine/core/plugin/network}/codec/NetworkCodec.java (93%) rename {bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload => core/src/main/java/net/momirealms/craftengine/core/plugin/network}/codec/NetworkCodecs.java (99%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkDecoder.java rename {bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload => core/src/main/java/net/momirealms/craftengine/core/plugin/network}/codec/NetworkEncoder.java (51%) rename {bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload => core/src/main/java/net/momirealms/craftengine/core/plugin/network}/codec/NetworkMemberEncoder.java (60%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java deleted file mode 100644 index 5c3831807..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Data.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload; - -import io.netty.buffer.ByteBuf; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkDecoder; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkMemberEncoder; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; - -public interface Data { - - default void handle(NetWorkUser user) { - } - - static NetworkCodec codec(NetworkMemberEncoder networkMemberEncoder, NetworkDecoder networkDecoder) { - return NetworkCodec.ofMember(networkMemberEncoder, networkDecoder); - } - -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java index b3cb840f3..39542a4b7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java @@ -1,55 +1,44 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload; import io.netty.buffer.Unpooled; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; -import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.CancelBlockUpdateData; -import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientBlockStateSizeData; -import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientCustomBlockData; +import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.CancelBlockUpdatePacket; +import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientBlockStateSizePacket; +import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientCustomBlockPacket; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.network.ModPacket; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.plugin.network.NetworkManager; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.ResourceKey; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Optional; public class PayloadHelper { - private static final Map, Byte> classToType = new HashMap<>(); - private static final Map> typeToCodec = new HashMap<>(); - private static final AtomicInteger typeCounter = new AtomicInteger(0); public static void registerDataTypes() { - registerDataType(ClientCustomBlockData.class, ClientCustomBlockData.CODEC); - registerDataType(CancelBlockUpdateData.class, CancelBlockUpdateData.CODEC); - registerDataType(ClientBlockStateSizeData.class, ClientBlockStateSizeData.CODEC); + registerDataType(ClientCustomBlockPacket.TYPE, ClientCustomBlockPacket.CODEC); + registerDataType(CancelBlockUpdatePacket.TYPE, CancelBlockUpdatePacket.CODEC); + registerDataType(ClientBlockStateSizePacket.TYPE, ClientBlockStateSizePacket.CODEC); } - @SuppressWarnings("unchecked") - private static void registerDataType(Class dataClass, NetworkCodec codec) { - if (classToType.containsKey(dataClass)) { - CraftEngine.instance().logger().warn("Duplicate data type class: " + dataClass.getName()); - return; - } - int next = typeCounter.getAndIncrement(); - if (next > 255) { - throw new IllegalStateException("Too many data types registered, byte index overflow (max 256)"); - } - byte type = (byte) next; - classToType.put((Class) dataClass, type); - typeToCodec.put(type, (NetworkCodec) codec); + public static void registerDataType(ResourceKey> key, NetworkCodec codec) { + ((WritableRegistry>) BuiltInRegistries.MOD_PACKET).register(key, codec); } - public static void sendData(NetWorkUser user, Data data) { - Class dataClass = data.getClass(); - Byte type = classToType.get(dataClass); - if (type == null) { - CraftEngine.instance().logger().warn("Unknown data type class: " + dataClass.getName()); + public static void sendData(NetWorkUser user, ModPacket data) { + Optional>> optionalType = BuiltInRegistries.MOD_PACKET.get(data.type()); + if (optionalType.isEmpty()) { + CraftEngine.instance().logger().warn("Unknown data type class: " + data.getClass().getName()); return; } - NetworkCodec codec = typeToCodec.get(type); + @SuppressWarnings("unchecked") + NetworkCodec codec = (NetworkCodec) optionalType.get().value(); FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); - buf.writeByte(type); + buf.writeByte(BuiltInRegistries.MOD_PACKET.getId(codec)); codec.encode(buf, data); user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, buf.array()); } @@ -57,13 +46,14 @@ public class PayloadHelper { public static void handleReceiver(Payload payload, NetWorkUser user) { FriendlyByteBuf buf = payload.toBuffer(); byte type = buf.readByte(); - NetworkCodec codec = typeToCodec.get(type); + @SuppressWarnings("unchecked") + NetworkCodec codec = (NetworkCodec) BuiltInRegistries.MOD_PACKET.getValue(type); if (codec == null) { CraftEngine.instance().logger().warn("Unknown data type received: " + type); return; } - Data networkData = codec.decode(buf); + ModPacket networkData = codec.decode(buf); networkData.handle(user); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java deleted file mode 100644 index 6116b6f62..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkDecoder.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; - -public interface NetworkDecoder { - T decode(I in); -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java deleted file mode 100644 index 96fddb2c1..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdateData.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; - -import net.momirealms.craftengine.bukkit.plugin.network.payload.Data; -import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; -import net.momirealms.craftengine.core.util.FriendlyByteBuf; - -public record CancelBlockUpdateData(boolean enabled) implements Data { - public static final NetworkCodec CODEC = Data.codec( - CancelBlockUpdateData::encode, - CancelBlockUpdateData::new - ); - - private CancelBlockUpdateData(FriendlyByteBuf buf) { - this(buf.readBoolean()); - } - - private void encode(FriendlyByteBuf buf) { - buf.writeBoolean(this.enabled); - } - - @Override - public void handle(NetWorkUser user) { - if (!this.enabled) return; - PayloadHelper.sendData(user, this); - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java new file mode 100644 index 000000000..2ff964065 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java @@ -0,0 +1,39 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; + +import net.momirealms.craftengine.core.plugin.network.ModPacket; +import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; + +public record CancelBlockUpdatePacket(boolean enabled) implements ModPacket { + public static final ResourceKey> TYPE = ResourceKey.create( + BuiltInRegistries.MOD_PACKET.key().location(), Key.of("craftengine", "cancel_block_update") + ); + public static final NetworkCodec CODEC = ModPacket.codec( + CancelBlockUpdatePacket::encode, + CancelBlockUpdatePacket::new + ); + + private CancelBlockUpdatePacket(FriendlyByteBuf buf) { + this(buf.readBoolean()); + } + + private void encode(FriendlyByteBuf buf) { + buf.writeBoolean(this.enabled); + } + + @Override + public ResourceKey> type() { + return TYPE; + } + + @Override + public void handle(NetWorkUser user) { + if (!this.enabled) return; + PayloadHelper.sendData(user, this); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java deleted file mode 100644 index ff20e81e9..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizeData.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; - - -import net.momirealms.craftengine.bukkit.plugin.network.payload.Data; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; -import net.momirealms.craftengine.core.util.FriendlyByteBuf; -import net.momirealms.craftengine.core.util.IntIdentityList; - -public record ClientBlockStateSizeData(int blockStateSize) implements Data { - public static final NetworkCodec CODEC = Data.codec( - ClientBlockStateSizeData::encode, - ClientBlockStateSizeData::new - ); - - private ClientBlockStateSizeData(FriendlyByteBuf buf) { - this(buf.readInt()); - } - - private void encode(FriendlyByteBuf buf) { - buf.writeInt(this.blockStateSize); - } - - @Override - public void handle(NetWorkUser user) { - user.setClientBlockList(new IntIdentityList(this.blockStateSize)); - } - -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java new file mode 100644 index 000000000..7e87ee564 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; + + +import net.momirealms.craftengine.core.plugin.network.ModPacket; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.IntIdentityList; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; + +public record ClientBlockStateSizePacket(int blockStateSize) implements ModPacket { + public static final ResourceKey> TYPE = ResourceKey.create( + BuiltInRegistries.MOD_PACKET.key().location(), Key.of("craftengine", "client_block_state_size") + ); + public static final NetworkCodec CODEC = ModPacket.codec( + ClientBlockStateSizePacket::encode, + ClientBlockStateSizePacket::new + ); + + private ClientBlockStateSizePacket(FriendlyByteBuf buf) { + this(buf.readInt()); + } + + private void encode(FriendlyByteBuf buf) { + buf.writeInt(this.blockStateSize); + } + + @Override + public ResourceKey> type() { + return TYPE; + } + + @Override + public void handle(NetWorkUser user) { + user.setClientBlockList(new IntIdentityList(this.blockStateSize)); + } + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java similarity index 56% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java index c24d0f403..9bfe2a7f8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockData.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java @@ -3,20 +3,26 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslationArgument; -import net.momirealms.craftengine.bukkit.plugin.network.payload.Data; -import net.momirealms.craftengine.bukkit.plugin.network.payload.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.ModPacket; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.IntIdentityList; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; -public record ClientCustomBlockData(int size) implements Data { - public static final NetworkCodec CODEC = Data.codec( - ClientCustomBlockData::encode, - ClientCustomBlockData::new +public record ClientCustomBlockPacket(int size) implements ModPacket { + public static final ResourceKey> TYPE = ResourceKey.create( + BuiltInRegistries.MOD_PACKET.key().location(), Key.of("craftengine", "client_custom_block") + ); + public static final NetworkCodec CODEC = ModPacket.codec( + ClientCustomBlockPacket::encode, + ClientCustomBlockPacket::new ); - private ClientCustomBlockData(FriendlyByteBuf buf) { + private ClientCustomBlockPacket(FriendlyByteBuf buf) { this(buf.readInt()); } @@ -24,6 +30,11 @@ public record ClientCustomBlockData(int size) implements Data { buf.writeInt(this.size); } + @Override + public ResourceKey> type() { + return TYPE; + } + @Override public void handle(NetWorkUser user) { int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/ModPacket.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/ModPacket.java new file mode 100644 index 000000000..9776a4fee --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/ModPacket.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.core.plugin.network; + +import io.netty.buffer.ByteBuf; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkDecoder; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkMemberEncoder; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.ResourceKey; + +public interface ModPacket { + + ResourceKey> type(); + + default void handle(NetWorkUser user) { + } + + static NetworkCodec codec(NetworkMemberEncoder networkMemberEncoder, NetworkDecoder networkDecoder) { + return NetworkCodec.ofMember(networkMemberEncoder, networkDecoder); + } + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodec.java similarity index 93% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodec.java index 3b60ae459..356ee14f7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodec.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodec.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; +package net.momirealms.craftengine.core.plugin.network.codec; import java.util.function.Function; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodecs.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java similarity index 99% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodecs.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java index 0ff08bc1d..0589a2df5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkCodecs.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; +package net.momirealms.craftengine.core.plugin.network.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkDecoder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkDecoder.java new file mode 100644 index 000000000..b6e1bd5a8 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkDecoder.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.core.plugin.network.codec; + +public interface NetworkDecoder { + T decode(I in); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkEncoder.java similarity index 51% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkEncoder.java index eb17f0476..0c79a9164 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkEncoder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkEncoder.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; +package net.momirealms.craftengine.core.plugin.network.codec; public interface NetworkEncoder { void encode(O out, T value); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkMemberEncoder.java similarity index 60% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkMemberEncoder.java index 49a60707f..0b96c1b7a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/codec/NetworkMemberEncoder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkMemberEncoder.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.payload.codec; +package net.momirealms.craftengine.core.plugin.network.codec; @FunctionalInterface public interface NetworkMemberEncoder { diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index 806b20c22..e90410cab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -40,6 +40,9 @@ import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory import net.momirealms.craftengine.core.plugin.context.function.FunctionFactory; import net.momirealms.craftengine.core.plugin.context.number.NumberProviderFactory; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectorFactory; +import net.momirealms.craftengine.core.plugin.network.ModPacket; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.ResourceKey; public class BuiltInRegistries { @@ -81,6 +84,7 @@ public class BuiltInRegistries { public static final Registry LEGACY_RECIPE_TYPE = createConstantBoundRegistry(Registries.LEGACY_RECIPE_TYPE); public static final Registry> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE); public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE); + public static final Registry> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET); private static Registry createConstantBoundRegistry(ResourceKey> key) { return new ConstantBoundRegistry<>(key); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index d06a53394..8b8725e2d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -40,6 +40,9 @@ import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory import net.momirealms.craftengine.core.plugin.context.function.FunctionFactory; import net.momirealms.craftengine.core.plugin.context.number.NumberProviderFactory; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectorFactory; +import net.momirealms.craftengine.core.plugin.network.ModPacket; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceKey; @@ -83,4 +86,5 @@ public class Registries { public static final ResourceKey> LEGACY_RECIPE_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("legacy_recipe_type")); public static final ResourceKey>> RECIPE_POST_PROCESSOR_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("recipe_post_processor_type")); public static final ResourceKey>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type")); + public static final ResourceKey>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet")); } From 2da91df7173b07673b5636c9f29a5380cb762c5a Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 1 Sep 2025 05:38:03 +0800 Subject: [PATCH 004/226] =?UTF-8?q?refactor(network):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=A4=8D=E6=9D=82=E8=AF=AD=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/network/payload/PayloadHelper.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java index 39542a4b7..0fee4fb84 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/PayloadHelper.java @@ -10,13 +10,10 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.plugin.network.NetworkManager; import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.core.registry.BuiltInRegistries; -import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.ResourceKey; -import java.util.Optional; - public class PayloadHelper { public static void registerDataTypes() { @@ -30,13 +27,12 @@ public class PayloadHelper { } public static void sendData(NetWorkUser user, ModPacket data) { - Optional>> optionalType = BuiltInRegistries.MOD_PACKET.get(data.type()); - if (optionalType.isEmpty()) { + @SuppressWarnings("unchecked") + NetworkCodec codec = (NetworkCodec) BuiltInRegistries.MOD_PACKET.getValue(data.type()); + if (codec == null) { CraftEngine.instance().logger().warn("Unknown data type class: " + data.getClass().getName()); return; } - @SuppressWarnings("unchecked") - NetworkCodec codec = (NetworkCodec) optionalType.get().value(); FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); buf.writeByte(BuiltInRegistries.MOD_PACKET.getId(codec)); codec.encode(buf, data); From d4ca37a3e1bcad8e4be51436ba222a1cc188b08b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 2 Sep 2025 04:34:18 +0800 Subject: [PATCH 005/226] =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=931?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/behavior/BlockItemBehavior.java | 2 +- .../feature/SearchRecipePlayerCommand.java | 3 +- .../feature/SearchUsagePlayerCommand.java | 3 +- .../plugin/command/feature/TestCommand.java | 22 +---- .../plugin/network/PacketConsumers.java | 14 ++- .../protocol/CancelBlockUpdatePacket.java | 4 +- .../protocol/ClientBlockStateSizePacket.java | 2 +- .../protocol/ClientCustomBlockPacket.java | 4 +- .../plugin/user/BukkitServerPlayer.java | 33 ++++--- .../craftengine/bukkit/util/LightUtils.java | 38 ++++++++ .../bukkit/world/BukkitCEWorld.java | 1 + .../craftengine/core/block/CustomBlock.java | 6 +- .../core/block/ImmutableBlockState.java | 15 +++ .../core/block/entity/BlockEntity.java | 91 +++++++++++++++++++ .../core/block/entity/BlockEntityType.java | 6 ++ .../core/block/entity/BlockEntityTypes.java | 5 + .../block/entity/tick/BlockEntityTicker.java | 11 +++ .../tick/ReplaceableTickingBlockEntity.java | 34 +++++++ .../block/entity/tick/TickingBlockEntity.java | 12 +++ .../entity/tick/TickingBlockEntityImpl.java | 51 +++++++++++ .../core/plugin/config/Config.java | 12 +++ .../core/plugin/logger/Debugger.java | 9 +- .../core/plugin/network/NetWorkUser.java | 12 ++- .../core/registry/AbstractMappedRegistry.java | 11 ++- .../core/registry/BuiltInRegistries.java | 86 +++++++++--------- .../core/registry/ConstantBoundRegistry.java | 7 +- .../core/registry/DynamicBoundRegistry.java | 4 +- .../craftengine/core/registry/Registries.java | 2 +- .../craftengine/core/world/CEWorld.java | 77 ++++++++++++---- .../craftengine/core/world/ChunkPos.java | 13 +++ .../craftengine/core/world/SectionPos.java | 4 + .../craftengine/core/world/chunk/CEChunk.java | 65 ++++++++++--- .../core/world/chunk/ChunkStatus.java | 7 ++ .../DefaultBlockEntitySerializer.java | 45 +++------ .../serialization/DefaultChunkSerializer.java | 2 +- 35 files changed, 545 insertions(+), 168 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/BlockEntityTicker.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntity.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index 3422f0352..b6164a8ef 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -73,7 +73,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { return InteractionResult.FAIL; } if (!context.canPlace()) { - return InteractionResult.FAIL; + return InteractionResult.PASS; } Player player = context.getPlayer(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchRecipePlayerCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchRecipePlayerCommand.java index 1082e366a..e1163bcb0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchRecipePlayerCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchRecipePlayerCommand.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.item.recipe.Recipe; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import net.momirealms.craftengine.core.util.ItemUtils; import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -30,7 +31,7 @@ public class SearchRecipePlayerCommand extends BukkitCommandFeature item = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); - if (item == null) { + if (ItemUtils.isEmpty(item)) { handleFeedback(context, MessageConstants.COMMAND_SEARCH_RECIPE_NO_ITEM); return; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchUsagePlayerCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchUsagePlayerCommand.java index 9425e7a3c..9cd21ae36 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchUsagePlayerCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SearchUsagePlayerCommand.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.item.recipe.Recipe; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import net.momirealms.craftengine.core.util.ItemUtils; import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -30,7 +31,7 @@ public class SearchUsagePlayerCommand extends BukkitCommandFeature item = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); - if (item.isEmpty()) { + if (ItemUtils.isEmpty(item)) { handleFeedback(context, MessageConstants.COMMAND_SEARCH_USAGE_NO_ITEM); return; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java index e5f42e169..437855e70 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java @@ -1,15 +1,10 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import org.bukkit.Location; -import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.incendo.cloud.Command; -import org.incendo.cloud.parser.standard.IntegerParser; public class TestCommand extends BukkitCommandFeature { @@ -20,22 +15,9 @@ public class TestCommand extends BukkitCommandFeature { @Override public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder - .required("start", IntegerParser.integerParser(0)) - .senderType(Player.class) .handler(context -> { - Player sender = context.sender(); - int start = context.get("start"); - int x = sender.getChunk().getX() * 16; - int z = sender.getChunk().getZ() * 16; - int y = (sender.getLocation().getBlockY() / 16) * 16; - for (int a = 0; a < 16; a++) { - for (int b = 0; b < 16; b++) { - for (int c = 0; c < 16; c++) { - BlockData blockData = BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(start + a + b * 16 + c * 256)); - sender.getWorld().setBlockData(new Location(sender.getWorld(), x + a, y + b, z + c), blockData); - } - } - } + // DO NOT PUSH ANY CODE FOR TEST COMMAND + // 禁止推送含有实现的Test指令 }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index e5cf08ac7..903712053 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -64,6 +64,7 @@ import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; @@ -263,11 +264,11 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); if (VersionHelper.isOrAbove1_20_2()) { long chunkPos = buf.readLong(); - user.setChunkTrackStatus(new ChunkPos(chunkPos), false); + user.removeTrackedChunk(chunkPos); } else { int x = buf.readInt(); int y = buf.readInt(); - user.setChunkTrackStatus(ChunkPos.of(x, y), false); + user.removeTrackedChunk(ChunkPos.asLong(x, y)); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundForgetLevelChunkPacket", e); @@ -280,7 +281,6 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); int chunkX = buf.readInt(); int chunkZ = buf.readInt(); - player.setChunkTrackStatus(ChunkPos.of(chunkX, chunkZ), true); boolean named = !VersionHelper.isOrAbove1_20_2(); // ClientboundLevelChunkPacketData int heightmapsCount = 0; @@ -366,6 +366,9 @@ public class PacketConsumers { } buffer = newBuf.array(); } + + // 开始修改 + event.setChanged(true); buf.clear(); buf.writeVarInt(event.packetID()); buf.writeInt(chunkX); @@ -394,7 +397,10 @@ public class PacketConsumers { buf.writeBitSet(emptyBlockYMask); buf.writeByteArrayList(skyUpdates); buf.writeByteArrayList(blockUpdates); - event.setChanged(true); + + ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + // 记录加载的区块 + player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelChunkWithLightPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java index 2ff964065..215046487 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/CancelBlockUpdatePacket.java @@ -1,9 +1,9 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; -import net.momirealms.craftengine.core.plugin.network.ModPacket; import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; -import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; +import net.momirealms.craftengine.core.plugin.network.ModPacket; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.Key; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java index 7e87ee564..e2840c816 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; import net.momirealms.craftengine.core.plugin.network.ModPacket; -import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.IntIdentityList; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java index 9bfe2a7f8..911438c58 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java @@ -3,10 +3,10 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslationArgument; -import net.momirealms.craftengine.core.plugin.network.ModPacket; -import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.bukkit.util.RegistryUtils; +import net.momirealms.craftengine.core.plugin.network.ModPacket; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.IntIdentityList; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 04927c89c..c448b31cd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.plugin.user; +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; import com.google.common.collect.Lists; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; @@ -35,9 +36,9 @@ import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldEvents; +import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; @@ -113,11 +114,9 @@ public class BukkitServerPlayer extends Player { // cooldown data private CooldownData cooldownData; // tracked chunks - private final Set trackedChunks = Collections.synchronizedSet(new HashSet<>()); - // relighted chunks - private final Set relightedChunks = Collections.synchronizedSet(new HashSet<>()); + private ConcurrentLong2ReferenceChainedHashTable trackedChunks; // entity view - private final Map entityTypeView = new ConcurrentHashMap<>(); + private Map entityTypeView; public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; @@ -139,6 +138,8 @@ public class BukkitServerPlayer extends Player { this.uuid = player.getUniqueId(); this.name = player.getName(); byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY); + this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(768, 0.5f); + this.entityTypeView = new ConcurrentHashMap<>(256); try { this.cooldownData = CooldownData.fromBytes(bytes); } catch (IOException e) { @@ -1050,17 +1051,23 @@ public class BukkitServerPlayer extends Player { } @Override - public boolean isChunkTracked(ChunkPos chunkPos) { - return this.trackedChunks.contains(chunkPos); + public boolean isChunkTracked(long chunkPos) { + return this.trackedChunks.containsKey(chunkPos); } @Override - public void setChunkTrackStatus(ChunkPos chunkPos, boolean tracked) { - if (tracked) { - this.trackedChunks.add(chunkPos); - } else { - this.trackedChunks.remove(chunkPos); - } + public ChunkStatus getTrackedChunk(long chunkPos) { + return this.trackedChunks.get(chunkPos); + } + + @Override + public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) { + this.trackedChunks.put(chunkPos, chunkStatus); + } + + @Override + public void removeTrackedChunk(long chunkPos) { + this.trackedChunks.remove(chunkPos); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java index 636c5c1e3..f51715074 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java @@ -36,4 +36,42 @@ public final class LightUtils { CraftEngine.instance().logger().warn("Could not update light for world " + world.getName(), e); } } +// +// public static void relightChunk(BukkitServerPlayer player, ChunkPos pos) { +// long chunkKey = pos.longKey; +// ChunkStatus status = player.getTrackedChunk(chunkKey); +// // 不处理未加载区块 +// if (status == null || status.relighted()) return; +// for (ChunkPos anotherPos : pos.adjacentChunkPos()) { +// // 要求周围区块必须都加载 +// if (player.getTrackedChunk(anotherPos.longKey) == null) { +// return; +// } +// } +// status.setRelighted(true); +// net.momirealms.craftengine.core.world.World world = player.world(); +// Object serverLevel = world.serverWorld(); +// Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); +// Object chunkHolder = FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, chunkKey); +// if (chunkHolder == null) return; +// CEWorld ceWorld = BukkitWorldManager.instance().getWorld(world.uuid()); +// CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkKey); +// if (ceChunk == null) return; +// CESection[] sections = ceChunk.sections(); +// BitSet bitSet = new BitSet(); +// for (int i = 0; i < sections.length; i++) { +// if (!sections[i].statesContainer().isEmpty()) { +// bitSet.set(i); +// } +// } +// if (bitSet.isEmpty()) return; +// try { +// Object lightEngine = CoreReflections.field$ChunkHolder$lightEngine.get(chunkHolder); +// Object chunkPos = FastNMS.INSTANCE.constructor$ChunkPos((int) chunkKey, (int) (chunkKey >> 32)); +// Object lightPacket = FastNMS.INSTANCE.constructor$ClientboundLightUpdatePacket(chunkPos, lightEngine, bitSet, bitSet); +// player.sendPacket(lightPacket, false); +// } catch (Throwable t) { +// CraftEngine.instance().logger().warn("Could not send relight packet for " + player.name() + " at " + player.world().name() + " " + pos.x + "," + pos.z, t); +// } +// } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index aadb65e6f..8d13cf1b1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -23,6 +23,7 @@ public class BukkitCEWorld extends CEWorld { @Override public void tick() { + super.tick(); HashSet poses; synchronized (super.updatedSectionSet) { poses = new HashSet<>(super.updatedSectionSet); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java index c40700c12..13948d536 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java @@ -19,11 +19,13 @@ public interface CustomBlock { Key id(); - @Nullable LootTable lootTable(); + @Nullable + LootTable lootTable(); void execute(PlayerOptionalContext context, EventTrigger trigger); - @NotNull BlockStateVariantProvider variantProvider(); + @NotNull + BlockStateVariantProvider variantProvider(); List getPossibleStates(CompoundTag nbt); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index fbf649bba..aac373b56 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.core.block; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; @@ -24,6 +26,7 @@ public final class ImmutableBlockState extends BlockStateHolder { private BlockBehavior behavior; private Integer hashCode; private BlockSettings settings; + private BlockEntityType blockEntityType; ImmutableBlockState( Holder owner, @@ -48,6 +51,14 @@ public final class ImmutableBlockState extends BlockStateHolder { this.settings = settings; } + public BlockEntityType blockEntityType() { + return blockEntityType; + } + + public void setBlockEntityType(BlockEntityType blockEntityType) { + this.blockEntityType = blockEntityType; + } + public boolean isEmpty() { return this == EmptyBlock.STATE; } @@ -67,6 +78,10 @@ public final class ImmutableBlockState extends BlockStateHolder { return this.hashCode; } + public boolean hasBlockEntity() { + return this.blockEntityType != null; + } + public BlockStateWrapper customBlockState() { return this.customBlockState; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java new file mode 100644 index 000000000..0c988377c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -0,0 +1,91 @@ +package net.momirealms.craftengine.core.block.entity; + +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.ChunkPos; +import net.momirealms.craftengine.core.world.SectionPos; +import net.momirealms.sparrow.nbt.CompoundTag; + +public abstract class BlockEntity { + protected final BlockPos pos; + protected final ImmutableBlockState blockState; + protected BlockEntityType type; + protected CEWorld world; + protected boolean valid; + + protected BlockEntity(BlockEntityType type, BlockPos pos, ImmutableBlockState blockState) { + this.pos = pos; + this.blockState = blockState; + this.type = type; + } + + public final CompoundTag saveAsTag() { + CompoundTag tag = new CompoundTag(); + this.savePos(tag); + this.saveCustomData(tag); + return tag; + } + + public CEWorld world() { + return world; + } + + public void setWorld(CEWorld world) { + this.world = world; + } + + public boolean isValid() { + return valid; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + + private void savePos(CompoundTag tag) { + tag.putInt("x", this.pos.x()); + tag.putInt("y", this.pos.y()); + tag.putInt("z", this.pos.z()); + } + + protected void saveCustomData(CompoundTag tag) { + } + + protected void readCustomData(CompoundTag tag) { + } + + public static BlockPos readPos(CompoundTag tag) { + return new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); + } + + public BlockEntityType type() { + return type; + } + + public static BlockPos readPosAndVerify(CompoundTag tag, ChunkPos chunkPos) { + int x = tag.getInt("x", 0); + int y = tag.getInt("y", 0); + int z = tag.getInt("z", 0); + int sectionX = SectionPos.blockToSectionCoord(x); + int sectionZ = SectionPos.blockToSectionCoord(z); + if (sectionX != chunkPos.x || sectionZ != chunkPos.z) { + x = chunkPos.x * 16 + SectionPos.sectionRelative(x); + z = chunkPos.z * 16 + SectionPos.sectionRelative(z); + } + return new BlockPos(x, y, z); + } + + public BlockPos pos() { + return pos; + } + + public boolean isValidBlockState(ImmutableBlockState blockState) { + return blockState.blockEntityType() == this.type; + } + + public interface Factory { + + T create(BlockPos pos, ImmutableBlockState state); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityType.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityType.java new file mode 100644 index 000000000..4291b6de5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityType.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.block.entity; + +import net.momirealms.craftengine.core.util.Key; + +public record BlockEntityType(Key id, BlockEntity.Factory factory) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java new file mode 100644 index 000000000..1f90b42c5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.core.block.entity; + +public class BlockEntityTypes { + +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/BlockEntityTicker.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/BlockEntityTicker.java new file mode 100644 index 000000000..6b5f57399 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/BlockEntityTicker.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.core.block.entity.tick; + +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; + +public interface BlockEntityTicker { + + void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, T blockEntity); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java new file mode 100644 index 000000000..c9526faae --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.block.entity.tick; + +import net.momirealms.craftengine.core.world.BlockPos; + +public class ReplaceableTickingBlockEntity implements TickingBlockEntity { + private TickingBlockEntity target; + + public ReplaceableTickingBlockEntity(TickingBlockEntity target) { + this.target = target; + } + + public TickingBlockEntity target() { + return target; + } + + public void setTarget(TickingBlockEntity target) { + this.target = target; + } + + @Override + public BlockPos pos() { + return this.target.pos(); + } + + @Override + public void tick() { + this.target.tick(); + } + + @Override + public boolean isValid() { + return this.target.isValid(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntity.java new file mode 100644 index 000000000..99599744d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntity.java @@ -0,0 +1,12 @@ +package net.momirealms.craftengine.core.block.entity.tick; + +import net.momirealms.craftengine.core.world.BlockPos; + +public interface TickingBlockEntity { + + void tick(); + + boolean isValid(); + + BlockPos pos(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java new file mode 100644 index 000000000..ee323e5a9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.block.entity.tick; + +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.logger.Debugger; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.chunk.CEChunk; + +public class TickingBlockEntityImpl implements TickingBlockEntity { + private final T blockEntity; + private final BlockEntityTicker ticker; + private final CEChunk chunk; + + public TickingBlockEntityImpl(CEChunk chunk, T blockEntity, BlockEntityTicker ticker) { + this.blockEntity = blockEntity; + this.ticker = ticker; + this.chunk = chunk; + } + + @Override + public BlockPos pos() { + return this.blockEntity.pos(); + } + + @Override + public void tick() { + // 已无效 + if (!this.isValid()) return; + // 还没加载完全 + if (this.blockEntity.world() == null) return; + BlockPos pos = pos(); + ImmutableBlockState state = this.chunk.getBlockState(pos); + // 不是合法方块 + if (!this.blockEntity.isValidBlockState(state)) { + this.chunk.removeBlockEntity(pos); + Debugger.BLOCK_ENTITY.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null); + return; + } + try { + this.ticker.tick(this.chunk.world(), pos, state, this.blockEntity); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to tick block entity(" + this.blockEntity.getClass().getSimpleName() + ") at world " + this.chunk.world().name() + " " + pos, t); + } + } + + @Override + public boolean isValid() { + return this.blockEntity.isValid(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 8956ca100..2a207bb57 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -120,6 +120,7 @@ public class Config { protected boolean block$predict_breaking; protected int block$predict_breaking_interval; protected double block$extended_interaction_range; + protected boolean block$chunk_relighter; protected boolean recipe$enable; protected boolean recipe$disable_vanilla_recipes$all; @@ -224,6 +225,8 @@ public class Config { .builder() .setVersioning(new BasicVersioning("config-version")) .addIgnoredRoute(PluginProperties.getValue("config"), "resource-pack.delivery.hosting", '.') + .addIgnoredRoute(PluginProperties.getValue("config"), "chunk-system.process-invalid-blocks.convert", '.') + .addIgnoredRoute(PluginProperties.getValue("config"), "chunk-system.process-invalid-furniture.convert", '.') .build()); } try { @@ -384,6 +387,7 @@ public class Config { block$predict_breaking = config.getBoolean("block.predict-breaking.enable", true); block$predict_breaking_interval = Math.max(config.getInt("block.predict-breaking.interval", 10), 1); block$extended_interaction_range = Math.max(config.getDouble("block.predict-breaking.extended-interaction-range", 0.5), 0.0); + block$chunk_relighter = config.getBoolean("block.chunk-relighter", true); // recipe recipe$enable = config.getBoolean("recipe.enable", true); @@ -449,6 +453,10 @@ public class Config { return instance.debug$item; } + public static boolean debugBlockEntity() { + return false; + } + public static boolean debugFurniture() { return instance.debug$furniture; } @@ -873,6 +881,10 @@ public class Config { return instance.item$update_triggers$drop; } + public static boolean enableChunkRelighter() { + return instance.block$chunk_relighter; + } + public void setObf(boolean enable) { this.resource_pack$protection$obfuscation$enable = enable; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java index fba7f7fa9..9ac5d509d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java @@ -10,7 +10,8 @@ public enum Debugger { PACKET(Config::debugPacket), FURNITURE(Config::debugFurniture), RESOURCE_PACK(Config::debugResourcePack), - ITEM(Config::debugItem); + ITEM(Config::debugItem), + BLOCK_ENTITY(Config::debugBlockEntity); private final Supplier condition; @@ -26,7 +27,11 @@ public enum Debugger { public void warn(Supplier message, Throwable e) { if (this.condition.get()) { - CraftEngine.instance().logger().warn("[DEBUG] " + message.get(), e); + if (e != null) { + CraftEngine.instance().logger().warn("[DEBUG] " + message.get(), e); + } else { + CraftEngine.instance().logger().warn("[DEBUG] " + message.get()); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 32a50d188..eaf72bf24 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -6,7 +6,7 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.ChunkPos; +import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -79,13 +79,17 @@ public interface NetWorkUser { boolean shouldProcessFinishConfiguration(); - boolean isChunkTracked(ChunkPos chunkPos); + boolean isChunkTracked(long chunkPos); - void setChunkTrackStatus(ChunkPos chunkPos, boolean tracked); + ChunkStatus getTrackedChunk(long chunkPos); + + void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus); void clearTrackedChunks(); - void setClientBlockList(IntIdentityList blockList); + void removeTrackedChunk(long chunkPos); IntIdentityList clientBlockList(); + + void setClientBlockList(IntIdentityList integers); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/AbstractMappedRegistry.java b/core/src/main/java/net/momirealms/craftengine/core/registry/AbstractMappedRegistry.java index efefaa5ba..c095c0f32 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/AbstractMappedRegistry.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/AbstractMappedRegistry.java @@ -9,12 +9,15 @@ import java.util.*; public abstract class AbstractMappedRegistry implements WritableRegistry { protected final ResourceKey> key; - protected final Map> byResourceLocation = new HashMap<>(512); - protected final Map, Holder.Reference> byResourceKey = new HashMap<>(512); - protected final List> byId = new ArrayList<>(512); + protected final Map> byResourceLocation; + protected final Map, Holder.Reference> byResourceKey; + protected final List> byId; - protected AbstractMappedRegistry(ResourceKey> key) { + protected AbstractMappedRegistry(ResourceKey> key, int expectedSize) { this.key = key; + this.byResourceLocation = new HashMap<>(expectedSize); + this.byResourceKey = new HashMap<>(expectedSize); + this.byId = new ArrayList<>(expectedSize); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index e90410cab..d002e007a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -46,51 +46,51 @@ import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.ResourceKey; public class BuiltInRegistries { - public static final Registry BLOCK = createDynamicBoundRegistry(Registries.BLOCK); - public static final Registry BLOCK_BEHAVIOR_FACTORY = createConstantBoundRegistry(Registries.BLOCK_BEHAVIOR_FACTORY); - public static final Registry> ITEM_DATA_MODIFIER_FACTORY = createConstantBoundRegistry(Registries.ITEM_DATA_MODIFIER_FACTORY); - public static final Registry ITEM_BEHAVIOR_FACTORY = createConstantBoundRegistry(Registries.ITEM_BEHAVIOR_FACTORY); - public static final Registry PROPERTY_FACTORY = createConstantBoundRegistry(Registries.PROPERTY_FACTORY); - public static final Registry> LOOT_FUNCTION_FACTORY = createConstantBoundRegistry(Registries.LOOT_FUNCTION_FACTORY); - public static final Registry> LOOT_CONDITION_FACTORY = createConstantBoundRegistry(Registries.LOOT_CONDITION_FACTORY); - public static final Registry> LOOT_ENTRY_CONTAINER_FACTORY = createConstantBoundRegistry(Registries.LOOT_ENTRY_CONTAINER_FACTORY); - public static final Registry NUMBER_PROVIDER_FACTORY = createConstantBoundRegistry(Registries.NUMBER_PROVIDER_FACTORY); - public static final Registry TEMPLATE_ARGUMENT_FACTORY = createConstantBoundRegistry(Registries.TEMPLATE_ARGUMENT_FACTORY); - public static final Registry ITEM_MODEL_FACTORY = createConstantBoundRegistry(Registries.ITEM_MODEL_FACTORY); - public static final Registry ITEM_MODEL_READER = createConstantBoundRegistry(Registries.ITEM_MODEL_READER); - public static final Registry TINT_FACTORY = createConstantBoundRegistry(Registries.TINT_FACTORY); - public static final Registry TINT_READER = createConstantBoundRegistry(Registries.TINT_READER); - public static final Registry SPECIAL_MODEL_FACTORY = createConstantBoundRegistry(Registries.SPECIAL_MODEL_FACTORY); - public static final Registry SPECIAL_MODEL_READER = createConstantBoundRegistry(Registries.SPECIAL_MODEL_READER); - public static final Registry RANGE_DISPATCH_PROPERTY_FACTORY = createConstantBoundRegistry(Registries.RANGE_DISPATCH_PROPERTY_FACTORY); - public static final Registry RANGE_DISPATCH_PROPERTY_READER = createConstantBoundRegistry(Registries.RANGE_DISPATCH_PROPERTY_READER); - public static final Registry CONDITION_PROPERTY_FACTORY = createConstantBoundRegistry(Registries.CONDITION_PROPERTY_FACTORY); - public static final Registry CONDITION_PROPERTY_READER = createConstantBoundRegistry(Registries.CONDITION_PROPERTY_READER); - public static final Registry SELECT_PROPERTY_FACTORY = createConstantBoundRegistry(Registries.SELECT_PROPERTY_FACTORY); - public static final Registry SELECT_PROPERTY_READER = createConstantBoundRegistry(Registries.SELECT_PROPERTY_READER); - public static final Registry>> RECIPE_SERIALIZER = createConstantBoundRegistry(Registries.RECIPE_FACTORY); - public static final Registry FORMULA_FACTORY = createConstantBoundRegistry(Registries.FORMULA_FACTORY); - public static final Registry> PATH_MATCHER_FACTORY = createConstantBoundRegistry(Registries.PATH_MATCHER_FACTORY); - public static final Registry RESOLUTION_FACTORY = createConstantBoundRegistry(Registries.RESOLUTION_FACTORY); - public static final Registry SMITHING_RESULT_PROCESSOR_FACTORY = createConstantBoundRegistry(Registries.SMITHING_RESULT_PROCESSOR_FACTORY); - public static final Registry HITBOX_FACTORY = createConstantBoundRegistry(Registries.HITBOX_FACTORY); - public static final Registry RESOURCE_PACK_HOST_FACTORY = createConstantBoundRegistry(Registries.RESOURCE_PACK_HOST_FACTORY); - public static final Registry> EVENT_FUNCTION_FACTORY = createConstantBoundRegistry(Registries.EVENT_FUNCTION_FACTORY); - public static final Registry> EVENT_CONDITION_FACTORY = createConstantBoundRegistry(Registries.EVENT_CONDITION_FACTORY); - public static final Registry> PLAYER_SELECTOR_FACTORY = createConstantBoundRegistry(Registries.PLAYER_SELECTOR_FACTORY); - public static final Registry EQUIPMENT_FACTORY = createConstantBoundRegistry(Registries.EQUIPMENT_FACTORY); - public static final Registry SLOT_DISPLAY_TYPE = createConstantBoundRegistry(Registries.SLOT_DISPLAY_TYPE); - public static final Registry RECIPE_DISPLAY_TYPE = createConstantBoundRegistry(Registries.RECIPE_DISPLAY_TYPE); - public static final Registry LEGACY_RECIPE_TYPE = createConstantBoundRegistry(Registries.LEGACY_RECIPE_TYPE); - public static final Registry> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE); - public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE); - public static final Registry> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET); + public static final Registry BLOCK = createDynamicBoundRegistry(Registries.BLOCK, 512); + public static final Registry BLOCK_BEHAVIOR_FACTORY = createConstantBoundRegistry(Registries.BLOCK_BEHAVIOR_FACTORY, 64); + public static final Registry> ITEM_DATA_MODIFIER_FACTORY = createConstantBoundRegistry(Registries.ITEM_DATA_MODIFIER_FACTORY, 64); + public static final Registry ITEM_BEHAVIOR_FACTORY = createConstantBoundRegistry(Registries.ITEM_BEHAVIOR_FACTORY, 64); + public static final Registry PROPERTY_FACTORY = createConstantBoundRegistry(Registries.PROPERTY_FACTORY, 16); + public static final Registry> LOOT_FUNCTION_FACTORY = createConstantBoundRegistry(Registries.LOOT_FUNCTION_FACTORY, 32); + public static final Registry> LOOT_CONDITION_FACTORY = createConstantBoundRegistry(Registries.LOOT_CONDITION_FACTORY, 32); + public static final Registry> LOOT_ENTRY_CONTAINER_FACTORY = createConstantBoundRegistry(Registries.LOOT_ENTRY_CONTAINER_FACTORY, 16); + public static final Registry NUMBER_PROVIDER_FACTORY = createConstantBoundRegistry(Registries.NUMBER_PROVIDER_FACTORY, 16); + public static final Registry TEMPLATE_ARGUMENT_FACTORY = createConstantBoundRegistry(Registries.TEMPLATE_ARGUMENT_FACTORY, 16); + public static final Registry ITEM_MODEL_FACTORY = createConstantBoundRegistry(Registries.ITEM_MODEL_FACTORY, 16); + public static final Registry ITEM_MODEL_READER = createConstantBoundRegistry(Registries.ITEM_MODEL_READER, 16); + public static final Registry TINT_FACTORY = createConstantBoundRegistry(Registries.TINT_FACTORY, 16); + public static final Registry TINT_READER = createConstantBoundRegistry(Registries.TINT_READER, 16); + public static final Registry SPECIAL_MODEL_FACTORY = createConstantBoundRegistry(Registries.SPECIAL_MODEL_FACTORY, 16); + public static final Registry SPECIAL_MODEL_READER = createConstantBoundRegistry(Registries.SPECIAL_MODEL_READER, 16); + public static final Registry RANGE_DISPATCH_PROPERTY_FACTORY = createConstantBoundRegistry(Registries.RANGE_DISPATCH_PROPERTY_FACTORY, 16); + public static final Registry RANGE_DISPATCH_PROPERTY_READER = createConstantBoundRegistry(Registries.RANGE_DISPATCH_PROPERTY_READER, 16); + public static final Registry CONDITION_PROPERTY_FACTORY = createConstantBoundRegistry(Registries.CONDITION_PROPERTY_FACTORY, 16); + public static final Registry CONDITION_PROPERTY_READER = createConstantBoundRegistry(Registries.CONDITION_PROPERTY_READER, 16); + public static final Registry SELECT_PROPERTY_FACTORY = createConstantBoundRegistry(Registries.SELECT_PROPERTY_FACTORY, 16); + public static final Registry SELECT_PROPERTY_READER = createConstantBoundRegistry(Registries.SELECT_PROPERTY_READER, 16); + public static final Registry>> RECIPE_SERIALIZER = createConstantBoundRegistry(Registries.RECIPE_FACTORY, 16); + public static final Registry FORMULA_FACTORY = createConstantBoundRegistry(Registries.FORMULA_FACTORY, 16); + public static final Registry> PATH_MATCHER_FACTORY = createConstantBoundRegistry(Registries.PATH_MATCHER_FACTORY, 16); + public static final Registry RESOLUTION_FACTORY = createConstantBoundRegistry(Registries.RESOLUTION_FACTORY, 16); + public static final Registry SMITHING_RESULT_PROCESSOR_FACTORY = createConstantBoundRegistry(Registries.SMITHING_RESULT_PROCESSOR_FACTORY, 16); + public static final Registry HITBOX_FACTORY = createConstantBoundRegistry(Registries.HITBOX_FACTORY, 16); + public static final Registry RESOURCE_PACK_HOST_FACTORY = createConstantBoundRegistry(Registries.RESOURCE_PACK_HOST_FACTORY, 16); + public static final Registry> EVENT_FUNCTION_FACTORY = createConstantBoundRegistry(Registries.EVENT_FUNCTION_FACTORY, 128); + public static final Registry> EVENT_CONDITION_FACTORY = createConstantBoundRegistry(Registries.EVENT_CONDITION_FACTORY, 128); + public static final Registry> PLAYER_SELECTOR_FACTORY = createConstantBoundRegistry(Registries.PLAYER_SELECTOR_FACTORY, 16); + public static final Registry EQUIPMENT_FACTORY = createConstantBoundRegistry(Registries.EQUIPMENT_FACTORY, 8); + public static final Registry SLOT_DISPLAY_TYPE = createConstantBoundRegistry(Registries.SLOT_DISPLAY_TYPE, 16); + public static final Registry RECIPE_DISPLAY_TYPE = createConstantBoundRegistry(Registries.RECIPE_DISPLAY_TYPE, 16); + public static final Registry LEGACY_RECIPE_TYPE = createConstantBoundRegistry(Registries.LEGACY_RECIPE_TYPE, 16); + public static final Registry> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE, 16); + public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE, 16); + public static final Registry> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET, 16); - private static Registry createConstantBoundRegistry(ResourceKey> key) { - return new ConstantBoundRegistry<>(key); + private static Registry createConstantBoundRegistry(ResourceKey> key, int expectedSize) { + return new ConstantBoundRegistry<>(key, expectedSize); } - private static Registry createDynamicBoundRegistry(ResourceKey> key) { - return new DynamicBoundRegistry<>(key); + private static Registry createDynamicBoundRegistry(ResourceKey> key, int expectedSize) { + return new DynamicBoundRegistry<>(key, expectedSize); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java b/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java index cf3da4675..1c24d56fc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java @@ -12,10 +12,11 @@ import java.util.Objects; public class ConstantBoundRegistry extends AbstractMappedRegistry { protected final Reference2IntMap toId = MCUtils.make(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); - protected final Map> byValue = new IdentityHashMap<>(512); + protected final Map> byValue; - public ConstantBoundRegistry(ResourceKey> key) { - super(key); + public ConstantBoundRegistry(ResourceKey> key, int expectedSize) { + super(key, expectedSize); + this.byValue = new IdentityHashMap<>(expectedSize); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/DynamicBoundRegistry.java b/core/src/main/java/net/momirealms/craftengine/core/registry/DynamicBoundRegistry.java index 47808d13f..fef205d22 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/DynamicBoundRegistry.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/DynamicBoundRegistry.java @@ -8,8 +8,8 @@ import java.util.Objects; public class DynamicBoundRegistry extends AbstractMappedRegistry { - public DynamicBoundRegistry(ResourceKey> key) { - super(key); + public DynamicBoundRegistry(ResourceKey> key, int expectedSize) { + super(key, expectedSize); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index 8b8725e2d..b4fda0671 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -86,5 +86,5 @@ public class Registries { public static final ResourceKey> LEGACY_RECIPE_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("legacy_recipe_type")); public static final ResourceKey>> RECIPE_POST_PROCESSOR_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("recipe_post_processor_type")); public static final ResourceKey>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type")); - public static final ResourceKey>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet")); + public static final ResourceKey>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet_type")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 62dfa77da..2807754bd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -1,7 +1,10 @@ package net.momirealms.craftengine.core.world; import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; @@ -9,8 +12,7 @@ import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.Collection; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public abstract class CEWorld { @@ -20,6 +22,9 @@ public abstract class CEWorld { protected final WorldDataStorage worldDataStorage; protected final WorldHeight worldHeightAccessor; protected final Set updatedSectionSet = ConcurrentHashMap.newKeySet(128); + protected final List tickingBlockEntities = new ArrayList<>(); + protected final List pendingTickingBlockEntities = new ArrayList<>(); + protected boolean isTickingBlockEntities = false; private CEChunk lastChunk; private long lastChunkPos; @@ -40,6 +45,14 @@ public abstract class CEWorld { this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; } + public String name() { + return this.world.name(); + } + + public UUID uuid() { + return this.world.uuid(); + } + public void save() { try { for (ConcurrentLong2ReferenceChainedHashTable.TableEntry entry : this.loadedChunkMap.entrySet()) { @@ -76,16 +89,6 @@ public abstract class CEWorld { @Nullable public CEChunk getChunkAtIfLoaded(long chunkPos) { - return getChunkAtIfLoadedMainThread(chunkPos); - } - - @Nullable - public CEChunk getChunkAtIfLoaded(int x, int z) { - return getChunkAtIfLoaded(ChunkPos.asLong(x, z)); - } - - @Nullable - public CEChunk getChunkAtIfLoadedMainThread(long chunkPos) { if (chunkPos == this.lastChunkPos) { return this.lastChunk; } @@ -98,14 +101,11 @@ public abstract class CEWorld { } @Nullable - public CEChunk getChunkAtIfLoadedMainThread(int x, int z) { - return getChunkAtIfLoadedMainThread(ChunkPos.asLong(x, z)); - } - - public WorldHeight worldHeight() { - return worldHeightAccessor; + public CEChunk getChunkAtIfLoaded(int x, int z) { + return getChunkAtIfLoaded(ChunkPos.asLong(x, z)); } + @Nullable public ImmutableBlockState getBlockStateAtIfLoaded(int x, int y, int z) { CEChunk chunk = getChunkAtIfLoaded(x >> 4, z >> 4); if (chunk == null) { @@ -114,6 +114,7 @@ public abstract class CEWorld { return chunk.getBlockState(x, y, z); } + @Nullable public ImmutableBlockState getBlockStateAtIfLoaded(BlockPos blockPos) { CEChunk chunk = getChunkAtIfLoaded(blockPos.x() >> 4, blockPos.z() >> 4); if (chunk == null) { @@ -123,7 +124,7 @@ public abstract class CEWorld { } public boolean setBlockStateAtIfLoaded(BlockPos blockPos, ImmutableBlockState blockState) { - if (worldHeightAccessor.isOutsideBuildHeight(blockPos)) { + if (this.worldHeightAccessor.isOutsideBuildHeight(blockPos)) { return false; } CEChunk chunk = getChunkAtIfLoaded(blockPos.x() >> 4, blockPos.z() >> 4); @@ -134,6 +135,18 @@ public abstract class CEWorld { return true; } + @Nullable + public BlockEntity getBlockEntityAtIfLoaded(BlockPos blockPos) { + if (this.worldHeightAccessor.isOutsideBuildHeight(blockPos)) { + return null; + } + CEChunk chunk = getChunkAtIfLoaded(blockPos.x() >> 4, blockPos.z() >> 4); + if (chunk == null) { + return null; + } + return chunk.getBlockEntity(blockPos); + } + public WorldDataStorage worldDataStorage() { return worldDataStorage; } @@ -146,5 +159,29 @@ public abstract class CEWorld { this.updatedSectionSet.addAll(pos); } - public abstract void tick(); + public WorldHeight worldHeight() { + return this.worldHeightAccessor; + } + + public void tick() { + this.tickBlockEntities(); + } + + protected void tickBlockEntities() { + this.isTickingBlockEntities = true; + if (!this.pendingTickingBlockEntities.isEmpty()) { + this.tickingBlockEntities.addAll(this.pendingTickingBlockEntities); + this.pendingTickingBlockEntities.clear(); + } + ReferenceOpenHashSet toRemove = new ReferenceOpenHashSet<>(); + for (TickingBlockEntity blockEntity : this.tickingBlockEntities) { + if (!blockEntity.isValid()) { + blockEntity.tick(); + } else { + toRemove.add(blockEntity); + } + } + this.tickingBlockEntities.removeAll(toRemove); + this.isTickingBlockEntities = false; + } } 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 a053afd79..32eb4788f 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 @@ -65,10 +65,23 @@ public class ChunkPos { return longKey; } + public ChunkPos[] adjacentChunkPos() { + return adjacentChunkPos(this); + } + public static long asLong(int chunkX, int chunkZ) { return (long) chunkX & 4294967295L | ((long) chunkZ & 4294967295L) << 32; } + public static ChunkPos[] adjacentChunkPos(ChunkPos chunkPos) { + return new ChunkPos[] { + new ChunkPos(chunkPos.x, chunkPos.z - 1), + new ChunkPos(chunkPos.x, chunkPos.z + 1), + new ChunkPos(chunkPos.x + 1, chunkPos.z), + new ChunkPos(chunkPos.x - 1, chunkPos.z) + }; + } + @Override public final boolean equals(Object o) { if (o == null) return false; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java index 3897374da..fac06da92 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java @@ -13,4 +13,8 @@ public class SectionPos extends Vec3i { public static SectionPos of(BlockPos pos) { return new SectionPos(pos.x() >> 4, pos.y() >> 4, pos.z() >> 4); } + + public static int sectionRelative(int rel) { + return rel & 15; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index fb00cc0dc..7684dea88 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -1,13 +1,18 @@ package net.momirealms.craftengine.core.world.chunk; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.momirealms.craftengine.core.block.BlockEntityState; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; +import net.momirealms.sparrow.nbt.ListTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; +import java.util.List; import java.util.Map; public class CEChunk { @@ -16,7 +21,7 @@ public class CEChunk { private final ChunkPos chunkPos; private final CESection[] sections; private final WorldHeight worldHeightAccessor; - private final Map blockEntities; + private final Map blockEntities; private boolean dirty; public CEChunk(CEWorld world, ChunkPos chunkPos) { @@ -24,14 +29,14 @@ public class CEChunk { this.chunkPos = chunkPos; this.worldHeightAccessor = world.worldHeight(); this.sections = new CESection[this.worldHeightAccessor.getSectionsCount()]; - this.blockEntities = new Int2ObjectOpenHashMap<>(16, 0.5f); + this.blockEntities = new Object2ObjectOpenHashMap<>(16, 0.5f); this.fillEmptySection(); } - public CEChunk(CEWorld world, ChunkPos chunkPos, CESection[] sections, Map blockEntities) { + public CEChunk(CEWorld world, ChunkPos chunkPos, CESection[] sections, ListTag blockEntitiesTag) { this.world = world; this.chunkPos = chunkPos; - this.blockEntities = blockEntities; + this.blockEntities = new Object2ObjectOpenHashMap<>(Math.max(blockEntitiesTag.size(), 16), 0.5f); this.worldHeightAccessor = world.worldHeight(); int sectionCount = this.worldHeightAccessor.getSectionsCount(); this.sections = new CESection[sectionCount]; @@ -44,10 +49,48 @@ public class CEChunk { } } this.fillEmptySection(); + List blockEntities = DefaultBlockEntitySerializer.deserialize(this, blockEntitiesTag); + for (BlockEntity blockEntity : blockEntities) { + this.setBlockEntity(blockEntity); + } } - public Map blockEntities() { - return this.blockEntities; + public void addBlockEntity(BlockEntity blockEntity) { + this.setBlockEntity(blockEntity); + } + + public void removeBlockEntity(BlockPos blockPos) { + + } + + public void setBlockEntity(BlockEntity blockEntity) { + BlockPos pos = blockEntity.pos(); + ImmutableBlockState blockState = this.getBlockState(pos); + if (!blockState.hasBlockEntity()) { + Debugger.BLOCK_ENTITY.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos); + return; + } + // 设置方块实体所在世界 + blockEntity.setWorld(this.world); + blockEntity.setValid(true); + BlockEntity previous = this.blockEntities.put(pos, blockEntity); + // 标记旧的方块实体无效 + if (previous != null && previous != blockEntity) { + previous.setValid(false); + } + } + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos) { + BlockEntity blockEntity = this.blockEntities.get(pos); + if (blockEntity == null) { + + } + return blockEntity; + } + + public Map blockEntities() { + return Collections.unmodifiableMap(this.blockEntities); } public boolean dirty() { @@ -93,17 +136,17 @@ public class CEChunk { } } - @Nullable + @NotNull public ImmutableBlockState getBlockState(BlockPos pos) { return getBlockState(pos.x(), pos.y(), pos.z()); } - @Nullable + @NotNull public ImmutableBlockState getBlockState(int x, int y, int z) { int index = sectionIndex(SectionPos.blockToSectionCoord(y)); CESection section = this.sections[index]; if (section == null) { - return null; + return EmptyBlock.STATE; } return section.getBlockState((y & 15) << 8 | (z & 15) << 4 | x & 15); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java new file mode 100644 index 000000000..744068aa6 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.world.chunk; + +public class ChunkStatus { + + public ChunkStatus() { + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index 8f8be31d9..f8b91a5f0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -1,48 +1,33 @@ package net.momirealms.craftengine.core.world.chunk.serialization; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.momirealms.craftengine.core.block.BlockEntityState; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.ListTag; -import org.jetbrains.annotations.ApiStatus; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public final class DefaultBlockEntitySerializer { - @ApiStatus.Experimental - public static ListTag serialize(Map tiles) { + public static ListTag serialize(Map tiles) { ListTag result = new ListTag(); - Map nbtToPosMap = new Object2ObjectOpenHashMap<>(Math.max(tiles.size(), 10), 0.75f); - for (Map.Entry entry : tiles.entrySet()) { - int pos = entry.getKey(); - CompoundTag tag = entry.getValue().nbt(); - int[] previous = nbtToPosMap.computeIfAbsent(tag, k -> new int[] {pos}); - int[] newPoses = new int[previous.length + 1]; - System.arraycopy(previous, 0, newPoses, 0, previous.length); - newPoses[previous.length] = pos; - nbtToPosMap.put(tag, newPoses); - } - for (Map.Entry entry : nbtToPosMap.entrySet()) { - CompoundTag blockEntityTag = new CompoundTag(); - blockEntityTag.put("data", entry.getKey()); - blockEntityTag.putIntArray("pos", entry.getValue()); - result.add(blockEntityTag); + for (Map.Entry entry : tiles.entrySet()) { + result.add(entry.getValue().saveAsTag()); } return result; } - @ApiStatus.Experimental - public static Map deserialize(ListTag tag) { - Map result = new Object2ObjectOpenHashMap<>(Math.max(tag.size(), 16), 0.5f); + public static List deserialize(CEChunk chunk, ListTag tag) { + List blockEntities = new ArrayList<>(tag.size()); for (int i = 0; i < tag.size(); i++) { - CompoundTag blockEntityTag = tag.getCompound(i); - CompoundTag data = blockEntityTag.getCompound("data"); - int[] pos = blockEntityTag.getIntArray("pos"); - for (int j = 0; j < pos.length; j++) { - result.put(j, new BlockEntityState(data)); - } + CompoundTag data = tag.getCompound(i); + BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); + ImmutableBlockState blockState = chunk.getBlockState(pos); } - return result; + return blockEntities; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index 1a44e0efc..fafceb99c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -47,6 +47,6 @@ public final class DefaultChunkSerializer { } } ListTag blockEntities = Optional.ofNullable(chunkNbt.getList("block_entities")).orElse(new ListTag()); - return new CEChunk(world, pos, sectionArray, DefaultBlockEntitySerializer.deserialize(blockEntities)); + return new CEChunk(world, pos, sectionArray, blockEntities); } } From 56df04084639045e8c071b101b1022177802eb47 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 3 Sep 2025 02:49:25 +0800 Subject: [PATCH 006/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0wg=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/compatibility/build.gradle.kts | 2 + .../BukkitCompatibilityManager.java | 14 +++ .../region/WorldGuardRegionCondition.java | 95 ++++++++++++++++++ .../condition/AlwaysFalseCondition.java | 23 +++++ ...ondition.java => AlwaysTrueCondition.java} | 6 +- .../context/condition/CommonConditions.java | 3 +- .../condition/ExpressionCondition.java | 2 +- .../craftengine/core/world/chunk/CEChunk.java | 23 ++++- .../DefaultBlockEntitySerializer.java | 5 +- gradle.properties | 2 +- libs/worldguard-bukkit-7.0.14-dist.jar | Bin 0 -> 1121921 bytes 11 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysFalseCondition.java rename core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/{EmptyCondition.java => AlwaysTrueCondition.java} (77%) create mode 100644 libs/worldguard-bukkit-7.0.14-dist.jar diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index f6795ac32..1ca637479 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -70,6 +70,8 @@ dependencies { compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21") // Zaphkiel compileOnly("ink.ptms:ZaphkielAPI:2.1.0") + // WorldGuard + compileOnly(files("${rootProject.rootDir}/libs/worldguard-bukkit-7.0.14-dist.jar")) } java { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index 303f3790b..78afe6f1b 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicItemDrop import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicSkillHelper; import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils; import net.momirealms.craftengine.bukkit.compatibility.permission.LuckPermsEventListeners; +import net.momirealms.craftengine.bukkit.compatibility.region.WorldGuardRegionCondition; import net.momirealms.craftengine.bukkit.compatibility.skript.SkriptHook; import net.momirealms.craftengine.bukkit.compatibility.slimeworld.SlimeFormatStorageAdaptor; import net.momirealms.craftengine.bukkit.compatibility.viaversion.ViaVersionUtils; @@ -20,9 +21,13 @@ import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.core.entity.furniture.ExternalModel; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.loot.LootConditions; import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager; import net.momirealms.craftengine.core.plugin.compatibility.LevelerProvider; import net.momirealms.craftengine.core.plugin.compatibility.ModelProvider; +import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition; +import net.momirealms.craftengine.core.plugin.context.condition.AlwaysTrueCondition; +import net.momirealms.craftengine.core.plugin.context.event.EventConditions; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldManager; @@ -117,6 +122,15 @@ public class BukkitCompatibilityManager implements CompatibilityManager { new MythicItemDropListener(this.plugin); logHook("MythicMobs"); } + Key worldGuardRegion = Key.of("worldguard:region"); + if (this.isPluginEnabled("WorldGuard")) { + EventConditions.register(worldGuardRegion, new WorldGuardRegionCondition.FactoryImpl<>()); + LootConditions.register(worldGuardRegion, new WorldGuardRegionCondition.FactoryImpl<>()); + logHook("WorldGuard"); + } else { + EventConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>()); + LootConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>()); + } } @Override diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java new file mode 100644 index 000000000..5594a4525 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java @@ -0,0 +1,95 @@ +package net.momirealms.craftengine.bukkit.compatibility.region; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.WorldPosition; +import org.bukkit.World; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +public class WorldGuardRegionCondition implements Condition { + private static final Key TYPE = Key.of("worldguard:region"); + private final MatchMode mode; + private final List regions; + + public WorldGuardRegionCondition(MatchMode mode, List regions) { + this.mode = mode; + this.regions = regions; + } + + @Override + public boolean test(CTX ctx) { + if (this.regions.isEmpty()) return false; + Optional optionalPos = ctx.getOptionalParameter(DirectContextParameters.POSITION); + if (optionalPos.isEmpty()) { + return false; + } + WorldPosition position = optionalPos.get(); + RegionManager regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt((World) position.world().platformWorld())); + if (regionManager != null) { + ApplicableRegionSet set = regionManager.getApplicableRegions(BlockVector3.at(position.x(), position.y(), position.z())); + List regionsAtThisPos = new ArrayList<>(set.size()); + for (ProtectedRegion region : set) { + String id = region.getId(); + regionsAtThisPos.add(id); + } + Predicate predicate = regionsAtThisPos::contains; + return this.mode.matcher.apply(predicate, this.regions); + } + return false; + } + + @Override + public Key type() { + return TYPE; + } + + public enum MatchMode { + ANY((p, regions) -> { + for (String region : regions) { + if (p.test(region)) { + return true; + } + } + return false; + }), + ALL((p, regions) -> { + for (String region : regions) { + if (!p.test(region)) { + return false; + } + } + return true; + }); + + private final BiFunction, List, Boolean> matcher; + + MatchMode(BiFunction, List, Boolean> matcher) { + this.matcher = matcher; + } + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + int mode = ResourceConfigUtils.getAsInt(arguments.getOrDefault("mode", 1), "mode") - 1; + MatchMode matchMode = MatchMode.values()[mode]; + List regions = MiscUtils.getAsStringList(arguments.get("regions")); + return new WorldGuardRegionCondition<>(matchMode, regions); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysFalseCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysFalseCondition.java new file mode 100644 index 000000000..127070193 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysFalseCondition.java @@ -0,0 +1,23 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.util.Key; + +import java.util.Map; + +public class AlwaysFalseCondition implements Condition { + + @Override + public Key type() { + return CommonConditions.ALWAYS_FALSE; + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + return new AlwaysFalseCondition<>(); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/EmptyCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysTrueCondition.java similarity index 77% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/EmptyCondition.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysTrueCondition.java index dda586d78..c36d53cb9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/EmptyCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AlwaysTrueCondition.java @@ -6,11 +6,11 @@ import net.momirealms.craftengine.core.util.Key; import java.util.Map; -public class EmptyCondition implements Condition { +public class AlwaysTrueCondition implements Condition { @Override public Key type() { - return CommonConditions.EMPTY; + return CommonConditions.ALWAYS_TRUE; } @Override @@ -22,7 +22,7 @@ public class EmptyCondition implements Condition { @Override public Condition create(Map arguments) { - return new EmptyCondition<>(); + return new AlwaysTrueCondition<>(); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java index d9a1eadae..156c17856 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java @@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.util.Key; public final class CommonConditions { private CommonConditions() {} - public static final Key EMPTY = Key.of("craftengine:empty"); + public static final Key ALWAYS_TRUE = Key.of("craftengine:always_true"); + public static final Key ALWAYS_FALSE = Key.of("craftengine:always_false"); public static final Key ALL_OF = Key.of("craftengine:all_of"); public static final Key ANY_OF = Key.of("craftengine:any_of"); public static final Key INVERTED = Key.of("craftengine:inverted"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/ExpressionCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/ExpressionCondition.java index 7ae158e1f..3dc4a67e2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/ExpressionCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/ExpressionCondition.java @@ -27,7 +27,7 @@ public class ExpressionCondition implements Condition @Override public boolean test(CTX ctx) { - String exp = expression.get(ctx); + String exp = this.expression.get(ctx); Expression expr = new Expression(exp); try { return expr.evaluate().getBooleanValue(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 7684dea88..37f6e5a6e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -60,7 +60,10 @@ public class CEChunk { } public void removeBlockEntity(BlockPos blockPos) { - + BlockEntity removedBlockEntity = this.blockEntities.remove(blockPos); + if (removedBlockEntity != null) { + removedBlockEntity.setValid(false); + } } public void setBlockEntity(BlockEntity blockEntity) { @@ -84,11 +87,27 @@ public class CEChunk { public BlockEntity getBlockEntity(BlockPos pos) { BlockEntity blockEntity = this.blockEntities.get(pos); if (blockEntity == null) { - + blockEntity = createBlockEntity(pos); + if (blockEntity != null) { + this.addBlockEntity(blockEntity); + } + } else { + if (!blockEntity.isValid()) { + this.blockEntities.remove(pos); + return null; + } } return blockEntity; } + private BlockEntity createBlockEntity(BlockPos pos) { + ImmutableBlockState blockState = this.getBlockState(pos); + if (!blockState.hasBlockEntity()) { + return null; + } + return blockState.blockEntityType().factory().create(pos, blockState); + } + public Map blockEntities() { return Collections.unmodifiableMap(this.blockEntities); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index f8b91a5f0..246a77363 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -16,7 +16,10 @@ public final class DefaultBlockEntitySerializer { public static ListTag serialize(Map tiles) { ListTag result = new ListTag(); for (Map.Entry entry : tiles.entrySet()) { - result.add(entry.getValue().saveAsTag()); + BlockEntity entity = entry.getValue(); + if (entity.isValid()) { + result.add(entity.saveAsTag()); + } } return result; } diff --git a/gradle.properties b/gradle.properties index 2bc460d2f..5c7dbfa1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.6 +project_version=0.0.62.8 config_version=45 lang_version=25 project_group=net.momirealms diff --git a/libs/worldguard-bukkit-7.0.14-dist.jar b/libs/worldguard-bukkit-7.0.14-dist.jar new file mode 100644 index 0000000000000000000000000000000000000000..69d590e5e84439b9316fb76c582a3db59e1808bd GIT binary patch literal 1121921 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%0D6K`Wj8Tx1ukP9ljI_pDpA_v{8GMr(fozw&&g{m7 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 z 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

NTe=Edzh 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

4qV-cSr}2cqv$JVD0f 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

eox*HD;bUSZ6 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 z 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 $SXH!WX0&9KeqIe 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

S#qdOdi8?uvfTc+5cwpiIKi`eF4Ob28 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

xX;CE!=Dsy|8;`NKdu)8vAbI{ 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%0A;^30kb*26xxU@YC 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]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

64#*!k_E5(L9=;@PeLt01>t$xNQ8P$6d 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�q?9R~NC@@*1J`qa* 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℘ar-wKuYl0}4Oo(Vv&rY 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 b<$>f7Pi`JU5u@O{f-l_*@P=N8rStv=)d+HP literal 0 HcmV?d00001 From b53eef6e08c0a9cb3ce80520a8b0d60a1276f1c3 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 3 Sep 2025 03:46:40 +0800 Subject: [PATCH 007/226] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B6=B2=E4=BD=93?= =?UTF-8?q?=E7=A2=B0=E6=92=9E=E6=94=BE=E7=BD=AE=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/LiquidCollisionBlockItemBehavior.java | 12 +++++++++--- .../craftengine/bukkit/util/DirectionUtils.java | 9 +++------ .../craftengine/core/pack/AbstractPackManager.java | 14 ++++++++++++++ gradle.properties | 4 ++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java index 896c742af..051838931 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.item.behavior; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionResult; @@ -45,11 +47,15 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior { try { if (player == null) return InteractionResult.FAIL; Object blockHitResult = CoreReflections.method$Item$getPlayerPOVHitResult.invoke(null, world.serverWorld(), player.serverPlayer(), CoreReflections.instance$ClipContext$Fluid$SOURCE_ONLY); - Object blockPos = CoreReflections.field$BlockHitResul$blockPos.get(blockHitResult); + Object blockPos = FastNMS.INSTANCE.field$BlockHitResul$blockPos(blockHitResult); BlockPos above = new BlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) + offsetY, FastNMS.INSTANCE.field$Vec3i$z(blockPos)); - Direction direction = Direction.values()[(int) CoreReflections.method$Direction$ordinal.invoke(CoreReflections.field$BlockHitResul$direction.get(blockHitResult))]; - boolean miss = CoreReflections.field$BlockHitResul$miss.getBoolean(blockHitResult); + Direction direction = DirectionUtils.fromNMSDirection(FastNMS.INSTANCE.field$BlockHitResul$direction(blockHitResult)); + boolean miss = FastNMS.INSTANCE.field$BlockHitResul$miss(blockHitResult); Vec3d hitPos = LocationUtils.fromVec(CoreReflections.field$HitResult$location.get(blockHitResult)); + Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.method$BlockGetter$getFluidState(world.serverWorld(), blockPos)); + if (fluidType != MFluids.WATER && fluidType != MFluids.LAVA) { + return InteractionResult.PASS; + } if (miss) { return super.useOnBlock(new UseOnContext(player, hand, BlockHitResult.miss(hitPos, direction, above))); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java index cf0ef5d0c..41ce992b8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java @@ -43,12 +43,9 @@ public final class DirectionUtils { } public static Direction fromNMSDirection(Object direction) { - try { - int index = (int) CoreReflections.method$Direction$ordinal.invoke(direction); - return Direction.values()[index]; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + Enum directionEnum = (Enum) direction; + int index = directionEnum.ordinal(); + return Direction.values()[index]; } public static boolean isYAxis(Object nmsDirection) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 99313220e..f68ae92f7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -42,6 +42,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.scanner.ScannerException; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -543,6 +544,19 @@ public abstract class AbstractPackManager implements PackManager { } catch (IOException e) { AbstractPackManager.this.plugin.logger().severe("Error while reading config file: " + path, e); return FileVisitResult.CONTINUE; + } catch (ScannerException e) { + if (e.getMessage() != null && e.getMessage().contains("TAB") && e.getMessage().contains("indentation")) { + try { + String content = Files.readString(path); + content = content.replace("\t", " "); + Files.writeString(path, content); + } catch (Exception ex) { + AbstractPackManager.this.plugin.logger().severe("Failed to fix tab indentation in config file: " + path, ex); + } + } else { + AbstractPackManager.this.plugin.logger().severe("Error found while reading config file: " + path, e); + } + return FileVisitResult.CONTINUE; } catch (LocalizedException e) { e.setArgument(0, path.toString()); TranslationManager.instance().log(e.node(), e.arguments()); diff --git a/gradle.properties b/gradle.properties index 5c7dbfa1c..a17071ff4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,8 +49,8 @@ mojang_brigadier_version=1.0.18 byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 -anti_grief_version=0.19 -nms_helper_version=1.0.67 +anti_grief_version=0.20 +nms_helper_version=1.0.69 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From 0d9177505e12af31a0e57eb2b7da096df8fd37df Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 3 Sep 2025 04:53:47 +0800 Subject: [PATCH 008/226] =?UTF-8?q?=E5=BF=BD=E7=95=A5=E5=B7=B2=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E7=9A=84=E6=8E=89=E8=90=BD=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/BlockEventListener.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 24da42748..07115036f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -15,7 +15,9 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.loot.LootContext; import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; @@ -174,6 +176,9 @@ public final class BlockEventListener implements Listener { // override vanilla block loots if (player.getGameMode() != GameMode.CREATIVE) { this.plugin.vanillaLootManager().getBlockLoot(stateId).ifPresent(it -> { + if (!event.isDropItems()) { + return; + } if (it.override()) { event.setDropItems(false); event.setExpToDrop(0); From 0216ee0f5eb52c46b2145e63fdfd02e104780b5e Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 3 Sep 2025 04:55:13 +0800 Subject: [PATCH 009/226] =?UTF-8?q?feat(network):=20=E5=85=BC=E5=AE=B9=201?= =?UTF-8?q?.20.1=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=A8=A1=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/pack/BukkitPackManager.java | 1 + .../plugin/network/BukkitNetworkManager.java | 3 +- .../plugin/network/PacketConsumers.java | 16 ++++++++-- .../bukkit/plugin/network/PacketIds.java | 2 ++ .../plugin/network/id/PacketIds1_20.java | 5 +++ .../plugin/network/id/PacketIds1_20_5.java | 5 +++ .../protocol/ClientCustomBlockPacket.java | 32 ++++++++++++++++--- .../reflection/minecraft/CoreReflections.java | 5 +++ .../reflection/paper/PaperReflections.java | 20 ++++++++++++ 9 files changed, 82 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 0ce69f22c..5b2e36c6b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -45,6 +45,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { public void onPlayerJoin(PlayerJoinEvent event) { if (Config.sendPackOnJoin() && !VersionHelper.isOrAbove1_20_2()) { Player player = BukkitAdaptors.adapt(event.getPlayer()); + if (player == null) return; this.sendResourcePack(player); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 9df95e2ff..f97d36b3e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -204,7 +204,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes registerNMSPacketConsumer(PacketConsumers.RENAME_ITEM, NetworkReflections.clazz$ServerboundRenameItemPacket); registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, NetworkReflections.clazz$ServerboundSignUpdatePacket); registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, NetworkReflections.clazz$ServerboundEditBookPacket); - registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD, NetworkReflections.clazz$ServerboundCustomPayloadPacket); + registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD_1_20_2, VersionHelper.isOrAbove1_20_2() ? NetworkReflections.clazz$ServerboundCustomPayloadPacket : null); registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_RESPONSE, NetworkReflections.clazz$ServerboundResourcePackPacket); registerNMSPacketConsumer(PacketConsumers.ENTITY_EVENT, NetworkReflections.clazz$ClientboundEntityEventPacket); registerNMSPacketConsumer(PacketConsumers.MOVE_POS_AND_ROTATE_ENTITY, NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot); @@ -247,6 +247,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes registerC2SByteBufPacketConsumer(PacketConsumers.SET_CREATIVE_MODE_SLOT, this.packetIds.serverboundSetCreativeModeSlotPacket()); registerC2SByteBufPacketConsumer(PacketConsumers.CONTAINER_CLICK_1_20, VersionHelper.isOrAbove1_21_5() ? -1 : this.packetIds.serverboundContainerClickPacket()); registerC2SByteBufPacketConsumer(PacketConsumers.INTERACT_ENTITY, this.packetIds.serverboundInteractPacket()); + registerC2SByteBufPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD_1_20, VersionHelper.isOrAbove1_20_2() ? -1 : this.packetIds.serverboundCustomPayloadPacket()); } @EventHandler(priority = EventPriority.LOWEST) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 903712053..740564e0a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1923,12 +1923,12 @@ public class PacketConsumers { return hasIllegal ? Pair.of(true, new String(newCodepoints, 0, newCodepoints.length)) : Pair.of(false, original); } - public static final TriConsumer CUSTOM_PAYLOAD = (user, event, packet) -> { + public static final TriConsumer CUSTOM_PAYLOAD_1_20_2 = (user, event, packet) -> { try { if (!VersionHelper.isOrAbove1_20_2()) return; Object payload = NetworkReflections.methodHandle$ServerboundCustomPayloadPacket$payloadGetter.invokeExact(packet); Payload clientPayload; - if (NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) { + if (VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) { clientPayload = DiscardedPayload.from(payload); } else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$ServerboundCustomPayloadPacket$UnknownPayload.isInstance(payload)) { clientPayload = UnknownPayload.from(payload); @@ -1942,6 +1942,18 @@ public class PacketConsumers { } }; + public static final BiConsumer CUSTOM_PAYLOAD_1_20 = (user, event) -> { + try { + if (VersionHelper.isOrAbove1_20_2()) return; + FriendlyByteBuf byteBuf = event.getBuffer(); + Key key = byteBuf.readKey(); + if (!key.equals(NetworkManager.MOD_CHANNEL_KEY)) return; + PayloadHelper.handleReceiver(new UnknownPayload(key, byteBuf.readBytes(byteBuf.readableBytes())), user); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e); + } + }; + @SuppressWarnings("unchecked") public static final BiConsumer SET_ENTITY_DATA = (user, event) -> { try { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java index dd41cb280..b8b0e9cc8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java @@ -69,4 +69,6 @@ public interface PacketIds { int clientboundUpdateRecipesPacket(); int clientboundForgetLevelChunkPacket(); + + int serverboundCustomPayloadPacket(); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java index 7a3b81947..99eb1c449 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java @@ -174,4 +174,9 @@ public class PacketIds1_20 implements PacketIds { public int clientboundForgetLevelChunkPacket() { return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundForgetLevelChunkPacket); } + + @Override + public int serverboundCustomPayloadPacket() { + return PacketIdFinder.serverboundByClazz(NetworkReflections.clazz$ServerboundCustomPayloadPacket); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java index b3a9b624a..70596861c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java @@ -173,4 +173,9 @@ public class PacketIds1_20_5 implements PacketIds { public int clientboundForgetLevelChunkPacket() { return PacketIdFinder.clientboundByName("minecraft:forget_level_chunk"); } + + @Override + public int serverboundCustomPayloadPacket() { + return PacketIdFinder.serverboundByName("custom_payload"); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java index 911438c58..257399aae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientCustomBlockPacket.java @@ -1,17 +1,21 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslationArgument; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.paper.PaperReflections; import net.momirealms.craftengine.bukkit.util.RegistryUtils; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.network.ModPacket; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec; import net.momirealms.craftengine.core.registry.BuiltInRegistries; -import net.momirealms.craftengine.core.util.FriendlyByteBuf; -import net.momirealms.craftengine.core.util.IntIdentityList; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.ResourceKey; +import net.momirealms.craftengine.core.util.*; +import org.bukkit.entity.Player; public record ClientCustomBlockPacket(int size) implements ModPacket { public static final ResourceKey> TYPE = ResourceKey.create( @@ -37,6 +41,7 @@ public record ClientCustomBlockPacket(int size) implements ModPacket { @Override public void handle(NetWorkUser user) { + if (user.clientModEnabled()) return; // 防止滥用 int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize(); if (this.size != serverBlockRegistrySize) { user.kick(Component.translatable( @@ -48,6 +53,25 @@ public record ClientCustomBlockPacket(int size) implements ModPacket { } user.setClientModState(true); user.setClientBlockList(new IntIdentityList(this.size)); + if (!VersionHelper.isOrAbove1_20_2()) { + // 因为旧版本没有配置阶段需要重新发送区块 + try { + Object chunkLoader = PaperReflections.field$ServerPlayer$chunkLoader.get(user.serverPlayer()); + LongOpenHashSet sentChunks = (LongOpenHashSet) PaperReflections.field$RegionizedPlayerChunkLoader$PlayerChunkLoaderData$sentChunks.get(chunkLoader); + Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(((Player) user.platformPlayer()).getWorld()); + Object lightEngine = CoreReflections.method$BlockAndTintGetter$getLightEngine.invoke(serverLevel); + Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); + for (long chunkPos : sentChunks) { + int chunkX = (int) chunkPos; + int chunkZ = (int) (chunkPos >> 32); + Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunk(chunkSource, chunkX, chunkZ, false); + Object packet = NetworkReflections.constructor$ClientboundLevelChunkWithLightPacket.newInstance(levelChunk, lightEngine, null, null); + user.sendPacket(packet, true); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to refresh chunk for player " + user.name(), e); + } + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 55ae08a45..04d613691 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -8,6 +8,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.momirealms.craftengine.bukkit.plugin.reflection.ReflectionInitException; +import net.momirealms.craftengine.bukkit.plugin.reflection.paper.PaperReflections; import net.momirealms.craftengine.bukkit.util.BukkitReflectionUtils; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ReflectionUtils; @@ -4177,4 +4178,8 @@ public final class CoreReflections { "world.level.storage.loot.entries.LootPoolEntryType" ) ); + + public static final Method method$BlockAndTintGetter$getLightEngine = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockAndTintGetter, clazz$LevelLightEngine) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/paper/PaperReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/paper/PaperReflections.java index 1210342a2..d6d4095bd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/paper/PaperReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/paper/PaperReflections.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.plugin.reflection.paper; import com.google.gson.Gson; import io.papermc.paper.event.player.AsyncChatDecorateEvent; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.core.util.ReflectionUtils; @@ -97,4 +98,23 @@ public final class PaperReflections { public static final Method method$BookMeta$page = requireNonNull( ReflectionUtils.getMethod(CraftBukkitReflections.clazz$BookMeta, void.class, int.class, clazz$AdventureComponent) ); + + public static final Class clazz$RegionizedPlayerChunkLoader$PlayerChunkLoaderData = requireNonNull( + ReflectionUtils.getClazz( + "ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader$PlayerChunkLoaderData", + "io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader$PlayerChunkLoaderData" + ) + ); + + public static final Field field$ServerPlayer$chunkLoader = requireNonNull( + ReflectionUtils.getDeclaredField( + CoreReflections.clazz$ServerPlayer, PaperReflections.clazz$RegionizedPlayerChunkLoader$PlayerChunkLoaderData, 0 + ) + ); + + public static final Field field$RegionizedPlayerChunkLoader$PlayerChunkLoaderData$sentChunks = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$RegionizedPlayerChunkLoader$PlayerChunkLoaderData, LongOpenHashSet.class, 0 + ) + ); } From b04ae5caf04a3af2380a0edc8d287f7b5ef83674 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 3 Sep 2025 05:16:14 +0800 Subject: [PATCH 010/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=BA=E5=B0=91?= =?UTF-8?q?=E7=8E=A9=E5=AE=B6=E5=8F=82=E6=95=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/loot/BukkitVanillaLootManager.java | 1 - .../bukkit/plugin/injector/BlockStateGenerator.java | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java index 9dc95e83b..68205240c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java @@ -55,7 +55,6 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme HandlerList.unregisterAll(this); } - @SuppressWarnings("UnstableApiUsage") @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onEntityDeath(EntityDeathEvent event) { Entity entity = event.getEntity(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java index 131b1dc1e..825f90a50 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java @@ -121,8 +121,10 @@ public final class BlockStateGenerator { if (!item.isEmpty()) { lootBuilder.withParameter(DirectContextParameters.ITEM_IN_HAND, item); } - BukkitServerPlayer player = optionalPlayer != null ? BukkitCraftEngine.instance().adapt(FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(optionalPlayer)) : null; + if (player != null) { + lootBuilder.withParameter(DirectContextParameters.PLAYER, player); + } Float radius = (Float) FastNMS.INSTANCE.method$LootParams$Builder$getOptionalParameter(builder, MLootContextParams.EXPLOSION_RADIUS); if (radius != null) { lootBuilder.withParameter(DirectContextParameters.EXPLOSION_RADIUS, radius); From ef2c26f15f0be8f8d34005801a9dbbfb349a1536 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 3 Sep 2025 20:33:30 +0800 Subject: [PATCH 011/226] =?UTF-8?q?=E9=87=8D=E7=BD=AEtick=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/world/BukkitCEWorld.java | 3 +- .../bukkit/world/BukkitWorldManager.java | 26 ++++---------- common-files/src/main/resources/config.yml | 2 ++ .../core/font/AbstractFontManager.java | 17 +++++---- .../core/plugin/config/Config.java | 6 ++++ .../core/util/AdventureHelper.java | 4 ++- .../craftengine/core/world/CEWorld.java | 35 ++++++++++++++++++- gradle.properties | 2 +- 8 files changed, 64 insertions(+), 31 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 8d13cf1b1..136eadb74 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -22,8 +22,7 @@ public class BukkitCEWorld extends CEWorld { } @Override - public void tick() { - super.tick(); + public void updateLight() { HashSet poses; synchronized (super.updatedSectionSet) { poses = new HashSet<>(super.updatedSectionSet); 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 134e2ee7f..c2431e7d9 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 @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.world; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; @@ -33,7 +34,6 @@ import java.io.IOException; import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; public class BukkitWorldManager implements WorldManager, Listener { @@ -42,12 +42,10 @@ public class BukkitWorldManager implements WorldManager, Listener { private final Map worlds; private CEWorld[] worldArray = new CEWorld[0]; private final ReentrantReadWriteLock worldMapLock = new ReentrantReadWriteLock(); - private SchedulerTask tickTask; // cache private UUID lastVisitedUUID; private CEWorld lastVisitedWorld; private StorageAdaptor storageAdaptor; - private boolean isTicking = false; private boolean initialized = false; public BukkitWorldManager(BukkitCraftEngine plugin) { @@ -101,20 +99,6 @@ public class BukkitWorldManager implements WorldManager, Listener { } public void delayedInit() { - // events and tasks - this.tickTask = this.plugin.scheduler().asyncRepeating(() -> { - try { - if (this.isTicking) { - return; - } - this.isTicking = true; - for (CEWorld world : this.worldArray) { - world.tick(); - } - } finally { - this.isTicking = false; - } - }, 50, 50, TimeUnit.MILLISECONDS); // load loaded chunks this.worldMapLock.writeLock().lock(); try { @@ -124,6 +108,7 @@ public class BukkitWorldManager implements WorldManager, Listener { for (Chunk chunk : world.getLoadedChunks()) { handleChunkLoad(ceWorld, chunk); } + ceWorld.setTicking(true); } catch (Exception e) { CraftEngine.instance().logger().warn("Error loading world: " + world.getName(), e); } @@ -142,11 +127,9 @@ public class BukkitWorldManager implements WorldManager, Listener { if (this.storageAdaptor instanceof Listener listener) { HandlerList.unregisterAll(listener); } - if (this.tickTask != null && !this.tickTask.cancelled()) { - this.tickTask.cancel(); - } for (World world : Bukkit.getWorlds()) { CEWorld ceWorld = getWorld(world.getUID()); + ceWorld.setTicking(false); for (Chunk chunk : world.getLoadedChunks()) { handleChunkUnload(ceWorld, chunk); } @@ -175,6 +158,7 @@ public class BukkitWorldManager implements WorldManager, Listener { for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { handleChunkLoad(ceWorld, chunk); } + ceWorld.setTicking(true); } finally { this.worldMapLock.writeLock().unlock(); } @@ -190,6 +174,7 @@ public class BukkitWorldManager implements WorldManager, Listener { for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) { handleChunkLoad(world, chunk); } + world.setTicking(true); } finally { this.worldMapLock.writeLock().unlock(); } @@ -229,6 +214,7 @@ public class BukkitWorldManager implements WorldManager, Listener { } finally { this.worldMapLock.writeLock().unlock(); } + ceWorld.setTicking(false); for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { handleChunkUnload(ceWorld, chunk); } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index b29463033..5c39b74bf 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -361,6 +361,8 @@ gui: light-system: # Required for custom light-emitting blocks enable: true + # Async light update + async-update: true chunk-system: # With cache system, those frequently load/unload chunks would consume fewer resources on serialization diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index adfbebdef..cc5c6cdfb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -40,7 +40,7 @@ public abstract class AbstractFontManager implements FontManager { private final EmojiParser emojiParser; private OffsetFont offsetFont; - protected Trie imageTagTrie; + protected Trie networkTagTrie; protected Trie emojiKeywordTrie; protected Map networkTagMapper; protected Map emojiMapper; @@ -67,9 +67,14 @@ public abstract class AbstractFontManager implements FontManager { this.images.clear(); this.illegalChars.clear(); this.emojis.clear(); + this.networkTagTrie = null; + this.emojiKeywordTrie = null; if (this.networkTagMapper != null) { this.networkTagMapper.clear(); } + if (this.emojiMapper != null) { + this.emojiMapper.clear(); + } } @Override @@ -88,7 +93,7 @@ public abstract class AbstractFontManager implements FontManager { this.registerImageTags(); this.registerShiftTags(); this.registerGlobalTags(); - this.buildImageTagTrie(); + this.buildNetworkTagTrie(); this.buildEmojiKeywordsTrie(); this.emojiList = new ArrayList<>(this.emojis.values()); this.allEmojiSuggestions = this.emojis.values().stream() @@ -131,11 +136,11 @@ public abstract class AbstractFontManager implements FontManager { @Override public Map matchTags(String json) { - if (this.imageTagTrie == null) { + if (this.networkTagTrie == null) { return Collections.emptyMap(); } Map tags = new HashMap<>(); - for (Token token : this.imageTagTrie.tokenize(json)) { + for (Token token : this.networkTagTrie.tokenize(json)) { if (token.isMatch()) { tags.put(token.getFragment(), this.networkTagMapper.get(token.getFragment())); } @@ -305,8 +310,8 @@ public abstract class AbstractFontManager implements FontManager { .build(); } - private void buildImageTagTrie() { - this.imageTagTrie = Trie.builder() + private void buildNetworkTagTrie() { + this.networkTagTrie = Trie.builder() .ignoreOverlaps() .addKeywords(this.networkTagMapper.keySet()) .build(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 2a207bb57..f87c745e8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -97,6 +97,7 @@ public class Config { protected Component resource_pack$send$prompt; protected boolean light_system$force_update_light; + protected boolean light_system$async_update; protected boolean light_system$enable; protected int chunk_system$compression_method; @@ -314,6 +315,7 @@ public class Config { // light light_system$force_update_light = config.getBoolean("light-system.force-update-light", false); + light_system$async_update = config.getBoolean("light-system.async-update", true); light_system$enable = config.getBoolean("light-system.enable", true); // chunk @@ -885,6 +887,10 @@ public class Config { return instance.block$chunk_relighter; } + public static boolean asyncLightUpdate() { + return instance.light_system$async_update; + } + public void setObf(boolean enable) { this.resource_pack$protection$obfuscation$enable = enable; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index d24d8d4ca..a8ad7ed18 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -365,6 +365,8 @@ public class AdventureHelper { return text.replaceText(builder -> builder.match(Pattern.compile(patternString)) .replacement((result, b) -> - replacements.get(result.group()).apply(context))); + Optional.ofNullable(replacements.get(result.group())).orElseThrow(() -> new IllegalStateException("Could not find tag '" + result.group() + "'")).apply(context) + ) + ); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 2807754bd..5feac8921 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -6,6 +6,8 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; @@ -25,6 +27,8 @@ public abstract class CEWorld { protected final List tickingBlockEntities = new ArrayList<>(); protected final List pendingTickingBlockEntities = new ArrayList<>(); protected boolean isTickingBlockEntities = false; + protected SchedulerTask syncTickTask; + protected SchedulerTask asyncTickTask; private CEChunk lastChunk; private long lastChunkPos; @@ -45,6 +49,24 @@ public abstract class CEWorld { this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; } + public void setTicking(boolean ticking) { + if (ticking) { + if (this.syncTickTask == null || this.syncTickTask.cancelled()) { + this.syncTickTask = CraftEngine.instance().scheduler().sync().runRepeating(this::syncTick, 1, 1); + } + if (this.asyncTickTask == null || this.asyncTickTask.cancelled()) { + this.asyncTickTask = CraftEngine.instance().scheduler().sync().runAsyncRepeating(this::asyncTick, 1, 1); + } + } else { + if (this.syncTickTask != null && !this.syncTickTask.cancelled()) { + this.syncTickTask.cancel(); + } + if (this.asyncTickTask != null && !this.asyncTickTask.cancelled()) { + this.asyncTickTask.cancel(); + } + } + } + public String name() { return this.world.name(); } @@ -163,10 +185,21 @@ public abstract class CEWorld { return this.worldHeightAccessor; } - public void tick() { + public void syncTick() { this.tickBlockEntities(); + if (!Config.asyncLightUpdate()) { + this.updateLight(); + } } + public void asyncTick() { + if (Config.asyncLightUpdate()) { + this.updateLight(); + } + } + + public abstract void updateLight(); + protected void tickBlockEntities() { this.isTickingBlockEntities = true; if (!this.pendingTickingBlockEntities.isEmpty()) { diff --git a/gradle.properties b/gradle.properties index a17071ff4..eca3dd45d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.8 +project_version=0.0.62.9 config_version=45 lang_version=25 project_group=net.momirealms From 65baf247ed99827efb792f1706421b2f05954293 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 00:26:19 +0800 Subject: [PATCH 012/226] =?UTF-8?q?=E6=98=8E=E7=A1=AE=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E6=9D=83=E9=99=90=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/build.gradle.kts | 4 ++ .../BukkitCompatibilityManager.java | 1 - .../region/WorldGuardRegionCondition.java | 5 +- bukkit/loader/build.gradle.kts | 4 ++ bukkit/paper-loader/build.gradle.kts | 4 ++ .../plugin/PaperCraftEngineBootstrap.java | 19 ++++-- .../bukkit/block/BlockEventListener.java | 2 - .../bukkit/plugin/BukkitCraftEngine.java | 8 ++- .../bukkit/plugin/agent/BlocksAgent.java | 2 +- .../classpath/BukkitClassPathAppender.java | 37 ++++++++++++ .../classpath/PaperClassPathAppender.java | 57 ------------------ .../PaperPluginClassPathAppender.java | 44 ++++++++++++++ .../reflection/minecraft/CoreReflections.java | 1 - .../bukkit/world/BukkitWorldManager.java | 2 - core/build.gradle.kts | 4 ++ .../craftengine/core/plugin/CraftEngine.java | 12 +++- .../craftengine/core/plugin/Plugin.java | 4 +- .../core/plugin/dependency/Dependencies.java | 58 +++++++++---------- .../core/plugin/dependency/Dependency.java | 15 +++++ .../dependency/DependencyManagerImpl.java | 16 +++-- .../dependency/relocation/Relocation.java | 2 +- gradle.properties | 4 +- 22 files changed, 190 insertions(+), 115 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperClassPathAppender.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperPluginClassPathAppender.java diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index 7519028f8..ec25aa893 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -101,6 +101,10 @@ tasks { relocate("com.google.common.jimfs", "net.momirealms.craftengine.libraries.jimfs") relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons") relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref") + relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http") + relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") + relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") + relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") } } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index 78afe6f1b..3f6d91917 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -26,7 +26,6 @@ import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager import net.momirealms.craftengine.core.plugin.compatibility.LevelerProvider; import net.momirealms.craftengine.core.plugin.compatibility.ModelProvider; import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition; -import net.momirealms.craftengine.core.plugin.context.condition.AlwaysTrueCondition; import net.momirealms.craftengine.core.plugin.context.event.EventConditions; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java index 5594a4525..86eeac55a 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/region/WorldGuardRegionCondition.java @@ -16,7 +16,10 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.WorldPosition; import org.bukkit.World; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Predicate; diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 53736c927..c46bb9ff0 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -81,5 +81,9 @@ tasks { relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons") relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref") relocate("ca.spottedleaf.concurrentutil", "net.momirealms.craftengine.libraries.concurrentutil") + relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http") + relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") + relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") + relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") } } diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index 8929b281f..94abf7189 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -154,5 +154,9 @@ tasks { relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons") relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref") relocate("ca.spottedleaf.concurrentutil", "net.momirealms.craftengine.libraries.concurrentutil") + relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http") + relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") + relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") + relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") } } diff --git a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java index aadff5866..553b4ce2b 100644 --- a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java +++ b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java @@ -5,11 +5,11 @@ import io.papermc.paper.plugin.bootstrap.PluginBootstrap; import io.papermc.paper.plugin.bootstrap.PluginProviderContext; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.momirealms.craftengine.bukkit.plugin.agent.RuntimePatcher; -import net.momirealms.craftengine.bukkit.plugin.classpath.PaperClassPathAppender; +import net.momirealms.craftengine.bukkit.plugin.classpath.BukkitClassPathAppender; +import net.momirealms.craftengine.bukkit.plugin.classpath.PaperPluginClassPathAppender; import net.momirealms.craftengine.core.plugin.logger.PluginLogger; import net.momirealms.craftengine.core.plugin.logger.Slf4jPluginLogger; import net.momirealms.craftengine.core.util.ReflectionUtils; -import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -42,11 +42,12 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap { this.plugin = new BukkitCraftEngine( logger, context.getDataDirectory(), - new PaperClassPathAppender(this.getClass().getClassLoader()) + new BukkitClassPathAppender(), + new PaperPluginClassPathAppender(this.getClass().getClassLoader()) ); this.plugin.applyDependencies(); this.plugin.setUpConfig(); - if (VersionHelper.isOrAbove1_21_4()) { + if (isDatapackDiscoveryAvailable()) { new ModernEventHandler(context, this.plugin).register(); } else { try { @@ -58,6 +59,16 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap { } } + private static boolean isDatapackDiscoveryAvailable() { + try { + Class eventsClass = Class.forName("io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents"); + eventsClass.getField("DATAPACK_DISCOVERY"); + return true; + } catch (ClassNotFoundException | NoSuchFieldException e) { + return false; + } + } + @Override public @NotNull JavaPlugin createPlugin(@NotNull PluginProviderContext context) { return new PaperCraftEnginePlugin(this); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 07115036f..1daab5e9d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -15,9 +15,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.loot.LootContext; import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; 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 00b940138..b191553b8 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 @@ -72,18 +72,20 @@ public class BukkitCraftEngine extends CraftEngine { private final Path dataFolderPath; protected BukkitCraftEngine(JavaPlugin plugin) { - this(new JavaPluginLogger(plugin.getLogger()), plugin.getDataFolder().toPath().toAbsolutePath(), new ReflectionClassPathAppender(plugin.getClass().getClassLoader())); + this(new JavaPluginLogger(plugin.getLogger()), plugin.getDataFolder().toPath().toAbsolutePath(), + new ReflectionClassPathAppender(plugin.getClass().getClassLoader()), new ReflectionClassPathAppender(plugin.getClass().getClassLoader())); this.setJavaPlugin(plugin); } - protected BukkitCraftEngine(PluginLogger logger, Path dataFolderPath, ClassPathAppender classPathAppender) { + protected BukkitCraftEngine(PluginLogger logger, Path dataFolderPath, ClassPathAppender sharedClassPathAppender, ClassPathAppender privateClassPathAppender) { super((p) -> { CraftEngineReloadEvent event = new CraftEngineReloadEvent((BukkitCraftEngine) p); EventUtils.fireAndForget(event); }); instance = this; this.dataFolderPath = dataFolderPath; - super.classPathAppender = classPathAppender; + super.sharedClassPathAppender = sharedClassPathAppender; + super.privateClassPathAppender = privateClassPathAppender; super.logger = logger; super.platform = new BukkitPlatform(); super.scheduler = new BukkitSchedulerAdapter(this); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java index cac4429ba..9b8f17333 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java @@ -34,7 +34,7 @@ public final class BlocksAgent { Method injectRegistries = plugin.getClass().getMethod("injectRegistries"); injectRegistries.invoke(plugin); } catch (Exception e) { - throw new RuntimeException("Failed to inject registries", e); + e.printStackTrace(); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java new file mode 100644 index 000000000..d70d4b615 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.bukkit.plugin.classpath; + +import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; +import net.momirealms.craftengine.core.plugin.classpath.URLClassLoaderAccess; +import org.bukkit.Bukkit; + +import java.net.MalformedURLException; +import java.net.URLClassLoader; +import java.nio.file.Path; + +public class BukkitClassPathAppender implements ClassPathAppender { + private final URLClassLoaderAccess libraryClassLoaderAccess; + + public BukkitClassPathAppender() { + // 这个类加载器用于加载重定位后的依赖库,这样所有插件都能访问到 + ClassLoader bukkitClassLoader = Bukkit.class.getClassLoader(); + if (bukkitClassLoader instanceof URLClassLoader urlClassLoader) { + this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader); + } else { + // ignite会把Bukkit放置于EmberClassLoader中,获取其父DynamicClassLoader + if (bukkitClassLoader.getClass().getName().equals("space.vectrix.ignite.launch.ember.EmberClassLoader") && bukkitClassLoader.getParent() instanceof URLClassLoader urlClassLoader) { + this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader); + } else { + this.libraryClassLoaderAccess = null; + } + } + } + + @Override + public void addJarToClasspath(Path file) { + try { + this.libraryClassLoaderAccess.addURL(file.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperClassPathAppender.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperClassPathAppender.java deleted file mode 100644 index 885b223cd..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperClassPathAppender.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.classpath; - -import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; -import net.momirealms.craftengine.core.plugin.classpath.URLClassLoaderAccess; -import net.momirealms.craftengine.core.util.ReflectionUtils; -import org.bukkit.Bukkit; - -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.Optional; - -public class PaperClassPathAppender implements ClassPathAppender { - public static final Class clazz$PaperPluginClassLoader = ReflectionUtils.getClazz( - "io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader" - ); - public static final Field field$PaperPluginClassLoader$libraryLoader = Optional.ofNullable(clazz$PaperPluginClassLoader) - .map(it -> ReflectionUtils.getDeclaredField(it, URLClassLoader.class, 0)) - .orElse(null); - private final URLClassLoaderAccess libraryClassLoaderAccess; - - // todo 是否有更好的方法让库被其他插件共享 - public PaperClassPathAppender(ClassLoader classLoader) { - // 这个类加载器用于加载重定位后的依赖库,这样所有插件都能访问到 - ClassLoader bukkitClassLoader = Bukkit.class.getClassLoader(); - if (bukkitClassLoader instanceof URLClassLoader urlClassLoader) { - this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader); - } else { - // ignite会把Bukkit放置于EmberClassLoader中,获取其父DynamicClassLoader - if (bukkitClassLoader.getClass().getName().equals("space.vectrix.ignite.launch.ember.EmberClassLoader") && bukkitClassLoader.getParent() instanceof URLClassLoader urlClassLoader) { - this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader); - return; - } - try { - // 最次的方案,使用paper自带的classloader去加载依赖,这种情况会发生依赖隔离 - if (clazz$PaperPluginClassLoader != null && clazz$PaperPluginClassLoader.isInstance(classLoader)) { - URLClassLoader libraryClassLoader = (URLClassLoader) field$PaperPluginClassLoader$libraryLoader.get(classLoader); - this.libraryClassLoaderAccess = URLClassLoaderAccess.create(libraryClassLoader); - } else { - throw new IllegalStateException("ClassLoader is not instance of URLClassLoader"); - } - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to instantiate PaperPluginClassLoader", e); - } - } - } - - @Override - public void addJarToClasspath(Path file) { - try { - this.libraryClassLoaderAccess.addURL(file.toUri().toURL()); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperPluginClassPathAppender.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperPluginClassPathAppender.java new file mode 100644 index 000000000..aa9bcb53a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/PaperPluginClassPathAppender.java @@ -0,0 +1,44 @@ +package net.momirealms.craftengine.bukkit.plugin.classpath; + +import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; +import net.momirealms.craftengine.core.plugin.classpath.URLClassLoaderAccess; +import net.momirealms.craftengine.core.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Optional; + +public class PaperPluginClassPathAppender implements ClassPathAppender { + public static final Class clazz$PaperPluginClassLoader = ReflectionUtils.getClazz( + "io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader" + ); + public static final Field field$PaperPluginClassLoader$libraryLoader = Optional.ofNullable(clazz$PaperPluginClassLoader) + .map(it -> ReflectionUtils.getDeclaredField(it, URLClassLoader.class, 0)) + .orElse(null); + private final URLClassLoaderAccess libraryClassLoaderAccess; + + public PaperPluginClassPathAppender(ClassLoader classLoader) { + try { + // 使用paper自带的classloader去加载依赖,这种情况会发生依赖隔离 + if (clazz$PaperPluginClassLoader != null && clazz$PaperPluginClassLoader.isInstance(classLoader)) { + URLClassLoader libraryClassLoader = (URLClassLoader) field$PaperPluginClassLoader$libraryLoader.get(classLoader); + this.libraryClassLoaderAccess = URLClassLoaderAccess.create(libraryClassLoader); + } else { + throw new IllegalStateException("ClassLoader is not instance of URLClassLoader"); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to instantiate PaperPluginClassLoader", e); + } + } + + @Override + public void addJarToClasspath(Path file) { + try { + this.libraryClassLoaderAccess.addURL(file.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 04d613691..085c81690 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -8,7 +8,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.momirealms.craftengine.bukkit.plugin.reflection.ReflectionInitException; -import net.momirealms.craftengine.bukkit.plugin.reflection.paper.PaperReflections; import net.momirealms.craftengine.bukkit.util.BukkitReflectionUtils; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ReflectionUtils; 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 c2431e7d9..2d8671d53 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 @@ -1,7 +1,6 @@ package net.momirealms.craftengine.bukkit.world; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; @@ -10,7 +9,6 @@ import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.SectionPos; diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 6a172d1c3..49f6057f5 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -101,6 +101,10 @@ tasks { relocate("com.google.common.jimfs", "net.momirealms.craftengine.libraries.jimfs") relocate("org.apache.commons", "net.momirealms.craftengine.libraries.commons") relocate("io.leangen.geantyref", "net.momirealms.craftengine.libraries.geantyref") + relocate("io.netty.handler.codec.http", "net.momirealms.craftengine.libraries.netty.handler.codec.http") + relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") + relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") + relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index a78856e6f..aa5eea243 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -51,7 +51,8 @@ public abstract class CraftEngine implements Plugin { protected PluginLogger logger; protected Config config; protected Platform platform; - protected ClassPathAppender classPathAppender; + protected ClassPathAppender sharedClassPathAppender; + protected ClassPathAppender privateClassPathAppender; protected DependencyManager dependencyManager; protected SchedulerAdapter scheduler; protected NetworkManager networkManager; @@ -338,8 +339,13 @@ public abstract class CraftEngine implements Plugin { } @Override - public ClassPathAppender classPathAppender() { - return classPathAppender; + public ClassPathAppender sharedClassPathAppender() { + return sharedClassPathAppender; + } + + @Override + public ClassPathAppender privateClassPathAppender() { + return privateClassPathAppender; } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java index 3521c3914..1813c2bd8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java @@ -35,7 +35,9 @@ public interface Plugin { PluginLogger logger(); - ClassPathAppender classPathAppender(); + ClassPathAppender sharedClassPathAppender(); + + ClassPathAppender privateClassPathAppender(); File dataFolderFile(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 37eae795e..2ab48273b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -143,25 +143,6 @@ public class Dependencies { Collections.emptyList() ); - public static final Dependency SLF4J_API = new Dependency( - "slf4j-api", - "org.slf4j", - "slf4j-api", - Collections.emptyList() - ); - - public static final Dependency SLF4J_SIMPLE = new Dependency( - "slf4j-simple", - "org.slf4j", - "slf4j-simple", - Collections.emptyList() - ) { - @Override - public String getVersion() { - return Dependencies.SLF4J_API.getVersion(); - } - }; - public static final Dependency COMMONS_LANG3 = new Dependency( "commons-lang3", "org{}apache{}commons", @@ -224,7 +205,8 @@ public class Dependencies { "option", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ); public static final Dependency ADVENTURE_API = new Dependency( @@ -233,7 +215,8 @@ public class Dependencies { "adventure-api", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ); public static final Dependency ADVENTURE_NBT = new Dependency( @@ -256,7 +239,8 @@ public class Dependencies { "adventure-key", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -270,7 +254,8 @@ public class Dependencies { "examination-api", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ); public static final Dependency EXAMINATION_STRING = new Dependency( @@ -279,7 +264,8 @@ public class Dependencies { "examination-string", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -293,7 +279,8 @@ public class Dependencies { "adventure-text-minimessage", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -307,7 +294,8 @@ public class Dependencies { "adventure-text-serializer-commons", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -321,7 +309,8 @@ public class Dependencies { "adventure-text-serializer-gson", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -335,7 +324,8 @@ public class Dependencies { "adventure-text-serializer-json-legacy-impl", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -349,7 +339,8 @@ public class Dependencies { "adventure-text-serializer-legacy", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -363,7 +354,8 @@ public class Dependencies { "adventure-text-serializer-json", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { @@ -396,14 +388,16 @@ public class Dependencies { "netty-codec-http", "io{}netty", "netty-codec-http", - Collections.emptyList() + List.of(Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy")) ); public static final Dependency NETTY_HTTP2 = new Dependency( "netty-codec-http2", "io{}netty", "netty-codec-http2", - Collections.emptyList() + List.of(Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2")) ); public static final Dependency REACTIVE_STREAMS = new Dependency( diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java index 893716bac..ef1713a5c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java @@ -15,21 +15,36 @@ public class Dependency { private final String rawArtifactId; private final List relocations; private final Predicate verifier; + private final boolean shared; public Dependency(String id, String groupId, String artifactId, List relocations) { + this(id, groupId, artifactId, relocations, false); + } + + public Dependency(String id, String groupId, String artifactId, List relocations, boolean shared) { this.id = id; this.groupId = groupId; this.rawArtifactId = artifactId; this.relocations = relocations; this.verifier = (p) -> true; + this.shared = shared; } public Dependency(String id, String groupId, String artifactId, List relocations, Predicate verifier) { + this(id, groupId, artifactId, relocations, verifier, false); + } + + public Dependency(String id, String groupId, String artifactId, List relocations, Predicate verifier, boolean shared) { this.id = id; this.groupId = groupId; this.rawArtifactId = artifactId; this.relocations = relocations; this.verifier = verifier; + this.shared = shared; + } + + public boolean shared() { + return shared; } public boolean verify(Path remapped) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java index 886f830d2..5e8eeec28 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java @@ -22,7 +22,8 @@ import java.util.stream.Stream; public class DependencyManagerImpl implements DependencyManager { private final DependencyRegistry registry; private final Path cacheDirectory; - private final ClassPathAppender classPathAppender; + private final ClassPathAppender sharedClassPathAppender; + private final ClassPathAppender privateClassPathAppender; private final Map loaded = Collections.synchronizedMap(new HashMap<>()); private final Map, IsolatedClassLoader> loaders = new HashMap<>(); private final RelocationHandler relocationHandler; @@ -33,7 +34,8 @@ public class DependencyManagerImpl implements DependencyManager { this.plugin = plugin; this.registry = new DependencyRegistry(); this.cacheDirectory = setupCacheDirectory(plugin); - this.classPathAppender = plugin.classPathAppender(); + this.sharedClassPathAppender = plugin.sharedClassPathAppender(); + this.privateClassPathAppender = plugin.privateClassPathAppender(); this.loadingExecutor = plugin.scheduler().async(); this.relocationHandler = new RelocationHandler(this); } @@ -108,8 +110,14 @@ public class DependencyManagerImpl implements DependencyManager { this.loaded.put(dependency, file); - if (this.classPathAppender != null && this.registry.shouldAutoLoad(dependency)) { - this.classPathAppender.addJarToClasspath(file); + if (dependency.shared()) { + if (this.sharedClassPathAppender != null && this.registry.shouldAutoLoad(dependency)) { + this.sharedClassPathAppender.addJarToClasspath(file); + } + } else { + if (this.privateClassPathAppender != null && this.registry.shouldAutoLoad(dependency)) { + this.privateClassPathAppender.addJarToClasspath(file); + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/relocation/Relocation.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/relocation/Relocation.java index 2833d3f3d..10a29eda1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/relocation/Relocation.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/relocation/Relocation.java @@ -6,7 +6,7 @@ public final class Relocation { private static final String RELOCATION_PREFIX = "net.momirealms.craftengine.libraries."; public static Relocation of(String id, String pattern) { - return new Relocation(pattern.replace("{}", "."), RELOCATION_PREFIX + id); + return new Relocation(pattern.replace("{}", "."), RELOCATION_PREFIX + id.replace("{}", ".")); } private final String pattern; diff --git a/gradle.properties b/gradle.properties index eca3dd45d..c60a73837 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.9 +project_version=0.0.62.10 config_version=45 lang_version=25 project_group=net.momirealms @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.20 -nms_helper_version=1.0.69 +nms_helper_version=1.0.73 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From c2973e904ce7ea2ce2cb6825f1aceacb11f6ba9e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 01:06:14 +0800 Subject: [PATCH 013/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BA=93=E7=B1=BB?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/PaperCraftEngineBootstrap.java | 21 +++++++++++++------ .../classpath/BukkitClassPathAppender.java | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java index 553b4ce2b..05364cd61 100644 --- a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java +++ b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java @@ -39,12 +39,21 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap { } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to getLogger", e); } - this.plugin = new BukkitCraftEngine( - logger, - context.getDataDirectory(), - new BukkitClassPathAppender(), - new PaperPluginClassPathAppender(this.getClass().getClassLoader()) - ); + try { + this.plugin = new BukkitCraftEngine( + logger, + context.getDataDirectory(), + new BukkitClassPathAppender(), + new PaperPluginClassPathAppender(this.getClass().getClassLoader()) + ); + } catch (UnsupportedOperationException e) { + this.plugin = new BukkitCraftEngine( + logger, + context.getDataDirectory(), + new PaperPluginClassPathAppender(this.getClass().getClassLoader()), + new PaperPluginClassPathAppender(this.getClass().getClassLoader()) + ); + } this.plugin.applyDependencies(); this.plugin.setUpConfig(); if (isDatapackDiscoveryAvailable()) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java index d70d4b615..804947546 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/classpath/BukkitClassPathAppender.java @@ -21,7 +21,7 @@ public class BukkitClassPathAppender implements ClassPathAppender { if (bukkitClassLoader.getClass().getName().equals("space.vectrix.ignite.launch.ember.EmberClassLoader") && bukkitClassLoader.getParent() instanceof URLClassLoader urlClassLoader) { this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader); } else { - this.libraryClassLoaderAccess = null; + throw new UnsupportedOperationException("Unsupported classloader " + bukkitClassLoader.getClass()); } } } From 3464a0ca2ab9c145811c5ce0f41a78d2aae68d6a Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 03:21:35 +0800 Subject: [PATCH 014/226] =?UTF-8?q?=E7=AE=80=E5=8C=96=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/injector/WorldStorageInjector.java | 26 +++++++++++++---- .../craftengine/bukkit/util/LightUtils.java | 2 +- .../bukkit/world/BukkitCEWorld.java | 13 +++++---- .../core/block/entity/BlockEntity.java | 3 ++ .../core/util/SectionPosUtils.java | 2 +- .../craftengine/core/world/CEWorld.java | 19 +------------ .../craftengine/core/world/chunk/CEChunk.java | 28 +++++++++---------- .../core/world/chunk/CESection.java | 2 +- gradle.properties | 2 +- 9 files changed, 50 insertions(+), 47 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 89e4bfea1..097873817 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -19,10 +19,13 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.SectionPosUtils; +import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.SectionPos; import net.momirealms.craftengine.core.world.chunk.CEChunk; @@ -224,7 +227,20 @@ public final class WorldStorageInjector { ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE); // 处理 自定义块 -> 原版块 if (!previous.isEmpty()) { - holder.ceChunk().setDirty(true); + CEChunk chunk = holder.ceChunk(); + chunk.setDirty(true); + if (previous.hasBlockEntity()) { + BlockPos pos = new BlockPos( + chunk.chunkPos().x * 16 + x, + section.sectionY() * 16 + y, + chunk.chunkPos().z * 16 + z + ); + BlockEntity blockEntity = chunk.getBlockEntity(pos); + if (blockEntity != null) { + blockEntity.preRemove(); + chunk.removeBlockEntity(pos); + } + } if (Config.enableLightSystem()) { // 自定义块到原版块,只需要判断旧块是否和客户端一直 BlockStateWrapper wrapper = previous.vanillaBlockState(); @@ -258,8 +274,8 @@ public final class WorldStorageInjector { } @SuppressWarnings("DuplicatedCode") - protected static void updateLight(@This InjectedHolder thisObj, Object clientState, Object serverState, int x, int y, int z) { - CEWorld world = thisObj.ceChunk().world(); + private static void updateLight(@This InjectedHolder thisObj, Object clientState, Object serverState, int x, int y, int z) { + CEWorld world = thisObj.ceChunk().world; Object blockPos = LocationUtils.toBlockPos(x, y, z); Object serverWorld = world.world().serverWorld(); if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(serverState, clientState, serverWorld, blockPos)) { @@ -270,8 +286,8 @@ public final class WorldStorageInjector { } @SuppressWarnings("DuplicatedCode") - protected static void updateLight$complex(@This InjectedHolder thisObj, Object newClientState, Object newServerState, Object oldServerState, int x, int y, int z) { - CEWorld world = thisObj.ceChunk().world(); + private static void updateLight$complex(@This InjectedHolder thisObj, Object newClientState, Object newServerState, Object oldServerState, int x, int y, int z) { + CEWorld world = thisObj.ceChunk().world; Object blockPos = LocationUtils.toBlockPos(x, y, z); Object serverWorld = world.world().serverWorld(); // 如果客户端新状态和服务端新状态光照属性不同 diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java index f51715074..e9fada3dc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java @@ -23,7 +23,7 @@ public final class LightUtils { if (chunkHolder == null) continue; List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(chunkHolder); if (players.isEmpty()) continue; - Object lightEngine = CoreReflections.field$ChunkHolder$lightEngine.get(chunkHolder); + Object lightEngine = FastNMS.INSTANCE.method$ChunkSource$getLightEngine(chunkSource); Object chunkPos = FastNMS.INSTANCE.constructor$ChunkPos((int) chunkKey, (int) (chunkKey >> 32)); Object lightPacket = FastNMS.INSTANCE.constructor$ClientboundLightUpdatePacket(chunkPos, lightEngine, entry.getValue(), entry.getValue()); for (Object player : players) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 136eadb74..a3f56861a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -9,7 +9,8 @@ import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.List; public class BukkitCEWorld extends CEWorld { @@ -23,17 +24,17 @@ public class BukkitCEWorld extends CEWorld { @Override public void updateLight() { - HashSet poses; + List poses; synchronized (super.updatedSectionSet) { - poses = new HashSet<>(super.updatedSectionSet); + poses = new ArrayList<>(super.updatedSectionSet); super.updatedSectionSet.clear(); } if (Config.enableLightSystem()) { LightUtils.updateChunkLight( - (org.bukkit.World) world.platformWorld(), + (org.bukkit.World) this.world.platformWorld(), SectionPosUtils.toMap(poses, - world.worldHeight().getMinSection() - 1, - world.worldHeight().getMaxSection() + 1 + this.world.worldHeight().getMinSection() - 1, + this.world.worldHeight().getMaxSection() + 1 ) ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java index 0c988377c..d8b8c93ae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -55,6 +55,9 @@ public abstract class BlockEntity { protected void readCustomData(CompoundTag tag) { } + public void preRemove() { + } + public static BlockPos readPos(CompoundTag tag) { return new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SectionPosUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/SectionPosUtils.java index 4640a4140..8d2871260 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/SectionPosUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SectionPosUtils.java @@ -29,7 +29,7 @@ public class SectionPosUtils { return nearby; } - public static Map toMap(Set sections, int minLightSection, int maxLightSection) { + public static Map toMap(Collection sections, int minLightSection, int maxLightSection) { int nBits = maxLightSection - minLightSection; Map nearby = new Long2ObjectOpenHashMap<>(Math.max(8, sections.size() / 2), 0.5f); for (SectionPos section : sections) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 5feac8921..6633a90c5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -30,15 +30,11 @@ public abstract class CEWorld { protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; - private CEChunk lastChunk; - private long lastChunkPos; - public CEWorld(World world, StorageAdaptor adaptor) { this.world = world; this.loadedChunkMap = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(1024, 0.5f); this.worldDataStorage = adaptor.adapt(world); this.worldHeightAccessor = world.worldHeight(); - this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; } public CEWorld(World world, WorldDataStorage dataStorage) { @@ -46,7 +42,6 @@ public abstract class CEWorld { this.loadedChunkMap = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(1024, 0.5f); this.worldDataStorage = dataStorage; this.worldHeightAccessor = world.worldHeight(); - this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; } public void setTicking(boolean ticking) { @@ -103,23 +98,11 @@ public abstract class CEWorld { public void removeLoadedChunk(CEChunk chunk) { this.loadedChunkMap.remove(chunk.chunkPos().longKey()); - if (this.lastChunk == chunk) { - this.lastChunk = null; - this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; - } } @Nullable public CEChunk getChunkAtIfLoaded(long chunkPos) { - if (chunkPos == this.lastChunkPos) { - return this.lastChunk; - } - CEChunk chunk = this.loadedChunkMap.get(chunkPos); - if (chunk != null) { - this.lastChunk = chunk; - this.lastChunkPos = chunkPos; - } - return chunk; + return this.loadedChunkMap.get(chunkPos); } @Nullable diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 37f6e5a6e..bc18edc44 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -16,13 +16,13 @@ import java.util.List; import java.util.Map; public class CEChunk { - private boolean loaded; - private final CEWorld world; - private final ChunkPos chunkPos; - private final CESection[] sections; - private final WorldHeight worldHeightAccessor; - private final Map blockEntities; - private boolean dirty; + public final CEWorld world; + public final ChunkPos chunkPos; + public final CESection[] sections; + public final WorldHeight worldHeightAccessor; + public final Map blockEntities; + private volatile boolean dirty; + private volatile boolean loaded; public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; @@ -131,9 +131,9 @@ public class CEChunk { } private void fillEmptySection() { - for (int i = 0; i < sections.length; ++i) { - if (sections[i] == null) { - sections[i] = new CESection(world.worldHeight().getSectionYFromSectionIndex(i), + for (int i = 0; i < this.sections.length; ++i) { + if (this.sections[i] == null) { + this.sections[i] = new CESection(this.world.worldHeight().getSectionYFromSectionIndex(i), new PalettedContainer<>(null, EmptyBlock.STATE, PalettedContainer.PaletteProvider.CUSTOM_BLOCK_STATE)); } } @@ -190,21 +190,21 @@ public class CEChunk { @NotNull public CEWorld world() { - return world; + return this.world; } @NotNull public ChunkPos chunkPos() { - return chunkPos; + return this.chunkPos; } @NotNull public CESection[] sections() { - return sections; + return this.sections; } public boolean isLoaded() { - return loaded; + return this.loaded; } public void load() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java index a7f9553ab..bb106d8fe 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java @@ -53,6 +53,6 @@ public class CESection { } public int sectionY() { - return sectionY; + return this.sectionY; } } diff --git a/gradle.properties b/gradle.properties index c60a73837..3704d79f4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.20 -nms_helper_version=1.0.73 +nms_helper_version=1.0.74 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From 279bdce3ed6df3747cafd19e2413f7d020e03e72 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 05:32:41 +0800 Subject: [PATCH 015/226] =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=932?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnsafeCompositeBlockBehavior.java | 18 +++ .../plugin/injector/WorldStorageInjector.java | 119 +++++++++++------- .../craftengine/bukkit/util/LightUtils.java | 1 - .../core/block/AbstractCustomBlock.java | 5 + .../craftengine/core/block/BlockBehavior.java | 10 ++ .../core/block/ImmutableBlockState.java | 10 ++ .../block/behavior/EntityBlockBehavior.java | 19 +++ .../core/block/entity/BlockEntity.java | 19 ++- .../entity/tick/DummyTickingBlockEntity.java | 21 ++++ .../tick/ReplaceableTickingBlockEntity.java | 2 +- .../core/registry/BuiltInRegistries.java | 2 + .../craftengine/core/registry/Registries.java | 2 + .../craftengine/core/world/BlockPos.java | 1 + .../craftengine/core/world/CEWorld.java | 12 +- .../craftengine/core/world/Vec3i.java | 4 +- .../craftengine/core/world/chunk/CEChunk.java | 44 ++++++- .../core/world/chunk/CESection.java | 4 +- .../DefaultBlockEntitySerializer.java | 17 ++- 18 files changed, 245 insertions(+), 65 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/DummyTickingBlockEntity.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 4542f71d2..7ffbfcd02 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -4,9 +4,11 @@ import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.UseOnContext; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Optional; @@ -31,6 +33,22 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { return Optional.empty(); } + @Nullable + @Override + public EntityBlockBehavior getEntityBehavior() { + EntityBlockBehavior target = null; + for (AbstractBlockBehavior behavior : this.behaviors) { + if (behavior instanceof EntityBlockBehavior entityBehavior) { + if (target == null) { + target = entityBehavior; + } else { + throw new IllegalArgumentException("Multiple entity block behaviors are not allowed"); + } + } + } + return target; + } + @Override public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { for (AbstractBlockBehavior behavior : this.behaviors) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 097873817..81fdda00b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -19,7 +19,6 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -36,6 +35,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; import java.util.function.Consumer; @@ -217,59 +217,84 @@ public final class WorldStorageInjector { } } + @SuppressWarnings("DuplicatedCode") private static void compareAndUpdateBlockState(int x, int y, int z, Object newState, Object previousState, InjectedHolder holder) { - try { - Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(newState); - CESection section = holder.ceSection(); - // 如果是原版方块 - if (optionalCustomState.isEmpty()) { - // 那么应该清空自定义块 - ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE); - // 处理 自定义块 -> 原版块 - if (!previous.isEmpty()) { - CEChunk chunk = holder.ceChunk(); - chunk.setDirty(true); - if (previous.hasBlockEntity()) { - BlockPos pos = new BlockPos( - chunk.chunkPos().x * 16 + x, - section.sectionY() * 16 + y, - chunk.chunkPos().z * 16 + z - ); - BlockEntity blockEntity = chunk.getBlockEntity(pos); - if (blockEntity != null) { - blockEntity.preRemove(); - chunk.removeBlockEntity(pos); - } - } - if (Config.enableLightSystem()) { - // 自定义块到原版块,只需要判断旧块是否和客户端一直 - BlockStateWrapper wrapper = previous.vanillaBlockState(); - if (wrapper != null) { - updateLight(holder, wrapper.literalObject(), previousState, x, y, z); - } + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(newState); + CESection section = holder.ceSection(); + // 如果是原版方块 + if (optionalCustomState.isEmpty()) { + // 那么应该清空自定义块 + ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE); + // 处理 自定义块 -> 原版块 + if (!previous.isEmpty()) { + CEChunk chunk = holder.ceChunk(); + chunk.setDirty(true); + if (previous.hasBlockEntity()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + BlockEntity blockEntity = chunk.getBlockEntity(pos, false); + if (blockEntity != null) { + blockEntity.preRemove(); + chunk.removeBlockEntity(pos); } } - } else { - ImmutableBlockState immutableBlockState = optionalCustomState.get(); - ImmutableBlockState previousImmutableBlockState = section.setBlockState(x, y, z, immutableBlockState); - if (previousImmutableBlockState == immutableBlockState) return; - // 处理 自定义块到自定义块或原版块到自定义块 - holder.ceChunk().setDirty(true); - // 不可能!绝对不可能! - if (immutableBlockState.isEmpty()) return; - // 如果新方块的光照属性和客户端认为的不同 if (Config.enableLightSystem()) { - if (previousImmutableBlockState.isEmpty()) { - // 原版块到自定义块,只需要判断新块是否和客户端视觉一致 - updateLight(holder, immutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z); - } else { - // 自定义块到自定义块 - updateLight$complex(holder, immutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); + // 自定义块到原版块,只需要判断旧块是否和客户端一直 + BlockStateWrapper wrapper = previous.vanillaBlockState(); + if (wrapper != null) { + updateLight(holder, wrapper.literalObject(), previousState, x, y, z); } } } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to intercept setBlockState", e); + } else { + ImmutableBlockState newImmutableBlockState = optionalCustomState.get(); + ImmutableBlockState previousImmutableBlockState = section.setBlockState(x, y, z, newImmutableBlockState); + if (previousImmutableBlockState == newImmutableBlockState) return; + // 处理 自定义块到自定义块或原版块到自定义块 + CEChunk chunk = holder.ceChunk(); + chunk.setDirty(true); + // 如果两个方块没有相同的主人 且 旧方块有方块实体 + if (!previousImmutableBlockState.isEmpty()) { + if (previousImmutableBlockState.owner() != newImmutableBlockState.owner() && previousImmutableBlockState.hasBlockEntity()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + BlockEntity blockEntity = chunk.getBlockEntity(pos, false); + if (blockEntity != null) { + try { + blockEntity.preRemove(); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Error removing block entity " + blockEntity.getClass().getName(), t); + } + chunk.removeBlockEntity(pos); + } + } + } + if (newImmutableBlockState.hasBlockEntity()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + BlockEntity blockEntity = chunk.getBlockEntity(pos, false); + if (blockEntity != null && !blockEntity.isValidBlockState(newImmutableBlockState)) { + chunk.removeBlockEntity(pos); + blockEntity = null; + } + if (blockEntity == null) { + blockEntity = Objects.requireNonNull(newImmutableBlockState.behavior().getEntityBehavior()).createBlockEntity(pos, newImmutableBlockState); + if (blockEntity != null) { + chunk.addBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(newImmutableBlockState); + // 方块类型未变,仅更新状态,选择性更新ticker + chunk.replaceOrCreateTickingBlockEntity(blockEntity); + } + } + // 如果新方块的光照属性和客户端认为的不同 + if (Config.enableLightSystem()) { + if (previousImmutableBlockState.isEmpty()) { + // 原版块到自定义块,只需要判断新块是否和客户端视觉一致 + updateLight(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z); + } else { + // 自定义块到自定义块 + updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); + } + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java index e9fada3dc..298ca5786 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LightUtils.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.core.plugin.CraftEngine; import org.bukkit.World; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index ec7a0ff65..733bdc88f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.block; import com.google.common.collect.ImmutableMap; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.parser.BlockNbtParser; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; @@ -80,12 +81,16 @@ public abstract class AbstractCustomBlock implements CustomBlock { state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); } + EntityBlockBehavior entityBlockBehavior = this.behavior.getEntityBehavior(); // double check if there's any invalid state for (ImmutableBlockState state : this.variantProvider().states()) { state.setBehavior(this.behavior); if (state.settings() == null) { state.setSettings(settings); } + if (entityBlockBehavior != null) { + state.setBlockEntityType(entityBlockBehavior.blockEntityType()); + } } this.applyPlatformSettings(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 4af326d66..af354ade7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.block; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; @@ -10,6 +11,7 @@ import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.Optional; import java.util.concurrent.Callable; @@ -24,6 +26,14 @@ public abstract class BlockBehavior { return Optional.empty(); } + @Nullable + public EntityBlockBehavior getEntityBehavior() { + if (this instanceof EntityBlockBehavior behavior) { + return behavior; + } + return null; + } + // BlockState state, Rotation rotation public Object rotate(Object thisBlock, Object[] args, Callable superMethod) throws Exception { return superMethod.call(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index aac373b56..cfa5ebb2d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -1,8 +1,10 @@ package net.momirealms.craftengine.core.block; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; @@ -10,6 +12,7 @@ import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.World; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; @@ -147,4 +150,11 @@ public final class ImmutableBlockState extends BlockStateHolder { if (lootTable == null) return List.of(); return lootTable.getRandomItems(builder.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, this).build(), world, player); } + + @SuppressWarnings("unchecked") + public BlockEntityTicker createBlockEntityTicker(CEWorld world, BlockEntityType type) { + EntityBlockBehavior blockBehavior = this.behavior.getEntityBehavior(); + if (blockBehavior == null) return null; + return (BlockEntityTicker) blockBehavior.createBlockEntityTicker(world, this, type); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java new file mode 100644 index 000000000..2c27e91fd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java @@ -0,0 +1,19 @@ +package net.momirealms.craftengine.core.block.behavior; + +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; + +public interface EntityBlockBehavior { + + BlockEntityType blockEntityType(); + + BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state); + + default BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + return null; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java index d8b8c93ae..566a75f08 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -9,7 +9,7 @@ import net.momirealms.sparrow.nbt.CompoundTag; public abstract class BlockEntity { protected final BlockPos pos; - protected final ImmutableBlockState blockState; + protected ImmutableBlockState blockState; protected BlockEntityType type; protected CEWorld world; protected boolean valid; @@ -22,11 +22,24 @@ public abstract class BlockEntity { public final CompoundTag saveAsTag() { CompoundTag tag = new CompoundTag(); + this.saveId(tag); this.savePos(tag); this.saveCustomData(tag); return tag; } + private void saveId(CompoundTag tag) { + tag.putString("id", this.type.id().asString()); + } + + public void setBlockState(ImmutableBlockState blockState) { + this.blockState = blockState; + } + + public ImmutableBlockState blockState() { + return blockState; + } + public CEWorld world() { return world; } @@ -52,7 +65,7 @@ public abstract class BlockEntity { protected void saveCustomData(CompoundTag tag) { } - protected void readCustomData(CompoundTag tag) { + public void loadCustomData(CompoundTag tag) { } public void preRemove() { @@ -84,7 +97,7 @@ public abstract class BlockEntity { } public boolean isValidBlockState(ImmutableBlockState blockState) { - return blockState.blockEntityType() == this.type; + return this.type == blockState.blockEntityType(); } public interface Factory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/DummyTickingBlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/DummyTickingBlockEntity.java new file mode 100644 index 000000000..e85353685 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/DummyTickingBlockEntity.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.core.block.entity.tick; + +import net.momirealms.craftengine.core.world.BlockPos; + +public class DummyTickingBlockEntity implements TickingBlockEntity { + public static final DummyTickingBlockEntity INSTANCE = new DummyTickingBlockEntity(); + + @Override + public boolean isValid() { + return false; + } + + @Override + public void tick() { + } + + @Override + public BlockPos pos() { + return BlockPos.ZERO; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java index c9526faae..74ba19c4d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/ReplaceableTickingBlockEntity.java @@ -13,7 +13,7 @@ public class ReplaceableTickingBlockEntity implements TickingBlockEntity { return target; } - public void setTarget(TickingBlockEntity target) { + public void setTicker(TickingBlockEntity target) { this.target = target; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index d002e007a..e5db08ae8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.registry; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; import net.momirealms.craftengine.core.block.properties.PropertyFactory; import net.momirealms.craftengine.core.entity.furniture.HitBoxFactory; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; @@ -85,6 +86,7 @@ public class BuiltInRegistries { public static final Registry> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE, 16); public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE, 16); public static final Registry> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET, 16); + public static final Registry> BLOCK_ENTITY_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_TYPE, 128); private static Registry createConstantBoundRegistry(ResourceKey> key, int expectedSize) { return new ConstantBoundRegistry<>(key, expectedSize); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index b4fda0671..374ff31f8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.registry; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; import net.momirealms.craftengine.core.block.properties.PropertyFactory; import net.momirealms.craftengine.core.entity.furniture.HitBoxFactory; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; @@ -87,4 +88,5 @@ public class Registries { public static final ResourceKey>> RECIPE_POST_PROCESSOR_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("recipe_post_processor_type")); public static final ResourceKey>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type")); public static final ResourceKey>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet_type")); + public static final ResourceKey>> BLOCK_ENTITY_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_type")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 686b5e95d..0bc751b85 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.MCUtils; public class BlockPos extends Vec3i { + public static final BlockPos ZERO = new BlockPos(0, 0, 0); public BlockPos(int x, int y, int z) { super(x, y, z); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 6633a90c5..9d68a0e1c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -149,7 +149,7 @@ public abstract class CEWorld { if (chunk == null) { return null; } - return chunk.getBlockEntity(blockPos); + return chunk.getBlockEntity(blockPos, true); } public WorldDataStorage worldDataStorage() { @@ -183,6 +183,14 @@ public abstract class CEWorld { public abstract void updateLight(); + public void addBlockEntityTicker(TickingBlockEntity ticker) { + if (this.isTickingBlockEntities) { + this.pendingTickingBlockEntities.add(ticker); + } else { + this.tickingBlockEntities.add(ticker); + } + } + protected void tickBlockEntities() { this.isTickingBlockEntities = true; if (!this.pendingTickingBlockEntities.isEmpty()) { @@ -191,7 +199,7 @@ public abstract class CEWorld { } ReferenceOpenHashSet toRemove = new ReferenceOpenHashSet<>(); for (TickingBlockEntity blockEntity : this.tickingBlockEntities) { - if (!blockEntity.isValid()) { + if (blockEntity.isValid()) { blockEntity.tick(); } else { toRemove.add(blockEntity); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java index 7d49c6aaa..9413aff03 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java @@ -47,12 +47,12 @@ public class Vec3i implements Comparable { @Override public boolean equals(Object object) { - return this == object || object instanceof Vec3i vec3i && this.x() == vec3i.x() && this.y() == vec3i.y() && this.z() == vec3i.z(); + return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z; } @Override public int hashCode() { - return (this.y() + this.z() * 31) * 31 + this.x(); + return (this.y + this.z * 31) * 31 + this.x; } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index bc18edc44..a740c94ff 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; @@ -14,6 +15,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; public class CEChunk { public final CEWorld world; @@ -23,6 +26,7 @@ public class CEChunk { public final Map blockEntities; private volatile boolean dirty; private volatile boolean loaded; + protected final Map tickingBlockEntitiesByPos = new ConcurrentHashMap<>(); public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; @@ -57,6 +61,7 @@ public class CEChunk { public void addBlockEntity(BlockEntity blockEntity) { this.setBlockEntity(blockEntity); + this.replaceOrCreateTickingBlockEntity(blockEntity); } public void removeBlockEntity(BlockPos blockPos) { @@ -66,6 +71,33 @@ public class CEChunk { } } + public void replaceOrCreateTickingBlockEntity(T blockEntity) { + ImmutableBlockState blockState = blockEntity.blockState(); + BlockEntityTicker ticker = blockState.createBlockEntityTicker(this.world, blockEntity.type()); + if (ticker != null) { + this.tickingBlockEntitiesByPos.compute(blockEntity.pos(), ((pos, previousTicker) -> { + TickingBlockEntity newTicker = new TickingBlockEntityImpl<>(this, blockEntity, ticker); + if (previousTicker != null) { + previousTicker.setTicker(newTicker); + return previousTicker; + } else { + ReplaceableTickingBlockEntity replaceableTicker = new ReplaceableTickingBlockEntity(newTicker); + this.world.addBlockEntityTicker(replaceableTicker); + return replaceableTicker; + } + })); + } else { + this.removeBlockEntityTicker(blockEntity.pos()); + } + } + + private void removeBlockEntityTicker(BlockPos pos) { + ReplaceableTickingBlockEntity blockEntity = this.tickingBlockEntitiesByPos.remove(pos); + if (blockEntity != null) { + blockEntity.setTicker(DummyTickingBlockEntity.INSTANCE); + } + } + public void setBlockEntity(BlockEntity blockEntity) { BlockPos pos = blockEntity.pos(); ImmutableBlockState blockState = this.getBlockState(pos); @@ -84,12 +116,14 @@ public class CEChunk { } @Nullable - public BlockEntity getBlockEntity(BlockPos pos) { + public BlockEntity getBlockEntity(BlockPos pos, boolean create) { BlockEntity blockEntity = this.blockEntities.get(pos); if (blockEntity == null) { - blockEntity = createBlockEntity(pos); - if (blockEntity != null) { - this.addBlockEntity(blockEntity); + if (create) { + blockEntity = createBlockEntity(pos); + if (blockEntity != null) { + this.addBlockEntity(blockEntity); + } } } else { if (!blockEntity.isValid()) { @@ -105,7 +139,7 @@ public class CEChunk { if (!blockState.hasBlockEntity()) { return null; } - return blockState.blockEntityType().factory().create(pos, blockState); + return Objects.requireNonNull(blockState.behavior().getEntityBehavior()).createBlockEntity(pos, blockState); } public Map blockEntities() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java index bb106d8fe..344509b5c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CESection.java @@ -9,8 +9,8 @@ public class CESection { public static final int SECTION_HEIGHT = 16; public static final int SECTION_SIZE = SECTION_WIDTH * SECTION_WIDTH * SECTION_HEIGHT; - private final int sectionY; - private final PalettedContainer statesContainer; + public final int sectionY; + public final PalettedContainer statesContainer; public CESection(int sectionY, PalettedContainer statesContainer) { this.sectionY = sectionY; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index 246a77363..0e1d59602 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -2,6 +2,10 @@ package net.momirealms.craftengine.core.world.chunk.serialization; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.plugin.logger.Debugger; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.sparrow.nbt.CompoundTag; @@ -28,8 +32,17 @@ public final class DefaultBlockEntitySerializer { List blockEntities = new ArrayList<>(tag.size()); for (int i = 0; i < tag.size(); i++) { CompoundTag data = tag.getCompound(i); - BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); - ImmutableBlockState blockState = chunk.getBlockState(pos); + Key id = Key.of(data.getString("id")); + BlockEntityType type = BuiltInRegistries.BLOCK_ENTITY_TYPE.getValue(id); + if (type == null) { + Debugger.BLOCK_ENTITY.debug(() -> "Unknown block entity type: " + id); + } else { + BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); + ImmutableBlockState blockState = chunk.getBlockState(pos); + BlockEntity blockEntity = type.factory().create(pos, blockState); + blockEntity.loadCustomData(data); + blockEntities.add(blockEntity); + } } return blockEntities; } From 75fb818c405414a8f39f82ada95684286e1f2497 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 05:51:07 +0800 Subject: [PATCH 016/226] =?UTF-8?q?=E9=99=90=E5=88=B6=E6=B4=BB=E5=A1=9E?= =?UTF-8?q?=E8=A1=8C=E5=8A=A8=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/block/AbstractCustomBlock.java | 11 +++++++--- .../craftengine/core/block/BlockSettings.java | 7 +++++++ .../craftengine/core/block/PushReaction.java | 20 ++++++++++++++----- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index 733bdc88f..c1852233d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -59,6 +59,11 @@ public abstract class AbstractCustomBlock implements CustomBlock { placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue())); } this.placementFunction = composite(placements); + EntityBlockBehavior entityBlockBehavior = this.behavior.getEntityBehavior(); + boolean isEntityBlock = entityBlockBehavior != null; + if (isEntityBlock) { + settings.toBlockEntitySettings(); + } for (Map.Entry entry : variantMapper.entrySet()) { String nbtString = entry.getKey(); CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); @@ -77,18 +82,18 @@ public abstract class AbstractCustomBlock implements CustomBlock { } // Late init states ImmutableBlockState state = possibleStates.getFirst(); - state.setSettings(blockStateVariant.settings()); + state.setSettings(isEntityBlock ? blockStateVariant.settings().toBlockEntitySettings() : blockStateVariant.settings()); state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); } - EntityBlockBehavior entityBlockBehavior = this.behavior.getEntityBehavior(); + // double check if there's any invalid state for (ImmutableBlockState state : this.variantProvider().states()) { state.setBehavior(this.behavior); if (state.settings() == null) { state.setSettings(settings); } - if (entityBlockBehavior != null) { + if (isEntityBlock) { state.setBlockEntityType(entityBlockBehavior.blockEntityType()); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java index e2e78a9a4..f3c08fe6c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java @@ -110,6 +110,13 @@ public class BlockSettings { return newSettings; } + public BlockSettings toBlockEntitySettings() { + if (!this.pushReaction.allowedForBlockEntity()) { + this.pushReaction = PushReaction.BLOCK; + } + return this; + } + public Set tags() { return tags; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java b/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java index db1d653be..e194b3f84 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java @@ -1,9 +1,19 @@ package net.momirealms.craftengine.core.block; public enum PushReaction { - NORMAL, - DESTROY, - BLOCK, - IGNORE, - PUSH_ONLY + NORMAL(false), + DESTROY(true), + BLOCK(true), + IGNORE(false), + PUSH_ONLY(false); + + private final boolean allowedForBlockEntity; + + PushReaction(boolean allowedForBlockEntity) { + this.allowedForBlockEntity = allowedForBlockEntity; + } + + public boolean allowedForBlockEntity() { + return allowedForBlockEntity; + } } From 1c7ce470a178422219f9c2920b22fcd7d64ffcaa Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 05:53:03 +0800 Subject: [PATCH 017/226] =?UTF-8?q?=E4=B8=8D=E5=AF=B9=E4=B8=8D=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/block/AbstractCustomBlock.java | 5 +---- .../craftengine/core/block/BlockSettings.java | 7 ------- .../craftengine/core/block/PushReaction.java | 20 +++++-------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index c1852233d..9a965a5ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -61,9 +61,6 @@ public abstract class AbstractCustomBlock implements CustomBlock { this.placementFunction = composite(placements); EntityBlockBehavior entityBlockBehavior = this.behavior.getEntityBehavior(); boolean isEntityBlock = entityBlockBehavior != null; - if (isEntityBlock) { - settings.toBlockEntitySettings(); - } for (Map.Entry entry : variantMapper.entrySet()) { String nbtString = entry.getKey(); CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); @@ -82,7 +79,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { } // Late init states ImmutableBlockState state = possibleStates.getFirst(); - state.setSettings(isEntityBlock ? blockStateVariant.settings().toBlockEntitySettings() : blockStateVariant.settings()); + state.setSettings(blockStateVariant.settings()); state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java index f3c08fe6c..e2e78a9a4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java @@ -110,13 +110,6 @@ public class BlockSettings { return newSettings; } - public BlockSettings toBlockEntitySettings() { - if (!this.pushReaction.allowedForBlockEntity()) { - this.pushReaction = PushReaction.BLOCK; - } - return this; - } - public Set tags() { return tags; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java b/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java index e194b3f84..db1d653be 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java @@ -1,19 +1,9 @@ package net.momirealms.craftengine.core.block; public enum PushReaction { - NORMAL(false), - DESTROY(true), - BLOCK(true), - IGNORE(false), - PUSH_ONLY(false); - - private final boolean allowedForBlockEntity; - - PushReaction(boolean allowedForBlockEntity) { - this.allowedForBlockEntity = allowedForBlockEntity; - } - - public boolean allowedForBlockEntity() { - return allowedForBlockEntity; - } + NORMAL, + DESTROY, + BLOCK, + IGNORE, + PUSH_ONLY } From 65b00161fbe076174b6a2144812356a43c3ec2bf Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 06:39:47 +0800 Subject: [PATCH 018/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/injector/BlockGenerator.java | 1 + .../reflection/minecraft/CoreReflections.java | 3 ++- .../reflection/minecraft/NetworkReflections.java | 1 - .../craftengine/core/block/BlockBehavior.java | 2 +- .../core/plugin/dependency/Dependencies.java | 3 ++- .../craftengine/core/world/chunk/CEChunk.java | 16 ++++++++++------ 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index 5b6b21096..85ba77e5b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -496,6 +496,7 @@ public final class BlockGenerator { } } } + public static class PerformBoneMealInterceptor { public static final PerformBoneMealInterceptor INSTANCE = new PerformBoneMealInterceptor(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 247226581..600e1505b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -1571,7 +1571,8 @@ public final class CoreReflections { ); public static final Method method$BlockBehaviour$hasAnalogOutputSignal = requireNonNull( - ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"hasAnalogOutputSignal", "c"}, clazz$BlockState) + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"hasAnalogOutputSignal", + VersionHelper.isOrAbove1_20_5() ? "c_" : "d_"}, clazz$BlockState) ); public static final Method method$BlockBehaviour$getAnalogOutputSignal = requireNonNull( diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java index 0da437e2a..f97ae5164 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java @@ -1057,7 +1057,6 @@ public final class NetworkReflections { ) ); - public static final Constructor constructor$ClientboundMoveEntityPacket$PosRot = requireNonNull( ReflectionUtils.getTheOnlyConstructor(clazz$ClientboundMoveEntityPacket$PosRot) ); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 6ef4b0cf6..5536e4322 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -100,7 +100,7 @@ public abstract class BlockBehavior { return false; } - //BlockState state Level level BlockPos pos + //BlockState state, Level level, BlockPos pos public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { return 0; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 2ab48273b..4b63deefb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -225,7 +225,8 @@ public class Dependencies { "adventure-nbt", List.of(Relocation.of("option", "net{}kyori{}option"), Relocation.of("examination", "net{}kyori{}examination"), - Relocation.of("adventure", "net{}kyori{}adventure")) + Relocation.of("adventure", "net{}kyori{}adventure")), + true ) { @Override public String getVersion() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index a740c94ff..cce78f674 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -12,11 +12,7 @@ import net.momirealms.sparrow.nbt.ListTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; public class CEChunk { public final CEWorld world; @@ -26,7 +22,7 @@ public class CEChunk { public final Map blockEntities; private volatile boolean dirty; private volatile boolean loaded; - protected final Map tickingBlockEntitiesByPos = new ConcurrentHashMap<>(); + protected final Map tickingBlockEntitiesByPos = new HashMap<>(); public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; @@ -71,6 +67,13 @@ public class CEChunk { } } + public void clearAllBlockEntities() { + this.blockEntities.values().forEach(e -> e.setValid(false)); + this.blockEntities.clear(); + this.tickingBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); + this.tickingBlockEntitiesByPos.clear(); + } + public void replaceOrCreateTickingBlockEntity(T blockEntity) { ImmutableBlockState blockState = blockEntity.blockState(); BlockEntityTicker ticker = blockState.createBlockEntityTicker(this.world, blockEntity.type()); @@ -250,6 +253,7 @@ public class CEChunk { public void unload() { if (!this.loaded) return; this.world.removeLoadedChunk(this); + this.clearAllBlockEntities(); this.loaded = false; } } From 4afa7d3b6cdc32c7688d67c4d6f80ae7f5b577be Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Thu, 4 Sep 2025 09:25:10 +0800 Subject: [PATCH 019/226] =?UTF-8?q?fix(core):=20=E9=80=82=E9=85=8D=20LobFi?= =?UTF-8?q?le=20api=20=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/LobFileHost.java | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 275521957..22a0dfc37 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.pack.host.impl; +import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; @@ -99,9 +100,9 @@ public class LobFileHost implements ResourcePackHost { public String getSpaceUsageText() { if (this.accountInfo == null) return "Usage data not available"; return String.format("Storage: %d/%d MB (%.1f%% used)", - this.accountInfo.getSpaceUsed() / 1_000_000, - this.accountInfo.getSpaceQuota() / 1_000_000, - (this.accountInfo.getSpaceUsed() * 100.0) / this.accountInfo.getSpaceQuota() + this.accountInfo.account.usage.spaceUsed / 1_000_000, + this.accountInfo.account.limits.spaceQuota / 1_000_000, + (this.accountInfo.account.usage.spaceUsed * 100.0) / this.accountInfo.account.limits.spaceQuota ); } @@ -172,7 +173,7 @@ public class LobFileHost implements ResourcePackHost { .thenApply(response -> { if (response.statusCode() == 200) { AccountInfo info = GsonHelper.get().fromJson(response.body(), AccountInfo.class); - if (info.isSuccess()) { + if (info.success) { this.accountInfo = info; return info; } @@ -278,31 +279,33 @@ public class LobFileHost implements ResourcePackHost { } } - @SuppressWarnings({"all"}) - public static class AccountInfo { - private boolean success; - private Map account_info; - private Map account_limits; - private Map account_usage; + public record AccountInfo( + boolean success, + Account account + ) {} - public String getEmail() { - return (String) this.account_info.get("email"); - } + public record Account( + Info info, + Limits limits, + Usage usage + ) {} - public int getSpaceQuota() { - return this.account_limits.getOrDefault("space_quota", 0); - } + public record Info( + String email, + String level, + @SerializedName("api_key") String apiKey, + @SerializedName("time_created") String timeCreated + ) {} - public int getSpaceUsed() { - return this.account_usage.getOrDefault("space_used", 0); - } + public record Limits( + @SerializedName("space_quota") long spaceQuota, + @SerializedName("slots_quota") long slotsQuota, + @SerializedName("max_file_size") long maxFileSize, + @SerializedName("max_file_download_speed") long maxFileDownloadSpeed + ) {} - public int getSlotsUsed() { - return this.account_usage.getOrDefault("slots_used", 0); - } - - public boolean isSuccess() { - return this.success; - } - } + public record Usage( + @SerializedName("space_used") long spaceUsed, + @SerializedName("slots_used") long slotsUsed + ) {} } \ No newline at end of file From c71fe993953a832e51070b99d6f3e0e8dad442e2 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 20:59:51 +0800 Subject: [PATCH 020/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8Ds3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../factory/ComponentItemFactory1_20_5.java | 1 + .../bukkit/util/EnchantmentUtils.java | 2 + core/build.gradle.kts | 2 +- .../core/plugin/dependency/Dependencies.java | 213 ++++++++++++++---- gradle.properties | 2 +- 5 files changed, 169 insertions(+), 51 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java index 447500af0..2231d8908 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java @@ -424,6 +424,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory getEnchantment(ComponentItemWrapper item, Key key) { Object enchant = item.getComponentExact(ComponentTypes.ENCHANTMENTS); + if (enchant == null) return Optional.empty(); try { Map map = EnchantmentUtils.toMap(enchant); Integer level = map.get(key.toString()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java index 6424b21f3..9bd42c689 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect import java.util.HashMap; import java.util.Map; +import java.util.Objects; public final class EnchantmentUtils { @@ -11,6 +12,7 @@ public final class EnchantmentUtils { @SuppressWarnings("unchecked") public static Map toMap(Object itemEnchantments) throws ReflectiveOperationException { + if (itemEnchantments == null) return Map.of(); Map map = new HashMap<>(); Map enchantments = (Map) CoreReflections.field$ItemEnchantments$enchantments.get(itemEnchantments); diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 49f6057f5..0694d2128 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}") implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}") // S3 - implementation("net.momirealms:craft-engine-s3:0.4") + implementation("net.momirealms:craft-engine-s3:0.5") // Util compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") // Adventure diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 4b63deefb..5caddb618 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -161,16 +161,7 @@ public class Dependencies { "commons-imaging", "org{}apache{}commons", "commons-imaging", - List.of(Relocation.of("commons", "org{}apache{}commons")), - (p) -> { - try (JarFile jarFile = new JarFile(p.toFile())) { - ZipEntry entry = jarFile.getEntry("net/momirealms/craftengine/libraries/commons/imaging/Imaging.class"); - return entry != null; - } catch (IOException e) { - CraftEngine.instance().logger().warn("Error reading jar file", e); - return false; - } - } + List.of(Relocation.of("commons", "org{}apache{}commons")) ); public static final Dependency BYTE_BUDDY = new Dependency( @@ -385,13 +376,22 @@ public class Dependencies { List.of(Relocation.of("evalex", "com{}ezylang{}evalex")) ); + public static final Dependency JIMFS = new Dependency( + "jimfs", + "com{}google{}jimfs", + "jimfs", + List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) + ); + public static final Dependency NETTY_HTTP = new Dependency( "netty-codec-http", "io{}netty", "netty-codec-http", - List.of(Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + List.of( + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), - Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy")) + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") + ) ); public static final Dependency NETTY_HTTP2 = new Dependency( @@ -405,14 +405,13 @@ public class Dependencies { "reactive-streams", "org{}reactivestreams", "reactive-streams", - List.of(Relocation.of("reactivestreams", "org{}reactivestreams")) - ); - - public static final Dependency JIMFS = new Dependency( - "jimfs", - "com{}google{}jimfs", - "jimfs", - List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) + List.of( + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") + ) ); public static final Dependency AMAZON_AWSSDK_S3 = new Dependency( @@ -421,7 +420,11 @@ public class Dependencies { "s3", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ); @@ -431,7 +434,11 @@ public class Dependencies { "netty-nio-client", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -446,7 +453,11 @@ public class Dependencies { "sdk-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -461,7 +472,11 @@ public class Dependencies { "auth", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -476,7 +491,11 @@ public class Dependencies { "regions", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -491,7 +510,11 @@ public class Dependencies { "identity-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -506,7 +529,11 @@ public class Dependencies { "http-client-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -521,7 +548,11 @@ public class Dependencies { "protocol-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -536,7 +567,11 @@ public class Dependencies { "aws-xml-protocol", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -551,7 +586,11 @@ public class Dependencies { "json-utils", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -566,7 +605,11 @@ public class Dependencies { "aws-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -581,7 +624,11 @@ public class Dependencies { "utils", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -596,7 +643,11 @@ public class Dependencies { "annotations", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -611,7 +662,11 @@ public class Dependencies { "crt-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -626,7 +681,11 @@ public class Dependencies { "checksums", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -641,7 +700,11 @@ public class Dependencies { "eventstream", List.of( Relocation.of("eventstream", "software{}amazon{}eventstream"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ); @@ -651,7 +714,11 @@ public class Dependencies { "profiles", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -666,7 +733,11 @@ public class Dependencies { "retries", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -681,7 +752,11 @@ public class Dependencies { "endpoints-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -696,7 +771,11 @@ public class Dependencies { "arns", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -711,7 +790,11 @@ public class Dependencies { "aws-query-protocol", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -726,7 +809,11 @@ public class Dependencies { "http-auth-aws", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -741,7 +828,11 @@ public class Dependencies { "http-auth-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -756,7 +847,11 @@ public class Dependencies { "http-auth", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -771,7 +866,11 @@ public class Dependencies { "http-auth-aws-eventstream", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -786,7 +885,11 @@ public class Dependencies { "checksums-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -801,7 +904,11 @@ public class Dependencies { "retries-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -816,7 +923,11 @@ public class Dependencies { "metrics-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override @@ -831,7 +942,11 @@ public class Dependencies { "third-party-jackson-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), - Relocation.of("reactivestreams", "org{}reactivestreams") + Relocation.of("reactivestreams", "org{}reactivestreams"), + Relocation.of("netty{}handler{}codec{}http2", "io{}netty{}handler{}codec{}http2"), + Relocation.of("netty{}handler{}codec{}http", "io{}netty{}handler{}codec{}http"), + Relocation.of("netty{}handler{}codec{}rtsp", "io{}netty{}handler{}codec{}rtsp"), + Relocation.of("netty{}handler{}codec{}spdy", "io{}netty{}handler{}codec{}spdy") ) ) { @Override diff --git a/gradle.properties b/gradle.properties index 3704d79f4..44d8cc6a9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.10 +project_version=0.0.62.12 config_version=45 lang_version=25 project_group=net.momirealms From 899f6bbe78cb67ba1ac61e183ba515dc6b40c821 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 4 Sep 2025 21:01:57 +0800 Subject: [PATCH 021/226] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 44d8cc6a9..09b0c867e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ anti_grief_version=0.20 nms_helper_version=1.0.74 evalex_version=3.5.0 reactive_streams_version=1.0.4 -amazon_awssdk_version=2.31.23 +amazon_awssdk_version=2.33.1 amazon_awssdk_eventstream_version=1.0.1 jimfs_version=1.3.0 authlib_version=6.0.58 From af20ddb38701da14d4372f7fada0fe536b16eb93 Mon Sep 17 00:00:00 2001 From: tamashii Date: Thu, 4 Sep 2025 19:43:55 +0200 Subject: [PATCH 022/226] Add de language to i18n.yml --- .../resources/internal/configuration/i18n.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/common-files/src/main/resources/resources/internal/configuration/i18n.yml b/common-files/src/main/resources/resources/internal/configuration/i18n.yml index f338e11d7..febd3d010 100644 --- a/common-files/src/main/resources/resources/internal/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/internal/configuration/i18n.yml @@ -24,4 +24,17 @@ i18n: internal.get_item.1: 右键单击取一组 internal.cooking_info: 配方信息 internal.cooking_info.0: '时间: 刻' - internal.cooking_info.1: '经验: ' \ No newline at end of file + internal.cooking_info.1: '经验: ' + de: + internal.next_page: Nächste Seite + internal.previous_page: Vorherige Seite + internal.return: Zurück zur vorherigen Seite + internal.exit: Verlassen + internal.next_recipe: Nächstes Rezept + internal.previous_recipe: Vorheriges Rezept + internal.get_item: Gegenstand nehmen + internal.get_item.0: Linksklick, um eines zu nehmen + internal.get_item.1: Rechtsklick, um einen Stapel zu nehmen + internal.cooking_info: Rezeptinformationen + internal.cooking_info.0: 'Zeit: Ticks' + internal.cooking_info.1: 'Erfahrung: ' \ No newline at end of file From c33d18faf104ec1d307e0992e9ff5b642bc85e5a Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 5 Sep 2025 02:03:12 +0800 Subject: [PATCH 023/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=9D=E9=99=A9?= =?UTF-8?q?=E6=9F=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../default/configuration/block_name.yml | 6 +- .../default/configuration/blocks.yml | 73 ++++++++++++++++++ .../default/configuration/categories.yml | 3 +- .../resources/default/configuration/i18n.yml | 2 + .../block/custom/safe_block_bottom.png | Bin 0 -> 232 bytes .../block/custom/safe_block_front.png | Bin 0 -> 397 bytes .../textures/block/custom/safe_block_side.png | Bin 0 -> 287 bytes .../textures/block/custom/safe_block_top.png | Bin 0 -> 268 bytes .../core/pack/AbstractPackManager.java | 4 + 9 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_bottom.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_top.png diff --git a/common-files/src/main/resources/resources/default/configuration/block_name.yml b/common-files/src/main/resources/resources/default/configuration/block_name.yml index 0b89fc743..f097abe8a 100644 --- a/common-files/src/main/resources/resources/default/configuration/block_name.yml +++ b/common-files/src/main/resources/resources/default/configuration/block_name.yml @@ -26,7 +26,8 @@ lang: block_name:default:gunpowder_block: GunPowder Block block_name:default:solid_gunpowder_block: Solid GunPowder Block block_name:default:copper_coil: Copper Coil - block_name:default:chessboard: Chessboard + block_name:default:chessboard_block: Chessboard Block + block_name:default:safe_block: Safe Block zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -51,4 +52,5 @@ lang: block_name:default:gunpowder_block: 火药粉末 block_name:default:solid_gunpowder_block: 凝固火药块 block_name:default:copper_coil: 铜线圈 - block_name:default:chessboard: 棋盘 \ No newline at end of file + block_name:default:chessboard_block: 棋盘方块 + block_name:default:safe_block: 保险柜 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 530b5f9b9..746bf00b4 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -435,6 +435,79 @@ items#misc: facing=west: appearance: west id: 21 + default:safe_block: + material: nether_brick + custom-model-data: 3007 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/safe_block + generation: + parent: minecraft:block/custom/safe_block + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 5 + resistance: 5 + is-suffocating: true + is-redstone-conductor: true + push-reaction: BLOCK + instrument: BASEDRUM + map-color: 6 + sounds: + break: minecraft:block.stone.break + fall: minecraft:block.stone.fall + hit: minecraft:block.stone.hit + place: minecraft:block.stone.place + step: minecraft:block.stone.step + states: + properties: + facing: + type: 4-direction + default: north + appearances: + east: + state: note_block:22 + model: + path: minecraft:block/custom/safe_block + y: 90 + generation: + parent: minecraft:block/orientable + textures: + front: minecraft:block/custom/safe_block_front + side: minecraft:block/custom/safe_block_side + top: minecraft:block/custom/safe_block_top + north: + state: note_block:23 + model: + path: minecraft:block/custom/safe_block + south: + state: note_block:24 + model: + path: minecraft:block/custom/safe_block + y: 180 + west: + state: note_block:25 + model: + path: minecraft:block/custom/safe_block + y: 270 + variants: + facing=east: + appearance: east + id: 22 + facing=north: + appearance: north + id: 23 + facing=south: + appearance: south + id: 24 + facing=west: + appearance: west + id: 25 recipes#misc: default:chinese_lantern: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 79c4e3fa9..abe2f99fd 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -78,4 +78,5 @@ categories: - default:flame_elytra - default:cap - default:pebble - - default:chessboard_block \ No newline at end of file + - default:chessboard_block + - default:safe_block \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index a2cfc0ddc..cf3e1cb2e 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -46,6 +46,7 @@ i18n: item.cap: Cap item.flower_basket: Flower Basket item.chessboard_block: Chessboard Block + item.safe_block: Safe Block category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -102,6 +103,7 @@ i18n: item.cap: 鸭舌帽 item.flower_basket: 花篮 item.chessboard_block: 棋盘方块 + item.safe_block: 保险柜 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_bottom.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b03c170465a630a37f514667cdad8890a2df89 GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|7J0fjhFJ9W zPTI)VY{26Zo1%DJ_=ELSt1tieFMXS`#z)p~lLCwLGL2o;&tpDS?7QcZvO3({Dt_?_ zF-4^|DfdoBBi%y*&wp@iyt;E&qwQJmMgMB;AAI^~QZaE)>PNXN&oUT{ZEf f;9T`1-~TbQhPx$M@d9MR5*==kxffNQ4ogTJGXcD006|6_C14brT{eRHF->?vz1BdR8pK$ zxuV3=#$>XdHhNuSChi5F^9cZsb7$hL+$y%DjqU{ikO1HpihkZ_;&mDp{}Zt#CLTu7 zcx1OKjx8wwAMah+s!^|LW0UwtMPfK05M=%_QSBhA=^b26S?YjH4+8DM9MD|MZR$O re{5mu5>)5yRoJ^WD{VKKmUI0AtqY~`+Mh!R00000NkvXXu0mjfM|-Hp literal 0 HcmV?d00001 diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png new file mode 100644 index 0000000000000000000000000000000000000000..c345df934fe434c66cac36cd105b3a52a488604c GIT binary patch literal 287 zcmV+)0pR|LP)Px#*-1n}R5*=|lD`guFc8K+8e)(T+B(TAI2d=08ehuE#V6Cm=g>I81T7&*m>do( z;ZOCZ-u3sT-}TNpp3VROq?wvYfnId#E`Zzh(&q+av)j9L;z9(Ww4g;foX)lZGVm{$6KOIm5xPAuHlQjur$%|R$F z8iO$abyb>@g(j>STpTA+G`DEtIB6`={OYQFKJM0rsTt)imMdzjvl?beKYO78aKWAR l_CSA37RO@Z`To=c@CHX6cL@gP8$s@$%xvug2tyiR`2x2wIU<0L;PCM7C~dF#w#ZP7X2yjhdgxwPx|&AjlnxhhJk_kPaq+xndP+V8^p*iFVdQ&MBb@0F=aLxBvhE literal 0 HcmV?d00001 diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index f68ae92f7..80831f27b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -422,6 +422,10 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on_side.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chessboard_block.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_top.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_bottom.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front.png"); // items plugin.saveResource("resources/default/configuration/items.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod.png"); From 8931433bb1c4bfaa4d27b48586555516bcacff28 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 5 Sep 2025 07:06:30 +0800 Subject: [PATCH 024/226] =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=933?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../behavior/SimpleStorageBlockBehavior.java | 186 ++++++++++++++++ .../block/entity/BlockEntityHolder.java | 28 +++ .../block/entity/BukkitBlockEntityTypes.java | 9 + .../entity/SimpleStorageBlockEntity.java | 205 ++++++++++++++++++ .../bukkit/plugin/gui/BukkitGuiManager.java | 32 ++- ...yHolder.java => CraftEngineGUIHolder.java} | 4 +- .../plugin/injector/BlockGenerator.java | 1 - .../reflection/minecraft/CoreReflections.java | 4 +- .../plugin/user/BukkitServerPlayer.java | 28 ++- .../bukkit/util/EnchantmentUtils.java | 1 - .../craftengine/bukkit/world/BukkitWorld.java | 5 + .../default/configuration/blocks.yml | 69 +++++- .../block/custom/safe_block_front_open.png | Bin 0 -> 368 bytes .../block/behavior/EntityBlockBehavior.java | 2 + .../block/entity/BlockEntityTypeKeys.java | 9 + .../core/block/entity/BlockEntityTypes.java | 12 + .../core/pack/AbstractPackManager.java | 1 + .../core/plugin/dependency/Dependencies.java | 4 - .../craftengine/core/util/MCUtils.java | 5 + .../craftengine/core/world/World.java | 7 + .../craftengine/core/world/chunk/CEChunk.java | 14 +- .../core/world/collision/AABB.java | 13 ++ gradle.properties | 4 +- 24 files changed, 612 insertions(+), 33 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BlockEntityHolder.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/{CraftEngineInventoryHolder.java => CraftEngineGUIHolder.java} (86%) create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front_open.png create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index d7b4b9b0d..0705b6c10 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -28,6 +28,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key PRESSURE_PLATE_BLOCK = Key.from("craftengine:pressure_plate_block"); public static final Key DOUBLE_HIGH_BLOCK = Key.from("craftengine:double_high_block"); public static final Key CHANGE_OVER_TIME_BLOCK = Key.from("craftengine:change_over_time_block"); + public static final Key SIMPLE_STORAGE_BLOCK = Key.from("craftengine:simple_storage_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -54,5 +55,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY); register(DOUBLE_HIGH_BLOCK, DoubleHighBlockBehavior.FACTORY); register(CHANGE_OVER_TIME_BLOCK, ChangeOverTimeBlockBehavior.FACTORY); + register(SIMPLE_STORAGE_BLOCK, SimpleStorageBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java new file mode 100644 index 000000000..65ae93bad --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -0,0 +1,186 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; +import net.momirealms.craftengine.bukkit.block.entity.SimpleStorageBlockEntity; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.gui.BukkitInventory; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final String containerTitle; + private final int rows; + private final SoundData openSound; + private final SoundData closeSound; + private final boolean hasAnalogOutputSignal; + @Nullable + private final Property openProperty; + + public SimpleStorageBlockBehavior(CustomBlock customBlock, + String containerTitle, + int rows, + SoundData openSound, + SoundData closeSound, + boolean hasAnalogOutputSignal, + Property openProperty) { + super(customBlock); + this.containerTitle = containerTitle; + this.rows = rows; + this.openSound = openSound; + this.closeSound = closeSound; + this.hasAnalogOutputSignal = hasAnalogOutputSignal; + this.openProperty = openProperty; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + CEWorld world = context.getLevel().storageWorld(); + net.momirealms.craftengine.core.entity.player.Player player = context.getPlayer(); + BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos()); + if (player != null && blockEntity instanceof SimpleStorageBlockEntity entity) { + Player bukkitPlayer = (Player) player.platformPlayer(); + Optional.ofNullable(entity.inventory()).ifPresent(inventory -> { + entity.onPlayerOpen(player); + bukkitPlayer.openInventory(inventory); + new BukkitInventory(inventory).open(player, AdventureHelper.miniMessage().deserialize(this.containerTitle, PlayerOptionalContext.of(player).tagResolvers())); + }); + } + return InteractionResult.SUCCESS_AND_CANCEL; + } + + // 1.21.5+ + @Override + public void affectNeighborsAfterRemoval(Object thisBlock, Object[] args, Callable superMethod) { + Object level = args[1]; + Object pos = args[2]; + Object blockState = args[0]; + FastNMS.INSTANCE.method$Level$updateNeighbourForOutputSignal(level, pos, BlockStateUtils.getBlockOwner(blockState)); + } + + @Override + public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object world = args[1]; + Object blockPos = args[2]; + BlockPos pos = LocationUtils.fromBlockPos(blockPos); + World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(world); + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(bukkitWorld.getUID()); + BlockEntity blockEntity = ceWorld.getBlockEntityAtIfLoaded(pos); + if (blockEntity instanceof SimpleStorageBlockEntity entity) { + entity.checkOpeners(world, blockPos, args[0]); + } + } + + @SuppressWarnings("unchecked") + @Override + public BlockEntityType blockEntityType() { + return (BlockEntityType) BukkitBlockEntityTypes.SIMPLE_STORAGE; + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { + return new SimpleStorageBlockEntity(pos, state); + } + + @NotNull + public String containerTitle() { + return this.containerTitle; + } + + @Nullable + public SoundData closeSound() { + return this.closeSound; + } + + @Nullable + public SoundData openSound() { + return this.openSound; + } + + public int rows() { + return this.rows; + } + + public @Nullable Property openProperty() { + return openProperty; + } + + @Override + public int getAnalogOutputSignal(Object thisBlock, Object[] args) { + if (!this.hasAnalogOutputSignal) return 0; + Object world = args[1]; + Object blockPos = args[2]; + BlockPos pos = LocationUtils.fromBlockPos(blockPos); + World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(world); + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(bukkitWorld.getUID()); + BlockEntity blockEntity = ceWorld.getBlockEntityAtIfLoaded(pos); + if (blockEntity instanceof SimpleStorageBlockEntity entity) { + Inventory inventory = entity.inventory(); + if (inventory != null) { + float signal = 0.0F; + for (int i = 0; i < inventory.getSize(); i++) { + ItemStack item = inventory.getItem(i); + if (item != null) { + signal += (float) item.getAmount() / (float) (Math.min(inventory.getMaxStackSize(), item.getMaxStackSize())); + } + } + signal /= (float) inventory.getSize(); + return MCUtils.lerpDiscrete(signal, 0, 15); + } + } + return 0; + } + + @Override + public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) { + return this.hasAnalogOutputSignal; + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + String title = arguments.getOrDefault("title", "").toString(); + int rows = MCUtils.clamp(ResourceConfigUtils.getAsInt(arguments.getOrDefault("rows", 1), "rows"), 1, 6); + Map sounds = (Map) arguments.get("sounds"); + boolean hasAnalogOutputSignal = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-signal", true), "has-signal"); + SoundData openSound = null; + SoundData closeSound = null; + if (sounds != null) { + openSound = Optional.ofNullable(sounds.get("open")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + closeSound = Optional.ofNullable(sounds.get("close")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + } + Property property = (Property) block.getProperty("open"); + return new SimpleStorageBlockBehavior(block, title, rows, openSound, closeSound, hasAnalogOutputSignal, property); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BlockEntityHolder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BlockEntityHolder.java new file mode 100644 index 000000000..23ec8fb4c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BlockEntityHolder.java @@ -0,0 +1,28 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; + +public class BlockEntityHolder implements InventoryHolder { + private final BlockEntity blockEntity; + private Inventory inventory; + + public BlockEntityHolder(BlockEntity entity) { + this.blockEntity = entity; + } + + public BlockEntity blockEntity() { + return blockEntity; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + @Override + public @NotNull Inventory getInventory() { + return this.inventory; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java new file mode 100644 index 000000000..0385b0b32 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -0,0 +1,9 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.BlockEntityTypeKeys; +import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; + +public class BukkitBlockEntityTypes extends BlockEntityTypes { + public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java new file mode 100644 index 000000000..b9ec60521 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -0,0 +1,205 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.block.behavior.SimpleStorageBlockBehavior; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.ListTag; +import org.bukkit.Bukkit; +import org.bukkit.GameEvent; +import org.bukkit.GameMode; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public class SimpleStorageBlockEntity extends BlockEntity { + private final SimpleStorageBlockBehavior behavior; + private final Inventory inventory; + private double maxInteractionDistance; + private boolean openState = false; + + public SimpleStorageBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.SIMPLE_STORAGE, pos, blockState); + this.behavior = super.blockState.behavior().getAs(SimpleStorageBlockBehavior.class).orElseThrow(); + BlockEntityHolder holder = new BlockEntityHolder(this); + this.inventory = Bukkit.createInventory(holder, this.behavior.rows() * 9); + } + + @Override + protected void saveCustomData(CompoundTag tag) { + // 保存前先把所有打开此容器的玩家界面关闭 + this.inventory.close(); + ListTag itemsTag = new ListTag(); + @Nullable ItemStack[] storageContents = this.inventory.getStorageContents(); + for (int i = 0; i < storageContents.length; i++) { + if (storageContents[i] != null) { + int slot = i; + CoreReflections.instance$ItemStack$CODEC.encodeStart(MRegistryOps.SPARROW_NBT, FastNMS.INSTANCE.field$CraftItemStack$handle(storageContents[i])) + .ifSuccess(success -> { + CompoundTag itemTag = (CompoundTag) success; + itemTag.putInt("slot", slot); + itemsTag.add(itemTag); + }) + .ifError(error -> CraftEngine.instance().logger().severe("Error while saving storage item: " + error)); + } + } + tag.put("items", itemsTag); + } + + @Override + public void loadCustomData(CompoundTag tag) { + ListTag itemsTag = Optional.ofNullable(tag.getList("items")).orElseGet(ListTag::new); + for (int i = 0; i < itemsTag.size(); i++) { + CompoundTag itemTag = itemsTag.getCompound(i); + int slot = itemTag.getInt("slot"); + if (slot < 0 || slot >= this.behavior.rows() * 9) { + continue; + } + CoreReflections.instance$ItemStack$CODEC.parse(MRegistryOps.SPARROW_NBT, itemTag) + .resultOrPartial((s) -> CraftEngine.instance().logger().severe("Tried to load invalid item: '" + itemTag + "'. " + s)) + .ifPresent(nmsStack -> this.inventory.setItem(slot, FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsStack))); + } + } + + public Inventory inventory() { + if (!isValid()) return null; + return this.inventory; + } + + public void onPlayerOpen(Player player) { + if (!isValidContainer()) return; + if (!player.isSpectatorMode()) { + // 有非观察者的人,那么就不触发开启音效和事件 + if (!hasNoViewer(this.inventory.getViewers())) return; + this.maxInteractionDistance = Math.max(player.getCachedInteractionRange(), this.maxInteractionDistance); + this.setOpen(player); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(super.world.world().serverWorld(), LocationUtils.toBlockPos(this.pos), BlockStateUtils.getBlockOwner(this.blockState.customBlockState().literalObject()), 5); + } + } + + public void onPlayerClose(Player player) { + if (!isValidContainer()) return; + if (!player.isSpectatorMode()) { + // 有非观察者的人,那么就不触发关闭音效和事件 + for (HumanEntity viewer : this.inventory.getViewers()) { + if (viewer.getGameMode() == GameMode.SPECTATOR || viewer == player.platformPlayer()) { + continue; + } + return; + } + this.maxInteractionDistance = 0; + this.setClose(player); + } + } + + private void setOpen(@Nullable Player player) { + this.updateOpenBlockState(true); + org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld(); + if (player != null) { + bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_OPEN, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); + } else { + bukkitWorld.sendGameEvent(null, GameEvent.CONTAINER_OPEN, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); + } + this.openState = true; + SoundData soundData = this.behavior.openSound(); + if (soundData != null) { + super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData); + } + } + + private void setClose(@Nullable Player player) { + this.updateOpenBlockState(false); + org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld(); + if (player != null) { + bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_CLOSE, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); + } else { + bukkitWorld.sendGameEvent(null, GameEvent.CONTAINER_CLOSE, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); + } + this.openState = false; + SoundData soundData = this.behavior.closeSound(); + if (soundData != null) { + super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData); + } + } + + private boolean hasNoViewer(List viewers) { + for (HumanEntity viewer : viewers) { + if (viewer.getGameMode() != GameMode.SPECTATOR) { + return false; + } + } + return true; + } + + private boolean isValidContainer() { + return this.isValid() && this.inventory != null && this.behavior != null; + } + + public void updateOpenBlockState(boolean open) { + ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos); + if (state == null || state.behavior() != this.behavior) return; + Property property = this.behavior.openProperty(); + if (property == null) return; + super.world.world().setBlockAt(this.pos.x(), this.pos.y(), this.pos.z(), state.with(property, open), UpdateOption.UPDATE_ALL.flags()); + } + + public void checkOpeners(Object level, Object pos, Object blockState) { + if (!this.isValidContainer()) return; + double maxInteractionDistance = 0d; + List viewers = this.inventory.getViewers(); + int validViewers = 0; + for (HumanEntity viewer : viewers) { + if (viewer instanceof org.bukkit.entity.Player player) { + maxInteractionDistance = Math.max(BukkitAdaptors.adapt(player).getCachedInteractionRange(), maxInteractionDistance); + if (player.getGameMode() != GameMode.SPECTATOR) { + validViewers++; + } + } + } + boolean shouldOpen = validViewers != 0; + if (shouldOpen && !this.openState) { + this.setOpen(null); + } else if (!shouldOpen && this.openState) { + this.setClose(null); + } + + this.maxInteractionDistance = maxInteractionDistance; + if (!viewers.isEmpty()) { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(blockState), 5); + } + } + + @Override + public void preRemove() { + this.inventory.close(); + Vec3d pos = Vec3d.atCenterOf(this.pos); + for (ItemStack stack : this.inventory.getContents()) { + if (stack != null) { + super.world.world().dropItemNaturally(pos, BukkitItemManager.instance().wrap(stack)); + } + } + this.inventory.clear(); + } + + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index 26b31cebc..afef50d62 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.bukkit.plugin.gui; import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder; +import net.momirealms.craftengine.bukkit.block.entity.SimpleStorageBlockEntity; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; @@ -18,16 +20,19 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.MenuType; public class BukkitGuiManager implements GuiManager, Listener { private static final boolean useNewOpenInventory = ReflectionUtils.getDeclaredMethod(InventoryView.class, void.class, new String[]{"open"}) != null; + private static BukkitGuiManager instance; private final BukkitCraftEngine plugin; public BukkitGuiManager(BukkitCraftEngine plugin) { this.plugin = plugin; + instance = this; } @Override @@ -83,7 +88,7 @@ public class BukkitGuiManager implements GuiManager, Listener { @Override public Inventory createInventory(Gui gui, int size) { - CraftEngineInventoryHolder holder = new CraftEngineInventoryHolder(gui); + CraftEngineGUIHolder holder = new CraftEngineGUIHolder(gui); org.bukkit.inventory.Inventory inventory = Bukkit.createInventory(holder, size); holder.holder().bindValue(inventory); return new BukkitInventory(inventory); @@ -95,10 +100,10 @@ public class BukkitGuiManager implements GuiManager, Listener { if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { return; } - if (!(inventory.getHolder() instanceof CraftEngineInventoryHolder craftEngineInventoryHolder)) { + if (!(inventory.getHolder() instanceof CraftEngineGUIHolder craftEngineGUIHolder)) { return; } - AbstractGui gui = (AbstractGui) craftEngineInventoryHolder.gui(); + AbstractGui gui = (AbstractGui) craftEngineGUIHolder.gui(); Player player = (Player) event.getWhoClicked(); if (event.getClickedInventory() == player.getInventory()) { gui.handleInventoryClick(new BukkitClick(event, gui, new BukkitInventory(player.getInventory()))); @@ -113,7 +118,7 @@ public class BukkitGuiManager implements GuiManager, Listener { if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { return; } - if (!(inventory.getHolder() instanceof CraftEngineInventoryHolder)) { + if (!(inventory.getHolder() instanceof CraftEngineGUIHolder)) { return; } for (int raw : event.getRawSlots()) { @@ -123,4 +128,23 @@ public class BukkitGuiManager implements GuiManager, Listener { } } } + + // 处理自定义容器的关闭音效 + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onInventoryClose(InventoryCloseEvent event) { + org.bukkit.inventory.Inventory inventory = event.getInventory(); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { + return; + } + if (!(inventory.getHolder() instanceof BlockEntityHolder holder)) { + return; + } + if (event.getPlayer() instanceof Player player && holder.blockEntity() instanceof SimpleStorageBlockEntity simpleStorageBlockEntity) { + simpleStorageBlockEntity.onPlayerClose(this.plugin.adapt(player)); + } + } + + public static BukkitGuiManager instance() { + return instance; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/CraftEngineInventoryHolder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/CraftEngineGUIHolder.java similarity index 86% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/CraftEngineInventoryHolder.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/CraftEngineGUIHolder.java index 50938e21b..a35679a4b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/CraftEngineInventoryHolder.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/CraftEngineGUIHolder.java @@ -6,11 +6,11 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.NotNull; -public class CraftEngineInventoryHolder implements InventoryHolder { +public class CraftEngineGUIHolder implements InventoryHolder { private final ObjectHolder inventory; private final Gui gui; - public CraftEngineInventoryHolder(Gui gui) { + public CraftEngineGUIHolder(Gui gui) { this.inventory = new ObjectHolder<>(); this.gui = gui; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index 85ba77e5b..b4d14745b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -15,7 +15,6 @@ import net.bytebuddy.implementation.bind.annotation.This; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.block.BukkitBlockShape; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator.*; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 600e1505b..dac330b75 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -3671,11 +3671,11 @@ public final class CoreReflections { // 1.20.5+ public static final Field field$ItemStack$CODEC = ReflectionUtils.getDeclaredField(clazz$ItemStack, "CODEC", "b"); - public static final Codec instance$ItemStack$CODEC; + public static final Codec instance$ItemStack$CODEC; static { try { - instance$ItemStack$CODEC = VersionHelper.isOrAbove1_20_5() ? (Codec) field$ItemStack$CODEC.get(null) : null; + instance$ItemStack$CODEC = VersionHelper.isOrAbove1_20_5() ? (Codec) field$ItemStack$CODEC.get(null) : null; } catch (ReflectiveOperationException e) { throw new ReflectionInitException("Failed to init ItemStack$CODEC", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index c448b31cd..e62760b77 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -7,10 +7,12 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; +import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder; +import net.momirealms.craftengine.bukkit.block.entity.SimpleStorageBlockEntity; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.gui.CraftEngineInventoryHolder; +import net.momirealms.craftengine.bukkit.plugin.gui.CraftEngineGUIHolder; import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; @@ -21,6 +23,7 @@ import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.Player; @@ -36,13 +39,16 @@ import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldEvents; import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; +import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; @@ -467,7 +473,7 @@ public class BukkitServerPlayer extends Player { } else { this.gameTicks = FastNMS.INSTANCE.field$MinecraftServer$currentTick(); } - if (this.gameTicks % 30 == 0) { + if (this.gameTicks % 20 == 0) { this.updateGUI(); } if (this.isDestroyingBlock) { @@ -491,11 +497,27 @@ public class BukkitServerPlayer extends Player { if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(top))) { return; } - if (top.getHolder() instanceof CraftEngineInventoryHolder holder) { + if (top.getHolder() instanceof CraftEngineGUIHolder holder) { holder.gui().onTimer(); + } else if (top.getHolder() instanceof BlockEntityHolder holder) { + BlockEntity blockEntity = holder.blockEntity(); + BlockPos blockPos = blockEntity.pos(); + if (!canInteractWithBlock(blockPos, 4d)) { + platformPlayer().closeInventory(); + } } } + public boolean canInteractWithBlock(BlockPos pos, double distance) { + double d = this.getCachedInteractionRange() + distance; + return (new AABB(pos)).distanceToSqr(this.getEyePosition()) < d * d; + } + + public final Vec3d getEyePosition() { + Location eyeLocation = this.platformPlayer().getEyeLocation(); + return new Vec3d(eyeLocation.getX(), eyeLocation.getY(), eyeLocation.getZ()); + } + @Override public float getDestroyProgress(Object blockState, BlockPos pos) { Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java index 9bd42c689..2a6bd2117 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EnchantmentUtils.java @@ -4,7 +4,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect import java.util.HashMap; import java.util.Map; -import java.util.Objects; public final class EnchantmentUtils { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index bfb04f654..db9d707bf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -123,4 +123,9 @@ public class BukkitWorld implements World { public void levelEvent(int id, BlockPos pos, int data) { FastNMS.INSTANCE.method$LevelAccessor$levelEvent(serverWorld(), id, LocationUtils.toBlockPos(pos), data); } + + @Override + public CEWorld storageWorld() { + return BukkitWorldManager.instance().getWorld(uuid()); + } } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 746bf00b4..573ed098d 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -464,11 +464,21 @@ items#misc: hit: minecraft:block.stone.hit place: minecraft:block.stone.place step: minecraft:block.stone.step + behavior: + type: simple_storage_block + title: "" + rows: 1 + sounds: + open: minecraft:block.iron_trapdoor.open + close: minecraft:block.iron_trapdoor.close states: properties: facing: type: 4-direction default: north + open: + type: boolean + default: false appearances: east: state: note_block:22 @@ -481,33 +491,70 @@ items#misc: front: minecraft:block/custom/safe_block_front side: minecraft:block/custom/safe_block_side top: minecraft:block/custom/safe_block_top - north: + east_open: state: note_block:23 model: - path: minecraft:block/custom/safe_block - south: + path: minecraft:block/custom/safe_block_open + y: 90 + generation: + parent: minecraft:block/orientable + textures: + front: minecraft:block/custom/safe_block_front_open + side: minecraft:block/custom/safe_block_side + top: minecraft:block/custom/safe_block_top + north: state: note_block:24 model: path: minecraft:block/custom/safe_block + north_open: + state: note_block:25 + model: + path: minecraft:block/custom/safe_block_open + south: + state: note_block:26 + model: + path: minecraft:block/custom/safe_block + y: 180 + south_open: + state: note_block:27 + model: + path: minecraft:block/custom/safe_block_open y: 180 west: - state: note_block:25 + state: note_block:28 model: path: minecraft:block/custom/safe_block y: 270 + west_open: + state: note_block:29 + model: + path: minecraft:block/custom/safe_block_open + y: 270 variants: - facing=east: + facing=east,open=false: appearance: east id: 22 - facing=north: - appearance: north + facing=east,open=true: + appearance: east_open id: 23 - facing=south: - appearance: south + facing=north,open=false: + appearance: north id: 24 - facing=west: - appearance: west + facing=north,open=true: + appearance: north_open id: 25 + facing=south,open=false: + appearance: south + id: 26 + facing=south,open=true: + appearance: south_open + id: 27 + facing=west,open=false: + appearance: west + id: 28 + facing=west,open=true: + appearance: west_open + id: 29 recipes#misc: default:chinese_lantern: type: shaped diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front_open.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front_open.png new file mode 100644 index 0000000000000000000000000000000000000000..50cd8f450d7ee4bd1d966887dbccad282d55f1fd GIT binary patch literal 368 zcmV-$0gwKPP)Px$DoI2^R5*==l21!QK@`P*4i7OA=RI8p{n@sNmQhh5{h`)v>Wj2$(}$@|OWQ_) zri6qQ*cXr%f^TL-)LgZ=@sQN%uI`*U_nx_TPU`aN8USKr3sJ~_5df8^M>!0JqnV`a zLPnCEz5PHk9frAlw7ROyhDoU9WXmM#phuZTJXS$Lo;OzV&xi~yI z_0OPUW4oPBa=yF0@vviK6>wiJ14qXPf7E6x$P%1&(!j3(D4_!F_7||ZRr0W%bqZKp z-vDaW8c=_Jn8%i1QvIG855E)Cuh8!q1vFl&{hkr%v-LWUJsu5ytpPY|0Uc^drb9 BlockEntityType blockEntityType(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java new file mode 100644 index 000000000..5d969518d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -0,0 +1,9 @@ +package net.momirealms.craftengine.core.block.entity; + +import net.momirealms.craftengine.core.util.Key; + +public final class BlockEntityTypeKeys { + private BlockEntityTypeKeys() {} + + public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java index 1f90b42c5..d3d0c0941 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java @@ -1,5 +1,17 @@ package net.momirealms.craftengine.core.block.entity; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.registry.Registries; +import net.momirealms.craftengine.core.registry.WritableRegistry; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; + public class BlockEntityTypes { + public static BlockEntityType register(Key id, BlockEntity.Factory factory) { + BlockEntityType type = new BlockEntityType<>(id, factory); + ((WritableRegistry>) BuiltInRegistries.BLOCK_ENTITY_TYPE) + .register(ResourceKey.create(Registries.BLOCK_ENTITY_TYPE.location(), id), type); + return type; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 80831f27b..a080cae06 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -426,6 +426,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_bottom.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front_open.png"); // items plugin.saveResource("resources/default/configuration/items.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod.png"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 5caddb618..5adccc541 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -1,13 +1,9 @@ package net.momirealms.craftengine.core.plugin.dependency; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.dependency.relocation.Relocation; -import java.io.IOException; import java.util.Collections; import java.util.List; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; public class Dependencies { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java index 95e1621f7..38cf3acdd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java @@ -31,6 +31,11 @@ public class MCUtils { return value < (double) truncated ? truncated - 1 : truncated; } + public static int lerpDiscrete(float delta, int start, int end) { + int i = end - start; + return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); + } + public static int murmurHash3Mixer(int value) { value ^= value >>> 16; value *= -2048144789; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/World.java b/core/src/main/java/net/momirealms/craftengine/core/world/World.java index a7d960454..287429f5a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/World.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/World.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.sound.SoundData; @@ -15,6 +16,8 @@ import java.util.UUID; public interface World { + CEWorld storageWorld(); + Object platformWorld(); Object serverWorld(); @@ -29,6 +32,10 @@ public interface World { void setBlockAt(int x, int y, int z, BlockStateWrapper blockState, int flags); + default void setBlockAt(int x, int y, int z, ImmutableBlockState blockState, int flags) { + this.setBlockAt(x, y, z, blockState.customBlockState(), flags); + } + String name(); Path directory(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index cce78f674..6b8acd1a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -65,11 +65,18 @@ public class CEChunk { if (removedBlockEntity != null) { removedBlockEntity.setValid(false); } + this.removeBlockEntityTicker(blockPos); } - public void clearAllBlockEntities() { + public void activateAllBlockEntities() { + for (BlockEntity blockEntity : this.blockEntities.values()) { + blockEntity.setValid(true); + replaceOrCreateTickingBlockEntity(blockEntity); + } + } + + public void deactivateAllBlockEntities() { this.blockEntities.values().forEach(e -> e.setValid(false)); - this.blockEntities.clear(); this.tickingBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); this.tickingBlockEntitiesByPos.clear(); } @@ -247,13 +254,14 @@ public class CEChunk { public void load() { if (this.loaded) return; this.world.addLoadedChunk(this); + this.activateAllBlockEntities(); this.loaded = true; } public void unload() { if (!this.loaded) return; this.world.removeLoadedChunk(this); - this.clearAllBlockEntities(); + this.deactivateAllBlockEntities(); this.loaded = false; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java index 931692fef..8121e3b64 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.core.world.collision; import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.EntityHitResult; import net.momirealms.craftengine.core.world.Vec3d; import org.jetbrains.annotations.Nullable; @@ -34,6 +36,17 @@ public class AABB { this.maxZ = Math.max(pos1.z, pos2.z); } + public AABB(BlockPos pos) { + this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1); + } + + public double distanceToSqr(Vec3d vec) { + double x = Math.max(Math.max(this.minX - vec.x, vec.x - this.maxX), 0.0F); + double y = Math.max(Math.max(this.minY - vec.y, vec.y - this.maxY), 0.0F); + double z = Math.max(Math.max(this.minZ - vec.z, vec.z - this.maxZ), 0.0F); + return x * x + y * y + z * z; + } + public static AABB fromInteraction(Vec3d pos, double width, double height) { return new AABB( pos.x - width / 2, diff --git a/gradle.properties b/gradle.properties index 09b0c867e..ffbcaf5cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.12 +project_version=0.0.62.13 config_version=45 lang_version=25 project_group=net.momirealms @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.20 -nms_helper_version=1.0.74 +nms_helper_version=1.0.75 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 64bfef7aa2ad69e9e3ac8ce7c6aacbe7582bda2c Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 5 Sep 2025 14:52:40 +0800 Subject: [PATCH 025/226] =?UTF-8?q?fix(core):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E6=94=BE=E7=BD=AE=E5=AE=B6=E5=85=B7=E5=87=BD=E6=95=B0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../context/function/SpawnFurnitureFunction.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java index 66b527272..917611a6e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java @@ -85,11 +85,11 @@ public class SpawnFurnitureFunction extends AbstractConditi @Override public Function create(Map arguments) { Key furnitureId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("furniture-id"), "warning.config.function.spawn_furniture.missing_furniture_id")); - NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); - NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); - NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); - NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "")); - NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "")); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "")); + NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "")); AnchorType anchorType = ResourceConfigUtils.getAsEnum(arguments.get("anchor-type"), AnchorType.class, null); boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound"); return new SpawnFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, anchorType, playSound, getPredicates(arguments)); From 4fd2455f9dcde69191cc8e2aa4a9174f48ca4d70 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 5 Sep 2025 17:36:01 +0800 Subject: [PATCH 026/226] =?UTF-8?q?fix(core):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E6=9F=93=E8=89=B2=E7=9B=94=E7=94=B2?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/reflection/minecraft/CoreReflections.java | 7 ++++--- .../core/item/equipment/ComponentBasedEquipment.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index dac330b75..8cce0ed83 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -3671,11 +3671,12 @@ public final class CoreReflections { // 1.20.5+ public static final Field field$ItemStack$CODEC = ReflectionUtils.getDeclaredField(clazz$ItemStack, "CODEC", "b"); - public static final Codec instance$ItemStack$CODEC; + public static final Codec instance$ItemStack$CODEC = getItemStack$CODEC(); - static { + @SuppressWarnings("unchecked") + private static Codec getItemStack$CODEC() { try { - instance$ItemStack$CODEC = VersionHelper.isOrAbove1_20_5() ? (Codec) field$ItemStack$CODEC.get(null) : null; + return VersionHelper.isOrAbove1_20_5() ? (Codec) field$ItemStack$CODEC.get(null) : null; } catch (ReflectiveOperationException e) { throw new ReflectionInitException("Failed to init ItemStack$CODEC", e); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java index d8d52d807..f60d497de 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java @@ -131,7 +131,7 @@ public class ComponentBasedEquipment extends AbstractEquipment implements Suppli return new DyeableData(ResourceConfigUtils.getAsInt(data.get("color-when-undyed"), "color-when-undyed")); } } - return new DyeableData(null); + return null; } @Override From 8888084196c1906f3e57e05f96fd8ce404d5fb87 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 6 Sep 2025 01:46:56 +0800 Subject: [PATCH 027/226] =?UTF-8?q?fix(core):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../context/function/SpawnFurnitureFunction.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java index 917611a6e..39a879c3e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java @@ -85,11 +85,11 @@ public class SpawnFurnitureFunction extends AbstractConditi @Override public Function create(Map arguments) { Key furnitureId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("furniture-id"), "warning.config.function.spawn_furniture.missing_furniture_id")); - NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); - NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); - NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); - NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "")); - NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "")); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "")); + NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "")); AnchorType anchorType = ResourceConfigUtils.getAsEnum(arguments.get("anchor-type"), AnchorType.class, null); boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound"); return new SpawnFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, anchorType, playSound, getPredicates(arguments)); From fcdf65dccb544a2440302cd8be58fc8978da97a8 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 6 Sep 2025 01:55:43 +0800 Subject: [PATCH 028/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=A7=A3=E6=9E=90papi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/plugin/context/GlobalVariableManager.java | 2 +- .../core/plugin/context/NetworkTextReplaceContext.java | 3 ++- .../craftengine/core/plugin/context/PlayerContext.java | 8 ++++++++ .../core/plugin/context/PlayerOptionalContext.java | 3 ++- .../core/plugin/text/minimessage/PlaceholderTag.java | 3 ++- 5 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerContext.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java index eb0b3ae25..bb2ab886d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java @@ -55,7 +55,7 @@ public class GlobalVariableManager implements Manageable { @Override public void parseObject(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Object object) throws LocalizedException { if (object != null) { - globalVariables.put(id.value(), object.toString()); + GlobalVariableManager.this.globalVariables.put(id.value(), object.toString()); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/NetworkTextReplaceContext.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/NetworkTextReplaceContext.java index 42f7c5984..1b80021c5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/NetworkTextReplaceContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/NetworkTextReplaceContext.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.plugin.text.minimessage.*; import java.util.Map; -public final class NetworkTextReplaceContext extends AbstractChainParameterContext { +public final class NetworkTextReplaceContext extends AbstractChainParameterContext implements PlayerContext { private final Player player; public NetworkTextReplaceContext(Player player) { @@ -19,6 +19,7 @@ public final class NetworkTextReplaceContext extends AbstractChainParameterConte return new NetworkTextReplaceContext(player); } + @Override public Player player() { return player; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerContext.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerContext.java new file mode 100644 index 000000000..89bc0f1fc --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerContext.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.plugin.context; + +import net.momirealms.craftengine.core.entity.player.Player; + +public interface PlayerContext { + + Player player(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java index 72667d538..f2a39186c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; -public class PlayerOptionalContext extends AbstractChainParameterContext implements Context { +public class PlayerOptionalContext extends AbstractChainParameterContext implements PlayerContext { public static final PlayerOptionalContext EMPTY = new PlayerOptionalContext(null, ContextHolder.EMPTY); protected final Player player; @@ -44,6 +44,7 @@ public class PlayerOptionalContext extends AbstractChainParameterContext impleme return new PlayerOptionalContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player))); } + @Override @Nullable public Player player() { return this.player; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java index 019d915e6..066e3216a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java @@ -6,6 +6,7 @@ import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.PlayerContext; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.util.AdventureHelper; import org.jetbrains.annotations.NotNull; @@ -26,7 +27,7 @@ public class PlaceholderTag implements TagResolver { String rawArgument = arguments.popOr("No argument relational placeholder provided").toString(); if (rawArgument.contains("<")) rawArgument = AdventureHelper.resolvePlainStringTags(rawArgument, this.context.tagResolvers()); String placeholder = "%" + rawArgument + "%"; - String parsed = this.context instanceof PlayerOptionalContext playerOptionalContext ? CraftEngine.instance().compatibilityManager().parse(playerOptionalContext.player(), placeholder) : CraftEngine.instance().compatibilityManager().parse(null, placeholder); + String parsed = this.context instanceof PlayerContext playerContext ? CraftEngine.instance().compatibilityManager().parse(playerContext.player(), placeholder) : CraftEngine.instance().compatibilityManager().parse(null, placeholder); if (parsed.equals(placeholder)) { parsed = arguments.popOr("No default papi value provided").toString(); } From 4415acecddad74b9d88a19dba705e837d6c3e45c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 6 Sep 2025 02:57:17 +0800 Subject: [PATCH 029/226] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=9E=83=E5=9C=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/item/behavior/BlockItemBehavior.java | 2 +- .../core/block/BlockEntityState.java | 17 ----------------- gradle.properties | 2 +- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/BlockEntityState.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index b6164a8ef..faf8bbc09 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -89,7 +89,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { ImmutableBlockState blockStateToPlace = getPlacementState(context, block); if (blockStateToPlace == null) { - return InteractionResult.FAIL; + return InteractionResult.PASS; } BlockPos againstPos = context.getAgainstPos(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockEntityState.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockEntityState.java deleted file mode 100644 index e6ac593f0..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockEntityState.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.momirealms.craftengine.core.block; - -import net.momirealms.sparrow.nbt.CompoundTag; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Experimental -public class BlockEntityState { - private final CompoundTag nbt; - - public BlockEntityState(CompoundTag nbt) { - this.nbt = nbt.deepClone(); - } - - public CompoundTag nbt() { - return this.nbt; - } -} diff --git a/gradle.properties b/gradle.properties index ffbcaf5cd..455403f8e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -48,7 +48,7 @@ datafixerupper_version=8.0.16 mojang_brigadier_version=1.0.18 byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 -snake_yaml_version=2.4 +snake_yaml_version=2.5 anti_grief_version=0.20 nms_helper_version=1.0.75 evalex_version=3.5.0 From a42f70b94188648b57539b74c9cb1490da698737 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 6 Sep 2025 05:57:52 +0800 Subject: [PATCH 030/226] =?UTF-8?q?=E5=AE=9D=E8=B4=9D=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=B8=B2=E6=9F=93=E9=9B=8F=E5=BD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/entity/BukkitBlockEntityTypes.java | 2 +- .../entity/SimpleStorageBlockEntity.java | 3 - .../plugin/injector/WorldStorageInjector.java | 12 +++ .../plugin/network/PacketConsumers.java | 18 ++++ .../plugin/user/BukkitServerPlayer.java | 2 - .../bukkit/world/BukkitCEWorld.java | 8 ++ .../core/block/ImmutableBlockState.java | 12 +++ .../block/entity/BlockEntityTypeKeys.java | 1 + .../entity/render/BlockEntityRenderer.java | 23 ++++ .../render/BlockEntityRendererConfig.java | 88 +++++++++++++++ .../text/minimessage/PlaceholderTag.java | 1 - .../craftengine/core/world/CEWorld.java | 9 ++ .../craftengine/core/world/chunk/CEChunk.java | 100 ++++++++++++++++-- .../DefaultBlockEntityRendererSerializer.java | 35 ++++++ .../DefaultBlockEntitySerializer.java | 7 +- .../serialization/DefaultChunkSerializer.java | 16 ++- .../core/world/collision/AABB.java | 1 - 17 files changed, 312 insertions(+), 26 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntityRendererSerializer.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java index 0385b0b32..3e778bd32 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -6,4 +6,4 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; public class BukkitBlockEntityTypes extends BlockEntityTypes { public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); -} +} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index b9ec60521..26541c363 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -24,7 +24,6 @@ import org.bukkit.GameEvent; import org.bukkit.GameMode; import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -200,6 +199,4 @@ public class SimpleStorageBlockEntity extends BlockEntity { } this.inventory.clear(); } - - } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 81fdda00b..50fafbd48 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -237,6 +237,10 @@ public final class WorldStorageInjector { chunk.removeBlockEntity(pos); } } + if (previous.hasBlockEntityRenderer()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + chunk.removeBlockEntityRenderer(pos); + } if (Config.enableLightSystem()) { // 自定义块到原版块,只需要判断旧块是否和客户端一直 BlockStateWrapper wrapper = previous.vanillaBlockState(); @@ -266,6 +270,10 @@ public final class WorldStorageInjector { chunk.removeBlockEntity(pos); } } + if (previousImmutableBlockState.hasBlockEntityRenderer()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + chunk.removeBlockEntityRenderer(pos); + } } if (newImmutableBlockState.hasBlockEntity()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); @@ -285,6 +293,10 @@ public final class WorldStorageInjector { chunk.replaceOrCreateTickingBlockEntity(blockEntity); } } + if (newImmutableBlockState.hasBlockEntityRenderer()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + chunk.addBlockEntityRenderer(pos, newImmutableBlockState); + } // 如果新方块的光照属性和客户端认为的不同 if (Config.enableLightSystem()) { if (previousImmutableBlockState.isEmpty()) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 740564e0a..cd2cb926b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -37,6 +37,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityType import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.*; +import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -64,6 +65,7 @@ import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; @@ -261,14 +263,24 @@ public class PacketConsumers { public static final BiConsumer FORGET_LEVEL_CHUNK = (user, event) -> { try { + BukkitServerPlayer player = (BukkitServerPlayer) user; FriendlyByteBuf buf = event.getBuffer(); + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); if (VersionHelper.isOrAbove1_20_2()) { long chunkPos = buf.readLong(); user.removeTrackedChunk(chunkPos); + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos); + if (ceChunk != null) { + ceChunk.despawnBlockEntities(player); + } } else { int x = buf.readInt(); int y = buf.readInt(); user.removeTrackedChunk(ChunkPos.asLong(x, y)); + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(x, y); + if (ceChunk != null) { + ceChunk.despawnBlockEntities(player); + } } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundForgetLevelChunkPacket", e); @@ -401,6 +413,12 @@ public class PacketConsumers { ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); // 记录加载的区块 player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); + + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); + if (ceChunk != null) { + ceChunk.spawnBlockEntities(player); + } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelChunkWithLightPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index e62760b77..4ec9a9a20 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -8,7 +8,6 @@ import io.netty.channel.ChannelHandler; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder; -import net.momirealms.craftengine.bukkit.block.entity.SimpleStorageBlockEntity; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; @@ -48,7 +47,6 @@ import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; -import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index a3f56861a..6d5d0cae4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -1,8 +1,11 @@ package net.momirealms.craftengine.bukkit.world; import net.momirealms.craftengine.bukkit.util.LightUtils; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; +import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.SectionPos; import net.momirealms.craftengine.core.world.World; @@ -39,4 +42,9 @@ public class BukkitCEWorld extends CEWorld { ); } } + + @Override + public BlockEntityRenderer createBlockEntityRenderer(BlockEntityRendererConfig config, BlockPos pos) { + return null; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index cfa5ebb2d..9a3a2b65d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.Player; @@ -30,6 +31,8 @@ public final class ImmutableBlockState extends BlockStateHolder { private Integer hashCode; private BlockSettings settings; private BlockEntityType blockEntityType; + @Nullable + private BlockEntityRendererConfig rendererConfig; ImmutableBlockState( Holder owner, @@ -66,6 +69,11 @@ public final class ImmutableBlockState extends BlockStateHolder { return this == EmptyBlock.STATE; } + @Nullable + public BlockEntityRendererConfig entityRenderer() { + return this.rendererConfig; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -85,6 +93,10 @@ public final class ImmutableBlockState extends BlockStateHolder { return this.blockEntityType != null; } + public boolean hasBlockEntityRenderer() { + return this.rendererConfig != null; + } + public BlockStateWrapper customBlockState() { return this.customBlockState; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java index 5d969518d..cb452fd4d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -5,5 +5,6 @@ import net.momirealms.craftengine.core.util.Key; public final class BlockEntityTypeKeys { private BlockEntityTypeKeys() {} + public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite"); public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java new file mode 100644 index 000000000..6414fb396 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java @@ -0,0 +1,23 @@ +package net.momirealms.craftengine.core.block.entity.render; + +import net.momirealms.craftengine.core.entity.player.Player; + +public abstract class BlockEntityRenderer { + private final int entityId; + + public BlockEntityRenderer(int entityId) { + this.entityId = entityId; + } + + public int entityId() { + return this.entityId; + } + + public abstract void spawn(); + + public abstract void despawn(); + + public abstract void spawn(Player player); + + public abstract void despawn(Player player); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java new file mode 100644 index 000000000..be4cdffbe --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java @@ -0,0 +1,88 @@ +package net.momirealms.craftengine.core.block.entity.render; + +import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.item.Item; + +public class BlockEntityRendererConfig { + private final float yRot; + private final float xRot; + private final ItemDisplayContext displayContext; + private final Item item; + private final float scale; + + public BlockEntityRendererConfig(ItemDisplayContext displayContext, + float yRot, + float xRot, + Item item, + float scale) { + this.displayContext = displayContext; + this.yRot = yRot; + this.xRot = xRot; + this.item = item; + this.scale = scale; + } + + public ItemDisplayContext displayContext() { + return displayContext; + } + + public Item item() { + return item; + } + + public float xRot() { + return xRot; + } + + public float yRot() { + return yRot; + } + + public float scale() { + return scale; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private ItemDisplayContext displayContext = ItemDisplayContext.NONE; + private Item item; + private float xRot; + private float yRot; + private float scale = 1f; + + public Builder() { + } + + public Builder displayContext(ItemDisplayContext displayContext) { + this.displayContext = displayContext; + return this; + } + + public Builder item(Item item) { + this.item = item; + return this; + } + + public Builder xRot(float xRot) { + this.xRot = xRot; + return this; + } + + public Builder yRot(float yRot) { + this.yRot = yRot; + return this; + } + + public Builder scale(float scale) { + this.scale = scale; + return this; + } + + public BlockEntityRendererConfig build() { + return new BlockEntityRendererConfig(this.displayContext, this.yRot, this.xRot, this.item, this.scale); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java index 066e3216a..51a74c794 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/PlaceholderTag.java @@ -7,7 +7,6 @@ import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.PlayerContext; -import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.util.AdventureHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 9d68a0e1c..b3c183f1b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -4,6 +4,8 @@ import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTabl import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -110,6 +112,11 @@ public abstract class CEWorld { return getChunkAtIfLoaded(ChunkPos.asLong(x, z)); } + @Nullable + public CEChunk getChunkAtIfLoaded(ChunkPos chunkPos) { + return getChunkAtIfLoaded(chunkPos.longKey); + } + @Nullable public ImmutableBlockState getBlockStateAtIfLoaded(int x, int y, int z) { CEChunk chunk = getChunkAtIfLoaded(x >> 4, z >> 4); @@ -208,4 +215,6 @@ public abstract class CEWorld { this.tickingBlockEntities.removeAll(toRemove); this.isTickingBlockEntities = false; } + + public abstract BlockEntityRenderer createBlockEntityRenderer(BlockEntityRendererConfig config, BlockPos pos); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 6b8acd1a7..4dbea6f15 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -4,15 +4,20 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.block.entity.tick.*; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; import net.momirealms.sparrow.nbt.ListTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; public class CEChunk { public final CEWorld world; @@ -20,23 +25,25 @@ public class CEChunk { public final CESection[] sections; public final WorldHeight worldHeightAccessor; public final Map blockEntities; + public final Map blockEntityRenderers; + private final ReentrantReadWriteLock renderLock = new ReentrantReadWriteLock(); private volatile boolean dirty; private volatile boolean loaded; - protected final Map tickingBlockEntitiesByPos = new HashMap<>(); + protected final Map tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; this.chunkPos = chunkPos; this.worldHeightAccessor = world.worldHeight(); this.sections = new CESection[this.worldHeightAccessor.getSectionsCount()]; - this.blockEntities = new Object2ObjectOpenHashMap<>(16, 0.5f); + this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); this.fillEmptySection(); } - public CEChunk(CEWorld world, ChunkPos chunkPos, CESection[] sections, ListTag blockEntitiesTag) { + public CEChunk(CEWorld world, ChunkPos chunkPos, CESection[] sections, @Nullable ListTag blockEntitiesTag, @Nullable ListTag itemDisplayBlockRenders) { this.world = world; this.chunkPos = chunkPos; - this.blockEntities = new Object2ObjectOpenHashMap<>(Math.max(blockEntitiesTag.size(), 16), 0.5f); this.worldHeightAccessor = world.worldHeight(); int sectionCount = this.worldHeightAccessor.getSectionsCount(); this.sections = new CESection[sectionCount]; @@ -49,9 +56,75 @@ public class CEChunk { } } this.fillEmptySection(); - List blockEntities = DefaultBlockEntitySerializer.deserialize(this, blockEntitiesTag); - for (BlockEntity blockEntity : blockEntities) { - this.setBlockEntity(blockEntity); + if (blockEntitiesTag != null) { + this.blockEntities = new Object2ObjectOpenHashMap<>(Math.max(blockEntitiesTag.size(), 10), 0.5f); + List blockEntities = DefaultBlockEntitySerializer.deserialize(this, blockEntitiesTag); + for (BlockEntity blockEntity : blockEntities) { + this.setBlockEntity(blockEntity); + } + } else { + this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); + } + if (itemDisplayBlockRenders != null) { + this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(itemDisplayBlockRenders.size(), 10), 0.5f); + List blockEntityRendererPoses = DefaultBlockEntityRendererSerializer.deserialize(this.chunkPos, itemDisplayBlockRenders); + for (BlockPos pos : blockEntityRendererPoses) { + this.addBlockEntityRenderer(pos); + } + } else { + this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + } + } + + public void spawnBlockEntities(Player player) { + try { + this.renderLock.readLock().lock(); + for (BlockEntityRenderer renderer : this.blockEntityRenderers.values()) { + renderer.spawn(player); + } + } finally { + this.renderLock.readLock().unlock(); + } + } + + public void despawnBlockEntities(Player player) { + try { + this.renderLock.readLock().lock(); + for (BlockEntityRenderer renderer : this.blockEntityRenderers.values()) { + renderer.despawn(player); + } + } finally { + this.renderLock.readLock().unlock(); + } + } + + public void addBlockEntityRenderer(BlockPos pos) { + this.addBlockEntityRenderer(pos, this.getBlockState(pos)); + } + + public void addBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) { + BlockEntityRendererConfig config = state.entityRenderer(); + if (config != null) { + BlockEntityRenderer renderer = this.world.createBlockEntityRenderer(config, pos); + renderer.spawn(); + try { + this.renderLock.writeLock().lock(); + this.blockEntityRenderers.put(pos, renderer); + } finally { + this.renderLock.writeLock().unlock(); + } + } + } + + public void removeBlockEntityRenderer(BlockPos pos) { + try { + this.renderLock.writeLock().lock(); + BlockEntityRenderer removed = this.blockEntityRenderers.remove(pos); + if (removed != null) { + removed.despawn(); + } + } finally { + this.renderLock.writeLock().unlock(); } } @@ -152,8 +225,17 @@ public class CEChunk { return Objects.requireNonNull(blockState.behavior().getEntityBehavior()).createBlockEntity(pos, blockState); } - public Map blockEntities() { - return Collections.unmodifiableMap(this.blockEntities); + public Collection blockEntities() { + return Collections.unmodifiableCollection(this.blockEntities.values()); + } + + public List blockEntityRenderers() { + try { + this.renderLock.readLock().lock(); + return new ArrayList<>(this.blockEntityRenderers.keySet()); + } finally { + this.renderLock.readLock().unlock(); + } } public boolean dirty() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntityRendererSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntityRendererSerializer.java new file mode 100644 index 000000000..cf89f584e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntityRendererSerializer.java @@ -0,0 +1,35 @@ +package net.momirealms.craftengine.core.world.chunk.serialization; + +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.ChunkPos; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.ListTag; + +import java.util.ArrayList; +import java.util.List; + +public final class DefaultBlockEntityRendererSerializer { + + public static List deserialize(ChunkPos chunkPos, ListTag blockEntitiesTag) { + List blockEntities = new ArrayList<>(blockEntitiesTag.size()); + for (int i = 0; i < blockEntitiesTag.size(); i++) { + CompoundTag tag = blockEntitiesTag.getCompound(i); + BlockPos blockPos = BlockEntity.readPosAndVerify(tag, chunkPos); + blockEntities.add(blockPos); + } + return blockEntities; + } + + public static ListTag serialize(List poses) { + ListTag listTag = new ListTag(); + for (BlockPos pos : poses) { + CompoundTag tag = new CompoundTag(); + tag.putInt("x", pos.x()); + tag.putInt("y", pos.y()); + tag.putInt("z", pos.z()); + listTag.add(tag); + } + return listTag; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index 0e1d59602..6dfc48961 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -12,15 +12,14 @@ import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.ListTag; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Map; public final class DefaultBlockEntitySerializer { - public static ListTag serialize(Map tiles) { + public static ListTag serialize(Collection entities) { ListTag result = new ListTag(); - for (Map.Entry entry : tiles.entrySet()) { - BlockEntity entity = entry.getValue(); + for (BlockEntity entity : entities) { if (entity.isValid()) { result.add(entity.saveAsTag()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index fafceb99c..4ef8a7955 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -9,8 +9,6 @@ import net.momirealms.sparrow.nbt.ListTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Optional; - public final class DefaultChunkSerializer { @Nullable @@ -28,7 +26,14 @@ public final class DefaultChunkSerializer { if (sections.isEmpty()) return null; CompoundTag chunkNbt = new CompoundTag(); chunkNbt.put("sections", sections); - chunkNbt.put("block_entities", DefaultBlockEntitySerializer.serialize(chunk.blockEntities())); + ListTag blockEntities = DefaultBlockEntitySerializer.serialize(chunk.blockEntities()); + if (!blockEntities.isEmpty()) { + chunkNbt.put("block_entities", blockEntities); + } + ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.blockEntityRenderers()); + if (!blockEntityRenders.isEmpty()) { + chunkNbt.put("block_entity_renders", blockEntityRenders); + } return chunkNbt; } @@ -46,7 +51,8 @@ public final class DefaultChunkSerializer { } } } - ListTag blockEntities = Optional.ofNullable(chunkNbt.getList("block_entities")).orElse(new ListTag()); - return new CEChunk(world, pos, sectionArray, blockEntities); + ListTag blockEntities = chunkNbt.getList("block_entities"); + ListTag itemDisplayBlockRenders = chunkNbt.getList("block_entity_renderers"); + return new CEChunk(world, pos, sectionArray, blockEntities, itemDisplayBlockRenders); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java index 8121e3b64..9a837a003 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.core.world.collision; import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.EntityHitResult; import net.momirealms.craftengine.core.world.Vec3d; From f491335316357c78392be6cf5b64dc41e64e9888 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 6 Sep 2025 06:28:45 +0800 Subject: [PATCH 031/226] =?UTF-8?q?=E7=A8=8D=E4=BD=9C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/world/BukkitWorldManager.java | 129 +- ...currentUUID2ReferenceChainedHashTable.java | 1154 +++++++++++++++++ 2 files changed, 1191 insertions(+), 92 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java 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 2d8671d53..18050cd49 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 @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.world; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; @@ -9,6 +8,7 @@ import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.ConcurrentUUID2ReferenceChainedHashTable; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.SectionPos; @@ -29,27 +29,21 @@ import org.bukkit.event.world.*; import org.jetbrains.annotations.NotNull; import java.io.IOException; -import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.locks.ReentrantReadWriteLock; public class BukkitWorldManager implements WorldManager, Listener { private static BukkitWorldManager instance; private final BukkitCraftEngine plugin; - private final Map worlds; - private CEWorld[] worldArray = new CEWorld[0]; - private final ReentrantReadWriteLock worldMapLock = new ReentrantReadWriteLock(); - // cache - private UUID lastVisitedUUID; - private CEWorld lastVisitedWorld; + private final ConcurrentUUID2ReferenceChainedHashTable worlds; + private CEWorld[] worldArray; private StorageAdaptor storageAdaptor; private boolean initialized = false; public BukkitWorldManager(BukkitCraftEngine plugin) { instance = this; this.plugin = plugin; - this.worlds = new Object2ObjectOpenHashMap<>(32, 0.5f); + this.worlds = ConcurrentUUID2ReferenceChainedHashTable.createWithCapacity(10, 0.5f); this.storageAdaptor = new DefaultStorageAdaptor(); for (World world : Bukkit.getWorlds()) { this.worlds.put(world.getUID(), new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); @@ -71,20 +65,7 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public CEWorld getWorld(UUID uuid) { - if (uuid.equals(this.lastVisitedUUID)) { - return this.lastVisitedWorld; - } - this.worldMapLock.readLock().lock(); - try { - CEWorld world = worlds.get(uuid); - if (world != null) { - this.lastVisitedUUID = uuid; - this.lastVisitedWorld = world; - } - return world; - } finally { - this.worldMapLock.readLock().unlock(); - } + return this.worlds.get(uuid); } @Override @@ -98,23 +79,18 @@ public class BukkitWorldManager implements WorldManager, Listener { public void delayedInit() { // load loaded chunks - this.worldMapLock.writeLock().lock(); - try { - for (World world : Bukkit.getWorlds()) { - try { - CEWorld ceWorld = this.worlds.computeIfAbsent(world.getUID(), k -> new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); - for (Chunk chunk : world.getLoadedChunks()) { - handleChunkLoad(ceWorld, chunk); - } - ceWorld.setTicking(true); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Error loading world: " + world.getName(), e); + for (World world : Bukkit.getWorlds()) { + try { + CEWorld ceWorld = this.worlds.computeIfAbsent(world.getUID(), k -> new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); + for (Chunk chunk : world.getLoadedChunks()) { + handleChunkLoad(ceWorld, chunk); } + ceWorld.setTicking(true); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Error loading world: " + world.getName(), e); } - this.resetWorldArray(); - } finally { - this.worldMapLock.writeLock().unlock(); } + this.resetWorldArray(); Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin()); this.initialized = true; } @@ -147,35 +123,25 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void loadWorld(net.momirealms.craftengine.core.world.World world) { - this.worldMapLock.writeLock().lock(); - try { - if (this.worlds.containsKey(world.uuid())) return; - CEWorld ceWorld = new BukkitCEWorld(world, this.storageAdaptor); - this.worlds.put(world.uuid(), ceWorld); - this.resetWorldArray(); - for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { - handleChunkLoad(ceWorld, chunk); - } - ceWorld.setTicking(true); - } finally { - this.worldMapLock.writeLock().unlock(); + if (this.worlds.containsKey(world.uuid())) return; + CEWorld ceWorld = new BukkitCEWorld(world, this.storageAdaptor); + this.worlds.put(world.uuid(), ceWorld); + this.resetWorldArray(); + for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { + handleChunkLoad(ceWorld, chunk); } + ceWorld.setTicking(true); } @Override public void loadWorld(CEWorld world) { - this.worldMapLock.writeLock().lock(); - try { - if (this.worlds.containsKey(world.world().uuid())) return; - this.worlds.put(world.world().uuid(), world); - this.resetWorldArray(); - for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) { - handleChunkLoad(world, chunk); - } - world.setTicking(true); - } finally { - this.worldMapLock.writeLock().unlock(); + if (this.worlds.containsKey(world.world().uuid())) return; + this.worlds.put(world.world().uuid(), world); + this.resetWorldArray(); + for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) { + handleChunkLoad(world, chunk); } + world.setTicking(true); } @Override @@ -198,20 +164,11 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void unloadWorld(net.momirealms.craftengine.core.world.World world) { CEWorld ceWorld; - this.worldMapLock.writeLock().lock(); - try { - ceWorld = this.worlds.remove(world.uuid()); - if (ceWorld == null) { - return; - } - if (ceWorld == this.lastVisitedWorld) { - this.lastVisitedWorld = null; - this.lastVisitedUUID = null; - } - this.resetWorldArray(); - } finally { - this.worldMapLock.writeLock().unlock(); + ceWorld = this.worlds.remove(world.uuid()); + if (ceWorld == null) { + return; } + this.resetWorldArray(); ceWorld.setTicking(false); for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { handleChunkUnload(ceWorld, chunk); @@ -238,30 +195,18 @@ public class BukkitWorldManager implements WorldManager, Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onChunkLoad(ChunkLoadEvent event) { - this.worldMapLock.readLock().lock(); - CEWorld world; - try { - world = worlds.get(event.getWorld().getUID()); - if (world == null) { - return; - } - } finally { - this.worldMapLock.readLock().unlock(); + CEWorld world = worlds.get(event.getWorld().getUID()); + if (world == null) { + return; } handleChunkLoad(world, event.getChunk()); } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onChunkUnload(ChunkUnloadEvent event) { - CEWorld world; - this.worldMapLock.readLock().lock(); - try { - world = worlds.get(event.getWorld().getUID()); - if (world == null) { - return; - } - } finally { - this.worldMapLock.readLock().unlock(); + CEWorld world = worlds.get(event.getWorld().getUID()); + if (world == null) { + return; } handleChunkUnload(world, event.getChunk()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java b/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java new file mode 100644 index 000000000..80ffcb701 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java @@ -0,0 +1,1154 @@ +/** + * This implementation references the ConcurrentUtil implementation by Tuinity, + * available at: https://github.com/Tuinity/ConcurrentUtil + *

+ * This work is licensed under the GNU General Public License v3.0 (GPLv3) + */ +package net.momirealms.craftengine.core.util; + +import ca.spottedleaf.concurrentutil.util.*; + +import java.lang.invoke.VarHandle; +import java.util.*; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Concurrent hashtable implementation supporting mapping UUID values onto non-null Object + * values with support for multiple writer and multiple reader threads. + */ +public class ConcurrentUUID2ReferenceChainedHashTable implements Iterable> { + protected static final int DEFAULT_CAPACITY = 16; + protected static final float DEFAULT_LOAD_FACTOR = 0.75f; + protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; + + protected final LongAdder size = new LongAdder(); + protected final float loadFactor; + + protected volatile TableEntry[] table; + + protected static final int THRESHOLD_NO_RESIZE = -1; + protected static final int THRESHOLD_RESIZING = -2; + protected volatile int threshold; + protected static final VarHandle THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(ConcurrentUUID2ReferenceChainedHashTable.class, "threshold", int.class); + + protected final int getThresholdAcquire() { + return (int)THRESHOLD_HANDLE.getAcquire(this); + } + + protected final int getThresholdVolatile() { + return (int)THRESHOLD_HANDLE.getVolatile(this); + } + + protected final void setThresholdPlain(final int threshold) { + THRESHOLD_HANDLE.set(this, threshold); + } + + protected final void setThresholdRelease(final int threshold) { + THRESHOLD_HANDLE.setRelease(this, threshold); + } + + protected final void setThresholdVolatile(final int threshold) { + THRESHOLD_HANDLE.setVolatile(this, threshold); + } + + protected final int compareExchangeThresholdVolatile(final int expect, final int update) { + return (int)THRESHOLD_HANDLE.compareAndExchange(this, expect, update); + } + + protected Values values; + protected EntrySet entrySet; + + public ConcurrentUUID2ReferenceChainedHashTable() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + protected static int getTargetThreshold(final int capacity, final float loadFactor) { + final double ret = (double)capacity * (double)loadFactor; + if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { + return THRESHOLD_NO_RESIZE; + } + + return (int)Math.ceil(ret); + } + + protected static int getCapacityFor(final int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Invalid capacity: " + capacity); + } + if (capacity >= MAXIMUM_CAPACITY) { + return MAXIMUM_CAPACITY; + } + return IntegerUtil.roundCeilLog2(capacity); + } + + protected ConcurrentUUID2ReferenceChainedHashTable(final int capacity, final float loadFactor) { + final int tableSize = getCapacityFor(capacity); + + if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { + throw new IllegalArgumentException("Invalid load factor: " + loadFactor); + } + + if (tableSize == MAXIMUM_CAPACITY) { + this.setThresholdPlain(THRESHOLD_NO_RESIZE); + } else { + this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor)); + } + + this.loadFactor = loadFactor; + // noinspection unchecked + this.table = (TableEntry[])new TableEntry[tableSize]; + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithCapacity(final int capacity) { + return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR); + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithCapacity(final int capacity, final float loadFactor) { + return new ConcurrentUUID2ReferenceChainedHashTable<>(capacity, loadFactor); + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithExpected(final int expected) { + return createWithExpected(expected, DEFAULT_LOAD_FACTOR); + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithExpected(final int expected, final float loadFactor) { + final int capacity = (int)Math.ceil((double)expected / (double)loadFactor); + return createWithCapacity(capacity, loadFactor); + } + + protected static int getHash(final UUID key) { + return (int)HashUtil.mix(key.getMostSignificantBits() ^ key.getLeastSignificantBits()); + } + + public final float getLoadFactor() { + return this.loadFactor; + } + + protected static TableEntry getAtIndexVolatile(final TableEntry[] table, final int index) { + //noinspection unchecked + return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getVolatile(table, index); + } + + protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { + TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); + } + + protected static void setAtIndexVolatile(final TableEntry[] table, final int index, final TableEntry value) { + TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setVolatile(table, index, value); + } + + protected static TableEntry compareAndExchangeAtIndexVolatile(final TableEntry[] table, final int index, + final TableEntry expect, final TableEntry update) { + //noinspection unchecked + return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.compareAndExchange(table, index, expect, update); + } + + /** + * Returns the possible node associated with the key, or {@code null} if there is no such node. + */ + protected final TableEntry getNode(final UUID key) { + final int hash = getHash(key); + + TableEntry[] table = this.table; + for (;;) { + TableEntry node = getAtIndexVolatile(table, hash & (table.length - 1)); + + if (node == null) { + return node; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue; + } + + for (; node != null; node = node.getNextVolatile()) { + if (node.key.equals(key)) { + return node; + } + } + + return node; + } + } + + /** + * Returns the currently mapped value associated with the specified key, or {@code null} if there is none. + */ + public V get(final UUID key) { + final TableEntry node = this.getNode(key); + return node == null ? null : node.getValueVolatile(); + } + + /** + * Returns the currently mapped value associated with the specified key, or the specified default value if there is none. + */ + public V getOrDefault(final UUID key, final V defaultValue) { + final TableEntry node = this.getNode(key); + if (node == null) { + return defaultValue; + } + + final V ret = node.getValueVolatile(); + if (ret == null) { + return defaultValue; + } + + return ret; + } + + /** + * Returns whether the specified key is mapped to some value. + */ + public boolean containsKey(final UUID key) { + return this.get(key) != null; + } + + /** + * Returns whether the specified value has a key mapped to it. + */ + public boolean containsValue(final V value) { + Validate.notNull(value, "Value cannot be null"); + + final NodeIterator iterator = new NodeIterator<>(this.table); + + TableEntry node; + while ((node = iterator.findNext()) != null) { + if (node.getValueAcquire() == value) { + return true; + } + } + + return false; + } + + /** + * Returns the number of mappings in this map. + */ + public int size() { + final long ret = this.size.sum(); + + if (ret < 0L) { + return 0; + } + if (ret > (long)Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int)ret; + } + + /** + * Returns whether this map has no mappings. + */ + public boolean isEmpty() { + return this.size.sum() <= 0L; + } + + /** + * Adds count to size and checks threshold for resizing + */ + protected final void addSize(final long count) { + this.size.add(count); + + final int threshold = this.getThresholdAcquire(); + + if (threshold < 0L) { + return; + } + + final long sum = this.size.sum(); + + if (sum < (long)threshold) { + return; + } + + if (threshold != this.compareExchangeThresholdVolatile(threshold, THRESHOLD_RESIZING)) { + return; + } + + this.resize(sum); + } + + /** + * Resizes table + */ + private void resize(final long sum) { + int capacity; + + final double targetD = ((double)sum / (double)this.loadFactor) + 1.0; + if (targetD >= (double)MAXIMUM_CAPACITY) { + capacity = MAXIMUM_CAPACITY; + } else { + capacity = (int)Math.ceil(targetD); + capacity = IntegerUtil.roundCeilLog2(capacity); + if (capacity > MAXIMUM_CAPACITY) { + capacity = MAXIMUM_CAPACITY; + } + } + + // noinspection unchecked + final TableEntry[] newTable = new TableEntry[capacity]; + // noinspection unchecked + final TableEntry resizeNode = new TableEntry<>(null, (V)newTable, true); + + final TableEntry[] oldTable = this.table; + + final int capOldShift = IntegerUtil.floorLog2(oldTable.length); + final int capDiffShift = IntegerUtil.floorLog2(capacity) - capOldShift; + + if (capDiffShift == 0) { + throw new IllegalStateException("Resizing to same size"); + } + + // noinspection unchecked + final TableEntry[] work = new TableEntry[1 << capDiffShift]; + + for (int i = 0, len = oldTable.length; i < len; ++i) { + TableEntry binNode = getAtIndexVolatile(oldTable, i); + + for (;;) { + if (binNode == null) { + if (null == (binNode = compareAndExchangeAtIndexVolatile(oldTable, i, null, resizeNode))) { + break; + } + } + + synchronized (binNode) { + if (binNode != (binNode = getAtIndexVolatile(oldTable, i))) { + continue; + } + + TableEntry next = binNode.getNextPlain(); + + if (next == null) { + newTable[getHash(binNode.key) & (capacity - 1)] = binNode; + } else { + Arrays.fill(work, null); + + for (TableEntry curr = binNode; curr != null; curr = curr.getNextPlain()) { + final int newTableIdx = getHash(curr.key) & (capacity - 1); + final int workIdx = newTableIdx >>> capOldShift; + + final TableEntry replace = new TableEntry<>(curr.key, curr.getValuePlain()); + + final TableEntry workNode = work[workIdx]; + work[workIdx] = replace; + + if (workNode == null) { + newTable[newTableIdx] = replace; + continue; + } else { + workNode.setNextPlain(replace); + continue; + } + } + } + + setAtIndexRelease(oldTable, i, resizeNode); + break; + } + } + } + + final int newThreshold; + if (capacity == MAXIMUM_CAPACITY) { + newThreshold = THRESHOLD_NO_RESIZE; + } else { + newThreshold = getTargetThreshold(capacity, loadFactor); + } + + this.table = newTable; + this.setThresholdVolatile(newThreshold); + } + + /** + * Subtracts count from size + */ + protected final void subSize(final long count) { + this.size.add(-count); + } + + /** + * Atomically updates the value associated with {@code key} to {@code value}, or inserts a new mapping. + */ + public V put(final UUID key, final V value) { + Validate.notNull(value, "Value may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { + this.addSize(1L); + return null; + } + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + TableEntry prev = null; + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + final V ret = node.getValuePlain(); + node.setValueVolatile(value); + return ret; + } + } + + prev.setNextRelease(new TableEntry<>(key, value)); + } + + this.addSize(1L); + return null; + } + } + } + + /** + * Atomically inserts a new mapping if and only if {@code key} is not currently mapped. + */ + public V putIfAbsent(final UUID key, final V value) { + Validate.notNull(value, "Value may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { + this.addSize(1L); + return null; + } + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + if (node.key.equals(key)) { + final V ret = node.getValueVolatile(); + if (ret != null) { + return ret; + } + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + TableEntry prev = null; + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + return node.getValuePlain(); + } + } + + prev.setNextRelease(new TableEntry<>(key, value)); + } + + this.addSize(1L); + return null; + } + } + } + + /** + * Atomically updates the value associated with {@code key} to {@code value}, or does nothing if not mapped. + */ + public V replace(final UUID key, final V value) { + Validate.notNull(value, "Value may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + for (; node != null; node = node.getNextPlain()) { + if (node.key.equals(key)) { + final V ret = node.getValuePlain(); + node.setValueVolatile(value); + return ret; + } + } + } + + return null; + } + } + } + + /** + * Atomically updates the value if the currently associated value is reference equal to {@code expect}. + */ + public V replace(final UUID key, final V expect, final V update) { + Validate.notNull(expect, "Expect may not be null"); + Validate.notNull(update, "Update may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + for (; node != null; node = node.getNextPlain()) { + if (node.key.equals(key)) { + final V ret = node.getValuePlain(); + + if (ret != expect) { + return ret; + } + + node.setValueVolatile(update); + return ret; + } + } + } + + return null; + } + } + } + + /** + * Atomically removes the mapping for the specified key. + */ + public V remove(final UUID key) { + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + boolean removed = false; + V ret = null; + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + TableEntry prev = null; + + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + ret = node.getValuePlain(); + removed = true; + + if (prev == null) { + setAtIndexRelease(table, index, node.getNextPlain()); + } else { + prev.setNextRelease(node.getNextPlain()); + } + + break; + } + } + } + + if (removed) { + this.subSize(1L); + } + + return ret; + } + } + } + + /** + * Atomically removes the mapping if it is mapped to {@code expect}. + */ + public V remove(final UUID key, final V expect) { + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + boolean removed = false; + V ret = null; + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + TableEntry prev = null; + + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + ret = node.getValuePlain(); + if (ret == expect) { + removed = true; + + if (prev == null) { + setAtIndexRelease(table, index, node.getNextPlain()); + } else { + prev.setNextRelease(node.getNextPlain()); + } + } + break; + } + } + } + + if (removed) { + this.subSize(1L); + } + + return ret; + } + } + } + + /** + * Removes at least all entries currently mapped at the beginning of this call. + */ + public void clear() { + final NodeIterator nodeIterator = new NodeIterator<>(this.table); + + TableEntry node; + while ((node = nodeIterator.findNext()) != null) { + this.remove(node.key); + } + } + + /** + * Returns an iterator over the entries in this map. + */ + public Iterator> entryIterator() { + return new EntryIterator<>(this); + } + + @Override + public final Iterator> iterator() { + return this.entryIterator(); + } + + /** + * Returns an iterator over the keys in this map. + */ + public Iterator keyIterator() { + return new KeyIterator<>(this); + } + + /** + * Returns an iterator over the values in this map. + */ + public Iterator valueIterator() { + return new ValueIterator<>(this); + } + + public Collection values() { + final Values values = this.values; + if (values != null) { + return values; + } + return this.values = new Values<>(this); + } + + public Set> entrySet() { + final EntrySet entrySet = this.entrySet; + if (entrySet != null) { + return entrySet; + } + return this.entrySet = new EntrySet<>(this); + } + + /** + * See {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)} + *

+ * This function is a "functional methods" as defined by {@link ConcurrentUUID2ReferenceChainedHashTable}. + *

+ */ + public V computeIfAbsent(final UUID key, final Function function) { + Validate.notNull(function, "Function may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + V ret = null; + if (node == null) { + final TableEntry insert = new TableEntry<>(key, null); + + boolean added = false; + + synchronized (insert) { + if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) { + try { + ret = function.apply(key); + } catch (final Throwable throwable) { + setAtIndexVolatile(table, index, null); + ThrowUtil.throwUnchecked(throwable); + // unreachable + return null; + } + + if (ret == null) { + setAtIndexVolatile(table, index, null); + return null; + } else { + // volatile ordering ensured by addSize(), but we need release here + // to ensure proper ordering with reads and other writes + insert.setValueRelease(ret); + added = true; + } + } // else: node != null, fall through + } + + if (added) { + this.addSize(1L); + return ret; + } + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + // optimise ifAbsent calls: check if first node is key before attempting lock acquire + if (node.key.equals(key)) { + ret = node.getValueVolatile(); + if (ret != null) { + return ret; + } // else: fall back to lock to read the node + } + + boolean added = false; + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + // plain reads are fine during synchronised access, as we are the only writer + TableEntry prev = null; + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + ret = node.getValuePlain(); + return ret; + } + } + + final V computed = function.apply(key); + if (computed != null) { + // volatile ordering ensured by addSize(), but we need release here + // to ensure proper ordering with reads and other writes + prev.setNextRelease(new TableEntry<>(key, computed)); + ret = computed; + added = true; + } + } + + if (added) { + this.addSize(1L); + } + + return ret; + } + } + } + + // Iterator implementations (similar to original but with UUID instead of long) + protected static final class EntryIterator extends BaseIteratorImpl> { + public EntryIterator(final ConcurrentUUID2ReferenceChainedHashTable map) { + super(map); + } + @Override public TableEntry next() { return this.nextNode(); } + @Override public void forEachRemaining(final Consumer> action) { + Validate.notNull(action, "Action may not be null"); + while (this.hasNext()) { action.accept(this.next()); } + } + } + + protected static final class KeyIterator extends BaseIteratorImpl { + public KeyIterator(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public UUID next() { return this.nextNode().key; } + @Override public void forEachRemaining(final Consumer action) { + Validate.notNull(action, "Action may not be null"); + while (this.hasNext()) { action.accept(this.next()); } + } + } + + protected static final class ValueIterator extends BaseIteratorImpl { + public ValueIterator(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public V next() { return this.nextNode().getValueVolatile(); } + @Override public void forEachRemaining(final Consumer action) { + Validate.notNull(action, "Action may not be null"); + while (this.hasNext()) { action.accept(this.next()); } + } + } + + protected static abstract class BaseIteratorImpl extends NodeIterator implements Iterator { + protected final ConcurrentUUID2ReferenceChainedHashTable map; + protected TableEntry lastReturned; + protected TableEntry nextToReturn; + + protected BaseIteratorImpl(final ConcurrentUUID2ReferenceChainedHashTable map) { + super(map.table); + this.map = map; + } + + @Override public final boolean hasNext() { + if (this.nextToReturn != null) return true; + return (this.nextToReturn = this.findNext()) != null; + } + + protected final TableEntry nextNode() throws NoSuchElementException { + TableEntry ret = this.nextToReturn; + if (ret != null) { + this.lastReturned = ret; + this.nextToReturn = null; + return ret; + } + ret = this.findNext(); + if (ret != null) { + this.lastReturned = ret; + return ret; + } + throw new NoSuchElementException(); + } + + @Override public final void remove() { + final TableEntry lastReturned = this.lastReturned; + if (lastReturned == null) throw new NoSuchElementException(); + this.lastReturned = null; + this.map.remove(lastReturned.key); + } + + @Override public abstract T next() throws NoSuchElementException; + @Override public abstract void forEachRemaining(final Consumer action); + } + + protected static class NodeIterator { + // Implementation similar to original but with UUID keys + protected TableEntry[] currentTable; + protected ResizeChain resizeChain; + protected TableEntry last; + protected int nextBin; + protected int increment; + + protected NodeIterator(final TableEntry[] baseTable) { + this.currentTable = baseTable; + this.increment = 1; + } + + private TableEntry[] pullResizeChain(final int index) { + final ResizeChain resizeChain = this.resizeChain; + if (resizeChain == null) { + this.currentTable = null; + return null; + } + + final ResizeChain prevChain = resizeChain.prev; + this.resizeChain = prevChain; + if (prevChain == null) { + this.currentTable = null; + return null; + } + + final TableEntry[] newTable = prevChain.table; + int newIdx = index & (newTable.length - 1); + + final ResizeChain nextPrevChain = prevChain.prev; + final int increment = nextPrevChain == null ? 1 : nextPrevChain.table.length; + + newIdx += increment; + this.increment = increment; + this.nextBin = newIdx; + this.currentTable = newTable; + + return newTable; + } + + private TableEntry[] pushResizeChain(final TableEntry[] table, final TableEntry entry) { + final ResizeChain chain = this.resizeChain; + + if (chain == null) { + // noinspection unchecked + final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); + final ResizeChain oldChain = new ResizeChain<>(table, null, null); + final ResizeChain currChain = new ResizeChain<>(nextTable, oldChain, null); + oldChain.next = currChain; + + this.increment = table.length; + this.resizeChain = currChain; + this.currentTable = nextTable; + + return nextTable; + } else { + ResizeChain currChain = chain.next; + if (currChain == null) { + // noinspection unchecked + final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); + currChain = new ResizeChain<>(ret, chain, null); + chain.next = currChain; + + this.increment = table.length; + this.resizeChain = currChain; + this.currentTable = ret; + + return ret; + } else { + this.increment = table.length; + this.resizeChain = currChain; + return this.currentTable = currChain.table; + } + } + } + + protected final TableEntry findNext() { + for (;;) { + final TableEntry last = this.last; + if (last != null) { + final TableEntry next = last.getNextVolatile(); + if (next != null) { + this.last = next; + if (next.getValuePlain() == null) continue; + return next; + } + } + + TableEntry[] table = this.currentTable; + if (table == null) return null; + + int idx = this.nextBin; + int increment = this.increment; + for (;;) { + if (idx >= table.length) { + table = this.pullResizeChain(idx); + idx = this.nextBin; + increment = this.increment; + if (table != null) continue; + this.last = null; + return null; + } + + final TableEntry entry = getAtIndexVolatile(table, idx); + if (entry == null) { + idx += increment; + continue; + } + + if (entry.resize) { + table = this.pushResizeChain(table, entry); + increment = this.increment; + continue; + } + + this.last = entry; + this.nextBin = idx + increment; + if (entry.getValuePlain() != null) return entry; + break; + } + } + } + + protected static final class ResizeChain { + public final TableEntry[] table; + public final ResizeChain prev; + public ResizeChain next; + + public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { + this.table = table; + this.prev = prev; + this.next = next; + } + } + } + + protected static abstract class BaseCollection implements Collection { + protected final ConcurrentUUID2ReferenceChainedHashTable map; + + protected BaseCollection(final ConcurrentUUID2ReferenceChainedHashTable map) { + this.map = map; + } + + @Override public int size() { return this.map.size(); } + @Override public boolean isEmpty() { return this.map.isEmpty(); } + @Override public void forEach(final Consumer action) { this.iterator().forEachRemaining(action); } + + private List asList() { + final List ret = new ArrayList<>(this.map.size()); + for (final E element : this) ret.add(element); + return ret; + } + + @Override public Object[] toArray() { return this.asList().toArray(); } + @Override public T[] toArray(final T[] a) { return this.asList().toArray(a); } + + @Override public boolean containsAll(final Collection collection) { + for (final Object value : collection) { + if (!this.contains(value)) return false; + } + return true; + } + + @Override public boolean add(final E value) { throw new UnsupportedOperationException(); } + @Override public boolean remove(final Object value) { throw new UnsupportedOperationException(); } + @Override public boolean addAll(final Collection collection) { throw new UnsupportedOperationException(); } + @Override public boolean removeAll(final Collection collection) { throw new UnsupportedOperationException(); } + @Override public boolean removeIf(final Predicate filter) { throw new UnsupportedOperationException(); } + @Override public boolean retainAll(final Collection collection) { throw new UnsupportedOperationException(); } + @Override public void clear() { throw new UnsupportedOperationException(); } + } + + protected static class Values extends BaseCollection { + public Values(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public boolean contains(final Object value) { return this.map.containsValue((V)value); } + @Override public Iterator iterator() { return this.map.valueIterator(); } + } + + protected static class EntrySet extends BaseCollection> implements Set> { + protected EntrySet(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public boolean contains(final Object value) { + if (!(value instanceof TableEntry entry)) return false; + final V mapped = this.map.get((UUID)entry.getKey()); + return mapped != null && mapped == value; + } + @Override public Iterator> iterator() { return this.map.entryIterator(); } + } + + public static final class TableEntry { + private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + + private final boolean resize; + private final UUID key; + + private volatile V value; + private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); + + private V getValuePlain() { return (V)VALUE_HANDLE.get(this); } + private V getValueAcquire() { return (V)VALUE_HANDLE.getAcquire(this); } + private V getValueVolatile() { return (V)VALUE_HANDLE.getVolatile(this); } + private void setValuePlain(final V value) { VALUE_HANDLE.set(this, (Object)value); } + private void setValueRelease(final V value) { VALUE_HANDLE.setRelease(this, (Object)value); } + private void setValueVolatile(final V value) { VALUE_HANDLE.setVolatile(this, (Object)value); } + + private volatile TableEntry next; + private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + + private TableEntry getNextPlain() { return (TableEntry)NEXT_HANDLE.get(this); } + private TableEntry getNextVolatile() { return (TableEntry)NEXT_HANDLE.getVolatile(this); } + private void setNextPlain(final TableEntry next) { NEXT_HANDLE.set(this, next); } + private void setNextRelease(final TableEntry next) { NEXT_HANDLE.setRelease(this, next); } + private void setNextVolatile(final TableEntry next) { NEXT_HANDLE.setVolatile(this, next); } + + public TableEntry(final UUID key, final V value) { + this.resize = false; + this.key = key; + this.setValuePlain(value); + } + + public TableEntry(final UUID key, final V value, final boolean resize) { + this.resize = resize; + this.key = key; + this.setValuePlain(value); + } + + public UUID getKey() { return this.key; } + public V getValue() { return this.getValueVolatile(); } + } +} \ No newline at end of file From 7b5d3edc72f8a604447689a102a3832f9b54973f Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 6 Sep 2025 07:09:48 +0800 Subject: [PATCH 032/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=9A=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E4=B8=96=E7=95=8C=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/injector/WorldStorageInjector.java | 64 +++++++++---------- .../bukkit/world/BukkitCEWorld.java | 16 ++--- .../craftengine/core/world/CEWorld.java | 15 +++-- gradle.properties | 2 +- 4 files changed, 46 insertions(+), 51 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 50fafbd48..71e043a19 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -14,9 +14,9 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.block.DelegatingBlockState; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; @@ -36,7 +36,6 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.Callable; import java.util.function.Consumer; @@ -219,38 +218,9 @@ public final class WorldStorageInjector { @SuppressWarnings("DuplicatedCode") private static void compareAndUpdateBlockState(int x, int y, int z, Object newState, Object previousState, InjectedHolder holder) { - Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(newState); CESection section = holder.ceSection(); - // 如果是原版方块 - if (optionalCustomState.isEmpty()) { - // 那么应该清空自定义块 - ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE); - // 处理 自定义块 -> 原版块 - if (!previous.isEmpty()) { - CEChunk chunk = holder.ceChunk(); - chunk.setDirty(true); - if (previous.hasBlockEntity()) { - BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); - BlockEntity blockEntity = chunk.getBlockEntity(pos, false); - if (blockEntity != null) { - blockEntity.preRemove(); - chunk.removeBlockEntity(pos); - } - } - if (previous.hasBlockEntityRenderer()) { - BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); - chunk.removeBlockEntityRenderer(pos); - } - if (Config.enableLightSystem()) { - // 自定义块到原版块,只需要判断旧块是否和客户端一直 - BlockStateWrapper wrapper = previous.vanillaBlockState(); - if (wrapper != null) { - updateLight(holder, wrapper.literalObject(), previousState, x, y, z); - } - } - } - } else { - ImmutableBlockState newImmutableBlockState = optionalCustomState.get(); + if (newState instanceof DelegatingBlockState delegatingBlockState) { + ImmutableBlockState newImmutableBlockState = delegatingBlockState.blockState(); ImmutableBlockState previousImmutableBlockState = section.setBlockState(x, y, z, newImmutableBlockState); if (previousImmutableBlockState == newImmutableBlockState) return; // 处理 自定义块到自定义块或原版块到自定义块 @@ -307,6 +277,34 @@ public final class WorldStorageInjector { updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); } } + } else { + // 如果是原版方块 + // 那么应该清空自定义块 + ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE); + // 处理 自定义块 -> 原版块 + if (!previous.isEmpty()) { + CEChunk chunk = holder.ceChunk(); + chunk.setDirty(true); + if (previous.hasBlockEntity()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + BlockEntity blockEntity = chunk.getBlockEntity(pos, false); + if (blockEntity != null) { + blockEntity.preRemove(); + chunk.removeBlockEntity(pos); + } + } + if (previous.hasBlockEntityRenderer()) { + BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); + chunk.removeBlockEntityRenderer(pos); + } + if (Config.enableLightSystem()) { + // 自定义块到原版块,只需要判断旧块是否和客户端一直 + BlockStateWrapper wrapper = previous.vanillaBlockState(); + if (wrapper != null) { + updateLight(holder, wrapper.literalObject(), previousState, x, y, z); + } + } + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 6d5d0cae4..93f3727cc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -7,14 +7,10 @@ import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; -import net.momirealms.craftengine.core.world.SectionPos; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; -import java.util.ArrayList; -import java.util.List; - public class BukkitCEWorld extends CEWorld { public BukkitCEWorld(World world, StorageAdaptor adaptor) { @@ -27,19 +23,19 @@ public class BukkitCEWorld extends CEWorld { @Override public void updateLight() { - List poses; - synchronized (super.updatedSectionSet) { - poses = new ArrayList<>(super.updatedSectionSet); - super.updatedSectionSet.clear(); - } if (Config.enableLightSystem()) { + super.isUpdatingLights = true; LightUtils.updateChunkLight( (org.bukkit.World) this.world.platformWorld(), - SectionPosUtils.toMap(poses, + SectionPosUtils.toMap(super.lightSections, this.world.worldHeight().getMinSection() - 1, this.world.worldHeight().getMaxSection() + 1 ) ); + super.lightSections.clear(); + super.isUpdatingLights = false; + super.lightSections.addAll(super.pendingLightSections); + super.pendingLightSections.clear(); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index b3c183f1b..32abf2075 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -17,7 +17,6 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; public abstract class CEWorld { public static final String REGION_DIRECTORY = "craftengine"; @@ -25,10 +24,12 @@ public abstract class CEWorld { protected final ConcurrentLong2ReferenceChainedHashTable loadedChunkMap; protected final WorldDataStorage worldDataStorage; protected final WorldHeight worldHeightAccessor; - protected final Set updatedSectionSet = ConcurrentHashMap.newKeySet(128); + protected final List pendingLightSections = new ArrayList<>(128); + protected final Set lightSections = new HashSet<>(128); protected final List tickingBlockEntities = new ArrayList<>(); protected final List pendingTickingBlockEntities = new ArrayList<>(); protected boolean isTickingBlockEntities = false; + protected boolean isUpdatingLights = false; protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; @@ -163,12 +164,12 @@ public abstract class CEWorld { return worldDataStorage; } - public void sectionLightUpdated(SectionPos pos) { - this.updatedSectionSet.add(pos); - } - public void sectionLightUpdated(Collection pos) { - this.updatedSectionSet.addAll(pos); + if (this.isUpdatingLights) { + this.pendingLightSections.addAll(pos); + } else { + this.lightSections.addAll(pos); + } } public WorldHeight worldHeight() { diff --git a/gradle.properties b/gradle.properties index 455403f8e..766a33a0c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.13 +project_version=0.0.62.14 config_version=45 lang_version=25 project_group=net.momirealms From 987d6c0e3593f9527980ebec92889938970b6935 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 6 Sep 2025 14:12:06 +0800 Subject: [PATCH 033/226] =?UTF-8?q?fix(bukkit):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=20MythicMobs=20=E7=89=A9=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 传递 Player 对象到 MythicMobs 以便解析占位符 --- .../compatibility/item/MythicMobsSource.java | 20 ++++++++++++++++++- ...currentUUID2ReferenceChainedHashTable.java | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MythicMobsSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MythicMobsSource.java index 67d974f20..8eb9e7d9c 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MythicMobsSource.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MythicMobsSource.java @@ -1,11 +1,18 @@ package net.momirealms.craftengine.bukkit.compatibility.item; +import io.lumine.mythic.api.adapters.AbstractPlayer; +import io.lumine.mythic.api.skills.SkillCaster; +import io.lumine.mythic.bukkit.BukkitAdapter; import io.lumine.mythic.bukkit.MythicBukkit; +import io.lumine.mythic.core.drops.DropMetadataImpl; import net.momirealms.craftengine.core.item.ExternalItemSource; import net.momirealms.craftengine.core.item.ItemBuildContext; +import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import java.util.Optional; + public class MythicMobsSource implements ExternalItemSource { private MythicBukkit mythicBukkit; @@ -20,7 +27,18 @@ public class MythicMobsSource implements ExternalItemSource { if (mythicBukkit == null || mythicBukkit.isClosed()) { this.mythicBukkit = MythicBukkit.inst(); } - return mythicBukkit.getItemManager().getItemStack(id); + return Optional.ofNullable(context.player()) + .map(p -> (Player) p.platformPlayer()) + .map(p -> { + AbstractPlayer target = BukkitAdapter.adapt(p); + SkillCaster caster = mythicBukkit.getSkillManager().getCaster(target); + DropMetadataImpl meta = new DropMetadataImpl(caster, target); + return mythicBukkit.getItemManager().getItem(id) + .map(i -> i.generateItemStack(meta, 1)) + .map(BukkitAdapter::adapt) + .orElse(null); + }) + .orElseGet(() -> mythicBukkit.getItemManager().getItemStack(id)); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java b/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java index 80ffcb701..f654ab07b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java @@ -19,6 +19,7 @@ import java.util.function.Predicate; * Concurrent hashtable implementation supporting mapping UUID values onto non-null Object * values with support for multiple writer and multiple reader threads. */ +@SuppressWarnings("unchecked") public class ConcurrentUUID2ReferenceChainedHashTable implements Iterable> { protected static final int DEFAULT_CAPACITY = 16; protected static final float DEFAULT_LOAD_FACTOR = 0.75f; From efe9070869c7a5822a32a6f35bf0c3dd6e98e4d4 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 6 Sep 2025 15:03:05 +0800 Subject: [PATCH 034/226] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0=20He?= =?UTF-8?q?adDatabase=20=E5=92=8C=20SX-Item=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/compatibility/build.gradle.kts | 5 +++ .../BukkitCompatibilityManager.java | 8 +++++ .../item/HeadDatabaseSource.java | 33 +++++++++++++++++++ .../compatibility/item/SXItemSource.java | 29 ++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 1ca637479..5d231402f 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -15,6 +15,7 @@ repositories { maven("https://repo.dmulloy2.net/repository/public/") // mcmmo required maven("https://repo.auxilor.io/repository/maven-public/") // eco maven("https://repo.hiusers.com/releases") // zaphkiel + maven("https://jitpack.io") // sxitem } dependencies { @@ -72,6 +73,10 @@ dependencies { compileOnly("ink.ptms:ZaphkielAPI:2.1.0") // WorldGuard compileOnly(files("${rootProject.rootDir}/libs/worldguard-bukkit-7.0.14-dist.jar")) + // HeadDatabase + compileOnly("com.arcaniax:HeadDatabase-API:1.3.2") + // SXItem + compileOnly("com.github.Saukiya:SX-Item:4.4.6") } java { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index 3f6d91917..d4cf8bebb 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -263,6 +263,14 @@ public class BukkitCompatibilityManager implements CompatibilityManager { itemManager.registerExternalItemSource(new ZaphkielSource()); logHook("Zaphkiel"); } + if (this.isPluginEnabled("HeadDatabase")) { + itemManager.registerExternalItemSource(new HeadDatabaseSource()); + logHook("HeadDatabase"); + } + if (this.isPluginEnabled("SX-Item")) { + itemManager.registerExternalItemSource(new SXItemSource()); + logHook("SX-Item"); + } } private Plugin getPlugin(String name) { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java new file mode 100644 index 000000000..5c3bb87c6 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.bukkit.compatibility.item; + +import net.momirealms.craftengine.core.item.ExternalItemSource; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import me.arcaniax.hdb.api.HeadDatabaseAPI; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class HeadDatabaseSource implements ExternalItemSource { + private HeadDatabaseAPI api; + + @Override + public String plugin() { + return "headdatabase"; + } + + @Nullable + @Override + public ItemStack build(String id, ItemBuildContext context) { + if (api == null) { + api = new HeadDatabaseAPI(); + } + return api.getItemHead(id); + } + + @Override + public String id(ItemStack item) { + if (api == null) { + api = new HeadDatabaseAPI(); + } + return api.getItemID(item); + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java new file mode 100644 index 000000000..ca7ddbc59 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java @@ -0,0 +1,29 @@ +package net.momirealms.craftengine.bukkit.compatibility.item; + +import github.saukiya.sxitem.SXItem; +import net.momirealms.craftengine.core.item.ExternalItemSource; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class SXItemSource implements ExternalItemSource { + + @Override + public String plugin() { + return "sxitem"; + } + + @Nullable + @Override + public ItemStack build(String id, ItemBuildContext context) { + return SXItem.getItemManager().getItem(id, Optional.ofNullable(context.player()).map(p -> (Player) p.platformPlayer()).orElse(null)); + } + + @Override + public String id(ItemStack item) { + return SXItem.getItemManager().getItemKey(item); + } +} From b1b7a15f228b6205265f35f2ef85f27d5ed91511 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 6 Sep 2025 15:28:27 +0800 Subject: [PATCH 035/226] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0=20Sl?= =?UTF-8?q?imefun=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/compatibility/build.gradle.kts | 4 ++- .../BukkitCompatibilityManager.java | 4 +++ .../compatibility/item/SlimefunSource.java | 28 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SlimefunSource.java diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 5d231402f..520da0749 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -15,7 +15,7 @@ repositories { maven("https://repo.dmulloy2.net/repository/public/") // mcmmo required maven("https://repo.auxilor.io/repository/maven-public/") // eco maven("https://repo.hiusers.com/releases") // zaphkiel - maven("https://jitpack.io") // sxitem + maven("https://jitpack.io") // sxitem slimefun } dependencies { @@ -77,6 +77,8 @@ dependencies { compileOnly("com.arcaniax:HeadDatabase-API:1.3.2") // SXItem compileOnly("com.github.Saukiya:SX-Item:4.4.6") + // Slimefun + compileOnly("io.github.Slimefun:Slimefun4:RC-32") } java { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index d4cf8bebb..b57cd1405 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -271,6 +271,10 @@ public class BukkitCompatibilityManager implements CompatibilityManager { itemManager.registerExternalItemSource(new SXItemSource()); logHook("SX-Item"); } + if (this.isPluginEnabled("Slimefun")) { + itemManager.registerExternalItemSource(new SlimefunSource()); + logHook("Slimefun"); + } } private Plugin getPlugin(String name) { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SlimefunSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SlimefunSource.java new file mode 100644 index 000000000..d9c73986e --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SlimefunSource.java @@ -0,0 +1,28 @@ +package net.momirealms.craftengine.bukkit.compatibility.item; + +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; +import net.momirealms.craftengine.core.item.ExternalItemSource; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class SlimefunSource implements ExternalItemSource { + + @Override + public String plugin() { + return "slimefun"; + } + + @Nullable + @Override + public ItemStack build(String id, ItemBuildContext context) { + return Optional.ofNullable(SlimefunItem.getById(id)).map(SlimefunItem::getItem).orElse(null); + } + + @Override + public String id(ItemStack item) { + return Optional.ofNullable(SlimefunItem.getByItem(item)).map(SlimefunItem::getId).orElse(null); + } +} From 66fd22ff49ae6d385ad8a14e0df33e6d8df78022 Mon Sep 17 00:00:00 2001 From: Yang Seong Mo Date: Sat, 6 Sep 2025 18:53:15 +0900 Subject: [PATCH 036/226] feat: make AsyncResourcePackCacheEvent --- .../event/AsyncResourcePackCacheEvent.java | 31 +++++++++++++++ .../bukkit/pack/BukkitPackManager.java | 16 ++++++-- .../core/pack/AbstractPackManager.java | 29 +++++++------- .../craftengine/core/pack/PackCacheData.java | 39 +++++++++++++++++++ 4 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java new file mode 100644 index 000000000..56776f611 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java @@ -0,0 +1,31 @@ +package net.momirealms.craftengine.bukkit.api.event; + +import net.momirealms.craftengine.core.pack.PackCacheData; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class AsyncResourcePackCacheEvent extends Event { + private static final HandlerList HANDLER_LIST = new HandlerList(); + private final PackCacheData cacheData; + + public AsyncResourcePackCacheEvent(@NotNull PackCacheData cacheData) { + super(true); + this.cacheData = cacheData; + } + + @NotNull + public PackCacheData cacheData() { + return cacheData; + } + + @NotNull + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + @NotNull + public HandlerList getHandlers() { + return getHandlerList(); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 5b2e36c6b..823071d54 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.pack; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackCacheEvent; import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.command.feature.ReloadCommand; @@ -28,10 +29,17 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { private final BukkitCraftEngine plugin; public BukkitPackManager(BukkitCraftEngine plugin) { - super(plugin, (rf, zp) -> { - AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp); - EventUtils.fireAndForget(endEvent); - }); + super( + plugin, + (cd) -> { + AsyncResourcePackCacheEvent cacheEvent = new AsyncResourcePackCacheEvent(cd); + EventUtils.fireAndForget(cacheEvent); + }, + (rf, zp) -> { + AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp); + EventUtils.fireAndForget(endEvent); + } + ); this.plugin = plugin; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index a080cae06..db2c41535 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -85,6 +85,7 @@ public abstract class AbstractPackManager implements PackManager { } private final CraftEngine plugin; + private final Consumer cacheEventDispatcher; private final BiConsumer eventDispatcher; private final Map loadedPacks = new HashMap<>(); private final Map sectionParsers = new HashMap<>(); @@ -94,8 +95,9 @@ public abstract class AbstractPackManager implements PackManager { protected BiConsumer zipGenerator; protected ResourcePackHost resourcePackHost; - public AbstractPackManager(CraftEngine plugin, BiConsumer eventDispatcher) { + public AbstractPackManager(CraftEngine plugin, Consumer cacheEventDispatcher, BiConsumer eventDispatcher) { this.plugin = plugin; + this.cacheEventDispatcher = cacheEventDispatcher; this.eventDispatcher = eventDispatcher; this.zipGenerator = (p1, p2) -> {}; Path resourcesFolder = this.plugin.dataFolderPath().resolve("resources"); @@ -267,7 +269,9 @@ public abstract class AbstractPackManager implements PackManager { @Override public void initCachedAssets() { try { - this.updateCachedAssets(null); + PackCacheData cacheData = new PackCacheData(plugin); + this.cacheEventDispatcher.accept(cacheData); + this.updateCachedAssets(cacheData, null); } catch (Exception e) { this.plugin.logger().warn("Failed to update cached assets", e); } @@ -658,11 +662,15 @@ public abstract class AbstractPackManager implements PackManager { this.plugin.logger().info("Generating resource pack..."); long time1 = System.currentTimeMillis(); + // Create cache data + PackCacheData cacheData = new PackCacheData(plugin); + this.cacheEventDispatcher.accept(cacheData); + // get the target location try (FileSystem fs = Jimfs.newFileSystem(Configuration.forCurrentPlatform())) { // firstly merge existing folders Path generatedPackPath = fs.getPath("resource_pack"); - List>> duplicated = this.updateCachedAssets(fs); + List>> duplicated = this.updateCachedAssets(cacheData, fs); if (!duplicated.isEmpty()) { plugin.logger().severe(AdventureHelper.miniMessage().stripTags(TranslationManager.instance().miniMessageTranslation("warning.config.pack.duplicated_files"))); int x = 1; @@ -2136,7 +2144,7 @@ public abstract class AbstractPackManager implements PackManager { } } - private List>> updateCachedAssets(@Nullable FileSystem fs) throws IOException { + private List>> updateCachedAssets(@NotNull PackCacheData generateData, @Nullable FileSystem fs) throws IOException { Map> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); Map previousFiles = this.cachedAssetFiles; this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); @@ -2146,10 +2154,7 @@ public abstract class AbstractPackManager implements PackManager { .filter(Pack::enabled) .map(Pack::resourcePackFolder) .toList()); - folders.addAll(Config.foldersToMerge().stream() - .map(it -> this.plugin.dataFolderPath().getParent().resolve(it)) - .filter(Files::exists) - .toList()); + folders.addAll(generateData.externalFolders()); for (Path sourceFolder : folders) { if (Files.exists(sourceFolder)) { Files.walkFileTree(sourceFolder, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() { @@ -2161,13 +2166,7 @@ public abstract class AbstractPackManager implements PackManager { }); } } - List externalZips = Config.zipsToMerge().stream() - .map(it -> this.plugin.dataFolderPath().getParent().resolve(it)) - .filter(Files::exists) - .filter(Files::isRegularFile) - .filter(file -> file.getFileName().toString().endsWith(".zip")) - .toList(); - for (Path zip : externalZips) { + for (Path zip : generateData.externalZips()) { processZipFile(zip, zip.getParent(), fs, conflictChecker, previousFiles); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java new file mode 100644 index 000000000..11a5379c6 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java @@ -0,0 +1,39 @@ +package net.momirealms.craftengine.core.pack; + +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.stream.Collectors; + +public class PackCacheData { + + private final Set externalZips; + private final Set externalFolders; + + PackCacheData(@NotNull CraftEngine plugin) { + externalFolders = Config.foldersToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .collect(Collectors.toSet()); + externalZips = Config.zipsToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".zip")) + .collect(Collectors.toSet()); + } + + @NotNull + public Set externalFolders() { + return externalFolders; + } + + @NotNull + public Set externalZips() { + return externalZips; + } +} From 275a5996ea7366aa5a378460a6877a1034b294b5 Mon Sep 17 00:00:00 2001 From: Yang Seong Mo Date: Sat, 6 Sep 2025 19:00:44 +0900 Subject: [PATCH 037/226] refactor: renaming --- .../craftengine/core/pack/AbstractPackManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index db2c41535..8ac111463 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -2144,7 +2144,7 @@ public abstract class AbstractPackManager implements PackManager { } } - private List>> updateCachedAssets(@NotNull PackCacheData generateData, @Nullable FileSystem fs) throws IOException { + private List>> updateCachedAssets(@NotNull PackCacheData cacheData, @Nullable FileSystem fs) throws IOException { Map> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); Map previousFiles = this.cachedAssetFiles; this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); @@ -2154,7 +2154,7 @@ public abstract class AbstractPackManager implements PackManager { .filter(Pack::enabled) .map(Pack::resourcePackFolder) .toList()); - folders.addAll(generateData.externalFolders()); + folders.addAll(cacheData.externalFolders()); for (Path sourceFolder : folders) { if (Files.exists(sourceFolder)) { Files.walkFileTree(sourceFolder, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() { @@ -2166,7 +2166,7 @@ public abstract class AbstractPackManager implements PackManager { }); } } - for (Path zip : generateData.externalZips()) { + for (Path zip : cacheData.externalZips()) { processZipFile(zip, zip.getParent(), fs, conflictChecker, previousFiles); } From f8618d18cb4c0eab159799a0fe93c4825c38a3be Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 03:41:45 +0800 Subject: [PATCH 038/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=96=E7=95=8C?= =?UTF-8?q?=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/world/BukkitWorldManager.java | 32 +++++++++++++++---- common-files/src/main/resources/config.yml | 2 +- .../craftengine/core/world/CEWorld.java | 18 ++++++----- .../serialization/DefaultChunkSerializer.java | 2 +- 4 files changed, 37 insertions(+), 17 deletions(-) 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 18050cd49..2a90f6333 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 @@ -39,6 +39,8 @@ public class BukkitWorldManager implements WorldManager, Listener { private CEWorld[] worldArray; private StorageAdaptor storageAdaptor; private boolean initialized = false; + private UUID lastWorldUUID = null; + private CEWorld lastWorld = null; public BukkitWorldManager(BukkitCraftEngine plugin) { instance = this; @@ -65,7 +67,15 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public CEWorld getWorld(UUID uuid) { - return this.worlds.get(uuid); + if (uuid == this.lastWorldUUID || uuid.equals(this.lastWorldUUID)) { + return this.lastWorld; + } + CEWorld world = this.worlds.get(uuid); + if (world != null) { + this.lastWorldUUID = uuid; + this.lastWorld = world; + } + return world; } @Override @@ -114,6 +124,8 @@ public class BukkitWorldManager implements WorldManager, Listener { } } this.worlds.clear(); + this.lastWorld = null; + this.lastWorldUUID = null; } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -123,9 +135,10 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void loadWorld(net.momirealms.craftengine.core.world.World world) { - if (this.worlds.containsKey(world.uuid())) return; + UUID uuid = world.uuid(); + if (this.worlds.containsKey(uuid)) return; CEWorld ceWorld = new BukkitCEWorld(world, this.storageAdaptor); - this.worlds.put(world.uuid(), ceWorld); + this.worlds.put(uuid, ceWorld); this.resetWorldArray(); for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { handleChunkLoad(ceWorld, chunk); @@ -135,8 +148,9 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void loadWorld(CEWorld world) { - if (this.worlds.containsKey(world.world().uuid())) return; - this.worlds.put(world.world().uuid(), world); + UUID uuid = world.world().uuid(); + if (this.worlds.containsKey(uuid)) return; + this.worlds.put(uuid, world); this.resetWorldArray(); for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) { handleChunkLoad(world, chunk); @@ -163,8 +177,8 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void unloadWorld(net.momirealms.craftengine.core.world.World world) { - CEWorld ceWorld; - ceWorld = this.worlds.remove(world.uuid()); + UUID uuid = world.uuid(); + CEWorld ceWorld = this.worlds.remove(uuid); if (ceWorld == null) { return; } @@ -173,6 +187,10 @@ public class BukkitWorldManager implements WorldManager, Listener { for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { handleChunkUnload(ceWorld, chunk); } + if (uuid.equals(this.lastWorldUUID)) { + this.lastWorld = null; + this.lastWorldUUID = null; + } try { ceWorld.worldDataStorage().close(); } catch (IOException e) { diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 5c39b74bf..5ef03acb9 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -66,9 +66,9 @@ resource-pack: remove-tinted-leaves-particle: true merge-external-folders: - ModelEngine/resource pack - - BetterModel/build merge-external-zip-files: - CustomNameplates/resourcepack.zip + - "BetterModel/build.zip" exclude-file-extensions: ["md", "psd", "bbmodel", "db", "ini"] # Exclude the shaders when generating the resource pack exclude-core-shaders: false diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 32abf2075..fafbb6da3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -78,7 +78,7 @@ public abstract class CEWorld { for (ConcurrentLong2ReferenceChainedHashTable.TableEntry entry : this.loadedChunkMap.entrySet()) { CEChunk chunk = entry.getValue(); if (chunk.dirty()) { - worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), chunk); + this.worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), chunk); chunk.setDirty(false); } } @@ -205,15 +205,17 @@ public abstract class CEWorld { this.tickingBlockEntities.addAll(this.pendingTickingBlockEntities); this.pendingTickingBlockEntities.clear(); } - ReferenceOpenHashSet toRemove = new ReferenceOpenHashSet<>(); - for (TickingBlockEntity blockEntity : this.tickingBlockEntities) { - if (blockEntity.isValid()) { - blockEntity.tick(); - } else { - toRemove.add(blockEntity); + if (!this.tickingBlockEntities.isEmpty()) { + ReferenceOpenHashSet toRemove = new ReferenceOpenHashSet<>(); + for (TickingBlockEntity blockEntity : this.tickingBlockEntities) { + if (blockEntity.isValid()) { + blockEntity.tick(); + } else { + toRemove.add(blockEntity); + } } + this.tickingBlockEntities.removeAll(toRemove); } - this.tickingBlockEntities.removeAll(toRemove); this.isTickingBlockEntities = false; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index 4ef8a7955..ff195c4d3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -32,7 +32,7 @@ public final class DefaultChunkSerializer { } ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.blockEntityRenderers()); if (!blockEntityRenders.isEmpty()) { - chunkNbt.put("block_entity_renders", blockEntityRenders); + chunkNbt.put("block_entity_renderers", blockEntityRenders); } return chunkNbt; } From 9ddda3454c23a9b86e7483bb6e30a4bd540b66b0 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 04:18:57 +0800 Subject: [PATCH 039/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=AD=98=E5=82=A8=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/HeadDatabaseSource.java | 2 +- .../entity/SimpleStorageBlockEntity.java | 42 +++++++++++++------ .../bukkit/pack/BukkitPackManager.java | 2 +- .../plugin/injector/WorldStorageInjector.java | 3 +- common-files/src/main/resources/config.yml | 4 +- .../core/pack/AbstractPackManager.java | 10 ++--- .../craftengine/core/pack/PackCacheData.java | 9 ++-- gradle.properties | 4 +- 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java index 5c3bb87c6..90f8763aa 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/HeadDatabaseSource.java @@ -1,8 +1,8 @@ package net.momirealms.craftengine.bukkit.compatibility.item; +import me.arcaniax.hdb.api.HeadDatabaseAPI; import net.momirealms.craftengine.core.item.ExternalItemSource; import net.momirealms.craftengine.core.item.ItemBuildContext; -import me.arcaniax.hdb.api.HeadDatabaseAPI; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index 26541c363..742295096 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -15,10 +15,12 @@ import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.ListTag; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.Bukkit; import org.bukkit.GameEvent; import org.bukkit.GameMode; @@ -29,6 +31,7 @@ import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Map; import java.util.Optional; public class SimpleStorageBlockEntity extends BlockEntity { @@ -52,14 +55,21 @@ public class SimpleStorageBlockEntity extends BlockEntity { @Nullable ItemStack[] storageContents = this.inventory.getStorageContents(); for (int i = 0; i < storageContents.length; i++) { if (storageContents[i] != null) { - int slot = i; - CoreReflections.instance$ItemStack$CODEC.encodeStart(MRegistryOps.SPARROW_NBT, FastNMS.INSTANCE.field$CraftItemStack$handle(storageContents[i])) - .ifSuccess(success -> { - CompoundTag itemTag = (CompoundTag) success; - itemTag.putInt("slot", slot); - itemsTag.add(itemTag); - }) - .ifError(error -> CraftEngine.instance().logger().severe("Error while saving storage item: " + error)); + if (VersionHelper.isOrAbove1_20_5()) { + int slot = i; + CoreReflections.instance$ItemStack$CODEC.encodeStart(MRegistryOps.SPARROW_NBT, FastNMS.INSTANCE.field$CraftItemStack$handle(storageContents[i])) + .ifSuccess(success -> { + CompoundTag itemTag = (CompoundTag) success; + itemTag.putInt("slot", slot); + itemsTag.add(itemTag); + }) + .ifError(error -> CraftEngine.instance().logger().severe("Error while saving storage item: " + error)); + } else { + Object nmsTag = FastNMS.INSTANCE.method$itemStack$save(FastNMS.INSTANCE.field$CraftItemStack$handle(storageContents[i]), FastNMS.INSTANCE.constructor$CompoundTag()); + CompoundTag itemTag = (CompoundTag) MRegistryOps.NBT.convertTo(MRegistryOps.SPARROW_NBT, nmsTag); + itemTag.putInt("slot", i); + itemsTag.add(itemTag); + } } } tag.put("items", itemsTag); @@ -68,16 +78,24 @@ public class SimpleStorageBlockEntity extends BlockEntity { @Override public void loadCustomData(CompoundTag tag) { ListTag itemsTag = Optional.ofNullable(tag.getList("items")).orElseGet(ListTag::new); + ItemStack[] storageContents = new ItemStack[this.behavior.rows() * 9]; for (int i = 0; i < itemsTag.size(); i++) { CompoundTag itemTag = itemsTag.getCompound(i); int slot = itemTag.getInt("slot"); - if (slot < 0 || slot >= this.behavior.rows() * 9) { + if (slot < 0 || slot >= storageContents.length) { continue; } - CoreReflections.instance$ItemStack$CODEC.parse(MRegistryOps.SPARROW_NBT, itemTag) - .resultOrPartial((s) -> CraftEngine.instance().logger().severe("Tried to load invalid item: '" + itemTag + "'. " + s)) - .ifPresent(nmsStack -> this.inventory.setItem(slot, FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsStack))); + if (VersionHelper.isOrAbove1_20_5()) { + CoreReflections.instance$ItemStack$CODEC.parse(MRegistryOps.SPARROW_NBT, itemTag) + .resultOrPartial((s) -> CraftEngine.instance().logger().severe("Tried to load invalid item: '" + itemTag + "'. " + s)) + .ifPresent(nmsStack -> storageContents[slot] = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsStack)); + } else { + Object nmsTag = MRegistryOps.SPARROW_NBT.convertTo(MRegistryOps.NBT, itemTag); + Object itemStack = FastNMS.INSTANCE.method$ItemStack$of(nmsTag); + storageContents[slot] = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack); + } } + this.inventory.setStorageContents(storageContents); } public Inventory inventory() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 823071d54..6acebe428 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -107,7 +107,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { return; } if (dataList.size() == 1) { - ResourcePackDownloadData data = dataList.get(0); + ResourcePackDownloadData data = dataList.getFirst(); player.sendPacket(ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1()), true); player.addResourcePackUUID(data.uuid()); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 71e043a19..2b299b534 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -221,6 +221,7 @@ public final class WorldStorageInjector { CESection section = holder.ceSection(); if (newState instanceof DelegatingBlockState delegatingBlockState) { ImmutableBlockState newImmutableBlockState = delegatingBlockState.blockState(); + if (newImmutableBlockState == null) return; ImmutableBlockState previousImmutableBlockState = section.setBlockState(x, y, z, newImmutableBlockState); if (previousImmutableBlockState == newImmutableBlockState) return; // 处理 自定义块到自定义块或原版块到自定义块 @@ -282,7 +283,7 @@ public final class WorldStorageInjector { // 那么应该清空自定义块 ImmutableBlockState previous = section.setBlockState(x, y, z, EmptyBlock.STATE); // 处理 自定义块 -> 原版块 - if (!previous.isEmpty()) { + if (previous != null && !previous.isEmpty()) { CEChunk chunk = holder.ceChunk(); chunk.setDirty(true); if (previous.hasBlockEntity()) { diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 5ef03acb9..2c0c66aad 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -65,9 +65,9 @@ resource-pack: # Remove 1.21.5+ tinted_leaves particles remove-tinted-leaves-particle: true merge-external-folders: - - ModelEngine/resource pack + - "ModelEngine/resource pack" merge-external-zip-files: - - CustomNameplates/resourcepack.zip + - "CustomNameplates/resourcepack.zip" - "BetterModel/build.zip" exclude-file-extensions: ["md", "psd", "bbmodel", "db", "ini"] # Exclude the shaders when generating the resource pack diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 8ac111463..c4dab03ce 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -86,7 +86,7 @@ public abstract class AbstractPackManager implements PackManager { private final CraftEngine plugin; private final Consumer cacheEventDispatcher; - private final BiConsumer eventDispatcher; + private final BiConsumer generationEventDispatcher; private final Map loadedPacks = new HashMap<>(); private final Map sectionParsers = new HashMap<>(); private final JsonObject vanillaAtlas; @@ -95,10 +95,10 @@ public abstract class AbstractPackManager implements PackManager { protected BiConsumer zipGenerator; protected ResourcePackHost resourcePackHost; - public AbstractPackManager(CraftEngine plugin, Consumer cacheEventDispatcher, BiConsumer eventDispatcher) { + public AbstractPackManager(CraftEngine plugin, Consumer cacheEventDispatcher, BiConsumer generationEventDispatcher) { this.plugin = plugin; this.cacheEventDispatcher = cacheEventDispatcher; - this.eventDispatcher = eventDispatcher; + this.generationEventDispatcher = generationEventDispatcher; this.zipGenerator = (p1, p2) -> {}; Path resourcesFolder = this.plugin.dataFolderPath().resolve("resources"); try { @@ -663,7 +663,7 @@ public abstract class AbstractPackManager implements PackManager { long time1 = System.currentTimeMillis(); // Create cache data - PackCacheData cacheData = new PackCacheData(plugin); + PackCacheData cacheData = new PackCacheData(this.plugin); this.cacheEventDispatcher.accept(cacheData); // get the target location @@ -725,7 +725,7 @@ public abstract class AbstractPackManager implements PackManager { } long time4 = System.currentTimeMillis(); this.plugin.logger().info("Created resource pack zip file in " + (time4 - time3) + "ms"); - this.eventDispatcher.accept(generatedPackPath, finalPath); + this.generationEventDispatcher.accept(generatedPackPath, finalPath); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java index 11a5379c6..1b4d1f23e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java @@ -10,16 +10,15 @@ import java.util.Set; import java.util.stream.Collectors; public class PackCacheData { - private final Set externalZips; private final Set externalFolders; PackCacheData(@NotNull CraftEngine plugin) { - externalFolders = Config.foldersToMerge().stream() + this.externalFolders = Config.foldersToMerge().stream() .map(it -> plugin.dataFolderPath().getParent().resolve(it)) .filter(Files::exists) .collect(Collectors.toSet()); - externalZips = Config.zipsToMerge().stream() + this.externalZips = Config.zipsToMerge().stream() .map(it -> plugin.dataFolderPath().getParent().resolve(it)) .filter(Files::exists) .filter(Files::isRegularFile) @@ -29,11 +28,11 @@ public class PackCacheData { @NotNull public Set externalFolders() { - return externalFolders; + return this.externalFolders; } @NotNull public Set externalZips() { - return externalZips; + return this.externalZips; } } diff --git a/gradle.properties b/gradle.properties index 766a33a0c..b9cf6317c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.14 +project_version=0.0.62.15 config_version=45 lang_version=25 project_group=net.momirealms @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.75 +nms_helper_version=1.0.76 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 1bdc2902244911b384b5dbda32e64c5fd3071bfe Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 04:22:40 +0800 Subject: [PATCH 040/226] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=8E=A9=E5=AE=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/gui/BukkitGuiManager.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index afef50d62..40a39ed12 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -22,6 +22,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.MenuType; @@ -143,6 +144,21 @@ public class BukkitGuiManager implements GuiManager, Listener { simpleStorageBlockEntity.onPlayerClose(this.plugin.adapt(player)); } } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onInventoryClose(PlayerQuitEvent event) { + Player player = event.getPlayer(); + org.bukkit.inventory.Inventory inventory = player.getInventory(); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { + return; + } + if (!(inventory.getHolder() instanceof BlockEntityHolder holder)) { + return; + } + if (holder.blockEntity() instanceof SimpleStorageBlockEntity simpleStorageBlockEntity) { + simpleStorageBlockEntity.onPlayerClose(this.plugin.adapt(player)); + } + } public static BukkitGuiManager instance() { return instance; From ff4efa13a008f8ff548571877735bbc81615c87c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 05:50:51 +0800 Subject: [PATCH 041/226] =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=EF=BC=8C=E7=BB=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 21 ++++ .../bukkit/block/BukkitCustomBlock.java | 6 +- .../renderer/BukkitBlockEntityElement.java | 106 ++++++++++++++++++ .../renderer/BukkitBlockEntityRenderer.java | 81 +++++++++++++ .../bukkit/entity/data/DisplayEntityData.java | 8 +- .../furniture/BukkitFurnitureManager.java | 3 +- .../entity/furniture/hitbox/CustomHitBox.java | 2 +- .../furniture/hitbox/HappyGhastHitBox.java | 2 +- .../furniture/hitbox/InteractionHitBox.java | 2 +- .../furniture/hitbox/ShulkerHitBox.java | 2 +- .../bukkit/plugin/gui/BukkitGuiManager.java | 2 +- .../bukkit/world/BukkitCEWorld.java | 11 +- .../core/block/AbstractBlockManager.java | 29 ++++- .../core/block/AbstractCustomBlock.java | 15 ++- .../core/block/BlockStateAppearance.java | 13 +++ .../craftengine/core/block/CustomBlock.java | 2 +- .../core/block/ImmutableBlockState.java | 10 +- .../entity/render/BlockEntityElement.java | 32 ++++++ .../entity/render/BlockEntityRenderer.java | 9 -- .../render/BlockEntityRendererConfig.java | 86 +------------- .../furniture/AbstractFurnitureManager.java | 10 +- .../core/entity/furniture/HitBoxFactory.java | 5 +- .../craftengine/core/item/ItemSettings.java | 10 +- .../core/item/modifier/DyedColorModifier.java | 3 +- .../model/generation/display/DisplayMeta.java | 7 +- .../craftengine/core/util/MiscUtils.java | 36 ------ .../core/util/ResourceConfigUtils.java | 38 +++++++ gradle.properties | 2 +- 28 files changed, 377 insertions(+), 176 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java 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 f32cb3a5b..c72d5abe3 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,6 +7,8 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitBlockEntityElement; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator; @@ -23,7 +25,10 @@ import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.bukkit.util.TagUtils; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; import net.momirealms.craftengine.core.block.parser.BlockStateParser; +import net.momirealms.craftengine.core.entity.Billboard; +import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; @@ -684,4 +689,20 @@ public final class BukkitBlockManager extends AbstractBlockManager { } return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)) != MBlocks.AIR; } + + @Override + protected BlockEntityElement createBlockEntityElement(Map arguments) { + Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "")); + return new BukkitBlockEntityElement( + LazyReference.lazyReference(() -> BukkitItemManager.instance().createWrappedItem(itemId, null)), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), + ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), + ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"), + ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)), + Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)) + ); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java index 84f1cbfe3..c70387f2e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java @@ -39,7 +39,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { @NotNull Key id, @NotNull Holder.Reference holder, @NotNull Map> properties, - @NotNull Map appearances, + @NotNull Map appearances, @NotNull Map variantMapper, @NotNull BlockSettings settings, @NotNull Map>> events, @@ -200,7 +200,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { public static class BuilderImpl implements Builder { protected final Key id; protected Map> properties; - protected Map appearances; + protected Map appearances; protected Map variantMapper; protected BlockSettings settings; protected List> behavior; @@ -218,7 +218,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { } @Override - public Builder appearances(Map appearances) { + public Builder appearances(Map appearances) { this.appearances = appearances; return this; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java new file mode 100644 index 000000000..93c1ff950 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java @@ -0,0 +1,106 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer; + +import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; +import net.momirealms.craftengine.core.entity.Billboard; +import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.util.LazyReference; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitBlockEntityElement implements BlockEntityElement { + private final LazyReference> lazyMetadataPacket; + private final LazyReference> item; + private final Vector3f scale; + private final Vector3f position; + private final Vector3f translation; + private final float xRot; + private final float yRot; + private final Quaternionf rotation; + private final ItemDisplayContext displayContext; + private final Billboard billboard; + + public BukkitBlockEntityElement(LazyReference> item, + Vector3f scale, + Vector3f position, + Vector3f translation, + float xRot, + float yRot, + Quaternionf rotation, + ItemDisplayContext displayContext, + Billboard billboard) { + this.item = item; + this.scale = scale; + this.position = position; + this.translation = translation; + this.xRot = xRot; + this.yRot = yRot; + this.rotation = rotation; + this.displayContext = displayContext; + this.billboard = billboard; + this.lazyMetadataPacket = LazyReference.lazyReference(() -> { + List dataValues = new ArrayList<>(); + ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.get().getLiteralObject(), dataValues); + ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); + ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); + ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); + ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); + ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues); + return dataValues; + }); + } + + @Override + public Item item() { + return this.item.get(); + } + + @Override + public Vector3f scale() { + return this.scale; + } + + @Override + public Vector3f translation() { + return this.translation; + } + + @Override + public Vector3f position() { + return this.position; + } + + @Override + public float yRot() { + return this.yRot; + } + + @Override + public float xRot() { + return this.xRot; + } + + @Override + public Billboard billboard() { + return billboard; + } + + @Override + public ItemDisplayContext displayContext() { + return displayContext; + } + + @Override + public Quaternionf rotation() { + return rotation; + } + + @Override + public LazyReference> metadataValues() { + return this.lazyMetadataPacket; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java new file mode 100644 index 000000000..d7f354018 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java @@ -0,0 +1,81 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.BlockPos; +import org.joml.Vector3f; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class BukkitBlockEntityRenderer extends BlockEntityRenderer { + private final Object cachedSpawnPacket; + private final Object cachedDespawnPacket; + private final WeakReference chunkHolder; + + public BukkitBlockEntityRenderer(WeakReference chunkHolder, + BlockEntityRendererConfig config, + BlockPos pos) { + this.chunkHolder = chunkHolder; + BlockEntityElement[] elements = config.elements(); + IntList ids = new IntArrayList(elements.length); + List spawnPackets = new ArrayList<>(elements.length); + for (BlockEntityElement element : elements) { + int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); + Vector3f position = element.position(); + spawnPackets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, + element.xRot(), element.yRot(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 + )); + spawnPackets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket( + entityId, element.metadataValues().get() + )); + ids.add(entityId); + } + this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(spawnPackets); + this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(ids); + } + + @Override + public void despawn() { + List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); + if (players.isEmpty()) return; + for (Object player : players) { + FastNMS.INSTANCE.method$ServerPlayerConnection$send( + FastNMS.INSTANCE.field$Player$connection(player), + this.cachedDespawnPacket + ); + } + } + + @Override + public void spawn() { + List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); + if (players.isEmpty()) return; + for (Object player : players) { + FastNMS.INSTANCE.method$ServerPlayerConnection$send( + FastNMS.INSTANCE.field$Player$connection(player), + this.cachedSpawnPacket + ); + } + } + + @Override + public void spawn(Player player) { + player.sendPacket(this.cachedSpawnPacket, false); + } + + @Override + public void despawn(Player player) { + player.sendPacket(this.cachedDespawnPacket, false); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/DisplayEntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/DisplayEntityData.java index a052feedb..b5f4bcd94 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/DisplayEntityData.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/DisplayEntityData.java @@ -15,10 +15,10 @@ public class DisplayEntityData extends BaseEntityData { public static final DisplayEntityData TransformationInterpolationDuration = of(DisplayEntityData.class, EntityDataValue.Serializers$INT, 0, VersionHelper.isOrAbove1_20_2()); public static final DisplayEntityData PositionRotationInterpolationDuration = of(DisplayEntityData.class, EntityDataValue.Serializers$INT, 0, VersionHelper.isOrAbove1_20_2()); - public static final DisplayEntityData Translation = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(0f), true); - public static final DisplayEntityData Scale = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(1f), true); - public static final DisplayEntityData RotationLeft = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true); - public static final DisplayEntityData RotationRight = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true); + public static final DisplayEntityData Translation = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(0f), true); + public static final DisplayEntityData Scale = of(DisplayEntityData.class, EntityDataValue.Serializers$VECTOR3, new Vector3f(1f), true); + public static final DisplayEntityData RotationLeft = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true); + public static final DisplayEntityData RotationRight = of(DisplayEntityData.class, EntityDataValue.Serializers$QUATERNION, new Quaternionf(0f, 0f, 0f, 1f), true); /** * Billboard Constraints (0 = FIXED, 1 = VERTICAL, 2 = HORIZONTAL, 3 = CENTER) */ diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index d9fb9c01d..3a8f53497 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; import org.bukkit.*; @@ -350,7 +351,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { plugin.logger().warn("Failed to get vector3f for player " + player.getName() + "'s seat"); return; } - Vector3f seatPos = MiscUtils.getAsVector3f(vector3f, "seat"); + Vector3f seatPos = ResourceConfigUtils.getAsVector3f(vector3f, "seat"); furniture.removeOccupiedSeat(seatPos); if (player.getVehicle() != null) return; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java index b76dcd24e..763c9a654 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java @@ -84,7 +84,7 @@ public class CustomHitBox extends AbstractHitBox { @Override public HitBox create(Map arguments) { - Vector3f position = MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"); + Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"); float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale"); String type = (String) arguments.getOrDefault("entity-type", "slime"); EntityType entityType = Registry.ENTITY_TYPE.get(new NamespacedKey("minecraft", type)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java index 4e67ffd02..fde041616 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java @@ -124,7 +124,7 @@ public class HappyGhastHitBox extends AbstractHitBox { boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building"); return new HappyGhastHitBox( HitBoxFactory.getSeats(arguments), - MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"), scale, canUseOn, blocksBuilding, canBeHitByProjectile, hardCollision ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java index 3fad43d0d..cd0d1a523 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java @@ -93,7 +93,7 @@ public class InteractionHitBox extends AbstractHitBox { @Override public HitBox create(Map arguments) { - Vector3f position = MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"); + Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"); float width; float height; if (arguments.containsKey("scale")) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 96db4da42..5f72065db 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -280,7 +280,7 @@ public class ShulkerHitBox extends AbstractHitBox { @Override public HitBox create(Map arguments) { - Vector3f position = MiscUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"); + Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"); float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", "1"), "scale"); byte peek = (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("peek", 0), "peek"); Direction directionEnum = Optional.ofNullable(arguments.get("direction")).map(it -> Direction.valueOf(it.toString().toUpperCase(Locale.ENGLISH))).orElse(Direction.UP); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index 40a39ed12..e01f4d056 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -144,7 +144,7 @@ public class BukkitGuiManager implements GuiManager, Listener { simpleStorageBlockEntity.onPlayerClose(this.plugin.adapt(player)); } } - + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) public void onInventoryClose(PlayerQuitEvent event) { Player player = event.getPlayer(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 93f3727cc..3ad6e4caa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.bukkit.world; +import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitBlockEntityRenderer; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.LightUtils; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; @@ -7,10 +9,13 @@ import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; +import java.lang.ref.WeakReference; + public class BukkitCEWorld extends CEWorld { public BukkitCEWorld(World world, StorageAdaptor adaptor) { @@ -41,6 +46,10 @@ public class BukkitCEWorld extends CEWorld { @Override public BlockEntityRenderer createBlockEntityRenderer(BlockEntityRendererConfig config, BlockPos pos) { - return null; + Object serverLevel = this.world.serverWorld(); + Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); + long chunkKey = ChunkPos.asLong(pos.x() >> 4, pos.z() >> 4); + Object chunkHolder = FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, chunkKey); + return new BukkitBlockEntityRenderer(new WeakReference<>(chunkHolder), config, pos); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index ce78d8bf1..2ecd81814 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -4,6 +4,9 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.loot.LootTable; @@ -166,6 +169,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected abstract CustomBlock.Builder platformBuilder(Key id); + protected abstract BlockEntityElement createBlockEntityElement(Map arguments); + public class BlockParser implements ConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; @@ -208,7 +213,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true)); // 读取基础外观配置 Map> properties; - Map appearances; + Map appearances; Map variants; // 读取states区域 Map stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow( @@ -221,11 +226,13 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 获取原版外观的注册表id int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow( stateSection.get("state"), "warning.config.block.state.missing_state")); + Optional blockEntityRenderer = parseBlockEntityRender(stateSection.get("entity-renderer")); + // 为原版外观赋予外观模型并检查模型冲突 this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(stateSection, "model", "models")); // 设置参数 properties = Map.of(); - appearances = Map.of("", appearanceId); + appearances = Map.of("", new BlockStateAppearance(appearanceId, blockEntityRenderer)); variants = Map.of("", new BlockStateVariant("", settings, getInternalBlockId(internalId, appearanceId))); } // 多方块状态 @@ -234,7 +241,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem appearances = parseBlockAppearances(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances")); variants = parseBlockVariants( ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"), - it -> appearances.getOrDefault(it, -1), settings + it -> { + BlockStateAppearance blockStateAppearance = appearances.get(it); + return blockStateAppearance == null ? -1 : blockStateAppearance.stateRegistryId(); + }, settings ); } @@ -278,18 +288,25 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return internalBlockRegistryId; } - private Map parseBlockAppearances(Map appearancesSection) { - Map appearances = new HashMap<>(); + private Map parseBlockAppearances(Map appearancesSection) { + Map appearances = new HashMap<>(); for (Map.Entry entry : appearancesSection.entrySet()) { Map appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow( appearanceSection.get("state"), "warning.config.block.state.missing_state")); this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(appearanceSection, "model", "models")); - appearances.put(entry.getKey(), appearanceId); + appearances.put(entry.getKey(), new BlockStateAppearance(appearanceId, parseBlockEntityRender(appearanceSection.get("entity-renderer")))); } return appearances; } + private Optional parseBlockEntityRender(Object arguments) { + if (arguments == null) return Optional.empty(); + List elements = ResourceConfigUtils.parseConfigAsList(arguments, AbstractBlockManager.this::createBlockEntityElement); + if (elements.isEmpty()) return Optional.empty(); + return Optional.of(new BlockEntityRendererConfig(elements.toArray(new BlockEntityElement[0]))); + } + @NotNull private Map> parseBlockProperties(Map propertiesSection) { Map> properties = new HashMap<>(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index 9a965a5ec..f69e2061d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -38,7 +38,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { @NotNull Key id, @NotNull Holder.Reference holder, @NotNull Map> properties, - @NotNull Map appearances, + @NotNull Map appearances, @NotNull Map variantMapper, @NotNull BlockSettings settings, @NotNull Map>> events, @@ -72,16 +72,21 @@ public abstract class AbstractCustomBlock implements CustomBlock { throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); } BlockStateVariant blockStateVariant = entry.getValue(); - int vanillaStateRegistryId = appearances.getOrDefault(blockStateVariant.appearance(), -1); + + BlockStateAppearance blockStateAppearance = appearances.getOrDefault(blockStateVariant.appearance(), BlockStateAppearance.INVALID); + int stateId; // This should never happen - if (vanillaStateRegistryId == -1) { - vanillaStateRegistryId = appearances.values().iterator().next(); + if (blockStateAppearance.isInvalid()) { + stateId = appearances.values().iterator().next().stateRegistryId(); + } else { + stateId = blockStateAppearance.stateRegistryId(); } // Late init states ImmutableBlockState state = possibleStates.getFirst(); state.setSettings(blockStateVariant.settings()); - state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); + state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(stateId)); state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); + blockStateAppearance.blockEntityRenderer().ifPresent(state::setEntityRenderer); } // double check if there's any invalid state diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java new file mode 100644 index 000000000..9bd5565f8 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.block; + +import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; + +import java.util.Optional; + +public record BlockStateAppearance(int stateRegistryId, Optional blockEntityRenderer) { + public static final BlockStateAppearance INVALID = new BlockStateAppearance(-1, Optional.empty()); + + public boolean isInvalid() { + return this.stateRegistryId < 0; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java index 13948d536..744d0f4c9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java @@ -45,7 +45,7 @@ public interface CustomBlock { Builder events(Map>> events); - Builder appearances(Map appearances); + Builder appearances(Map appearances); Builder behavior(List> behavior); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 9a3a2b65d..b733743a4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -32,7 +32,7 @@ public final class ImmutableBlockState extends BlockStateHolder { private BlockSettings settings; private BlockEntityType blockEntityType; @Nullable - private BlockEntityRendererConfig rendererConfig; + private BlockEntityRendererConfig renderer; ImmutableBlockState( Holder owner, @@ -71,7 +71,11 @@ public final class ImmutableBlockState extends BlockStateHolder { @Nullable public BlockEntityRendererConfig entityRenderer() { - return this.rendererConfig; + return this.renderer; + } + + public void setEntityRenderer(@Nullable BlockEntityRendererConfig rendererConfig) { + this.renderer = rendererConfig; } @Override @@ -94,7 +98,7 @@ public final class ImmutableBlockState extends BlockStateHolder { } public boolean hasBlockEntityRenderer() { - return this.rendererConfig != null; + return this.renderer != null; } public BlockStateWrapper customBlockState() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java new file mode 100644 index 000000000..07939e997 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.block.entity.render; + +import net.momirealms.craftengine.core.entity.Billboard; +import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.util.LazyReference; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.List; + +public interface BlockEntityElement { + Item item(); + + Vector3f scale(); + + Vector3f translation(); + + Vector3f position(); + + float yRot(); + + float xRot(); + + Billboard billboard(); + + ItemDisplayContext displayContext(); + + Quaternionf rotation(); + + LazyReference> metadataValues(); +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java index 6414fb396..2b1e5e7a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java @@ -3,15 +3,6 @@ package net.momirealms.craftengine.core.block.entity.render; import net.momirealms.craftengine.core.entity.player.Player; public abstract class BlockEntityRenderer { - private final int entityId; - - public BlockEntityRenderer(int entityId) { - this.entityId = entityId; - } - - public int entityId() { - return this.entityId; - } public abstract void spawn(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java index be4cdffbe..e49a3f164 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java @@ -1,88 +1,4 @@ package net.momirealms.craftengine.core.block.entity.render; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; -import net.momirealms.craftengine.core.item.Item; - -public class BlockEntityRendererConfig { - private final float yRot; - private final float xRot; - private final ItemDisplayContext displayContext; - private final Item item; - private final float scale; - - public BlockEntityRendererConfig(ItemDisplayContext displayContext, - float yRot, - float xRot, - Item item, - float scale) { - this.displayContext = displayContext; - this.yRot = yRot; - this.xRot = xRot; - this.item = item; - this.scale = scale; - } - - public ItemDisplayContext displayContext() { - return displayContext; - } - - public Item item() { - return item; - } - - public float xRot() { - return xRot; - } - - public float yRot() { - return yRot; - } - - public float scale() { - return scale; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private ItemDisplayContext displayContext = ItemDisplayContext.NONE; - private Item item; - private float xRot; - private float yRot; - private float scale = 1f; - - public Builder() { - } - - public Builder displayContext(ItemDisplayContext displayContext) { - this.displayContext = displayContext; - return this; - } - - public Builder item(Item item) { - this.item = item; - return this; - } - - public Builder xRot(float xRot) { - this.xRot = xRot; - return this; - } - - public Builder yRot(float yRot) { - this.yRot = yRot; - return this; - } - - public Builder scale(float scale) { - this.scale = scale; - return this; - } - - public BlockEntityRendererConfig build() { - return new BlockEntityRendererConfig(this.displayContext, this.yRot, this.xRot, this.item, this.scale); - } - } +public record BlockEntityRendererConfig(BlockEntityElement[] elements) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index abc819e6a..f343eb7ea 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -98,7 +98,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { // anchor type AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)); Map placementArguments = MiscUtils.castToMap(entry.getValue(), false); - Optional optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> MiscUtils.getAsVector3f(it, "loot-spawn-offset")); + Optional optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset")); // furniture display elements List elements = new ArrayList<>(); List> elementConfigs = (List>) placementArguments.getOrDefault("elements", List.of()); @@ -108,10 +108,10 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { .applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color")) .billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED)) .transform(ResourceConfigUtils.getOrDefault(element.get("transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) - .scale(MiscUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale")) - .position(MiscUtils.getAsVector3f(element.getOrDefault("position", "0"), "position")) - .translation(MiscUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation")) - .rotation(MiscUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation")) + .scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale")) + .position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position")) + .translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation")) + .rotation(ResourceConfigUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation")) .build(); elements.add(furnitureElement); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java index 3720eb9bd..1f06c8846 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.entity.furniture; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.List; import java.util.Map; @@ -15,8 +16,8 @@ public interface HitBoxFactory { return seats.stream() .map(arg -> { String[] split = arg.split(" "); - if (split.length == 1) return new Seat(MiscUtils.getAsVector3f(split[0], "seats"), 0, false); - return new Seat(MiscUtils.getAsVector3f(split[0], "seats"), Float.parseFloat(split[1]), true); + if (split.length == 1) return new Seat(ResourceConfigUtils.getAsVector3f(split[0], "seats"), 0, false); + return new Seat(ResourceConfigUtils.getAsVector3f(split[0], "seats"), Float.parseFloat(split[1]), true); }) .toArray(Seat[]::new); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java index f8d5ad125..b53902ccf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java @@ -399,9 +399,9 @@ public class ItemSettings { Key customTridentItemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("item"), "warning.config.item.settings.projectile.missing_item")); ItemDisplayContext displayType = ItemDisplayContext.valueOf(args.getOrDefault("display-transform", "NONE").toString().toUpperCase(Locale.ENGLISH)); Billboard billboard = Billboard.valueOf(args.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH)); - Vector3f translation = MiscUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation"); - Vector3f scale = MiscUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale"); - Quaternionf rotation = MiscUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation-left", "rotation"), "rotation-left"); + Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation"); + Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale"); + Quaternionf rotation = ResourceConfigUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation"), "rotation"); ProjectileType type = Optional.ofNullable(args.get("type")).map(String::valueOf).map(it -> ProjectileType.valueOf(it.toUpperCase(Locale.ENGLISH))).orElse(null); double range = ResourceConfigUtils.getAsDouble(args.getOrDefault("range", 1), "range"); return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range, type)); @@ -426,14 +426,14 @@ public class ItemSettings { if (value instanceof Integer i) { return settings -> settings.dyeColor(Color.fromDecimal(i)); } else { - return settings -> settings.dyeColor(Color.fromVector3f(MiscUtils.getAsVector3f(value, "dye-color"))); + return settings -> settings.dyeColor(Color.fromVector3f(ResourceConfigUtils.getAsVector3f(value, "dye-color"))); } })); registerFactory("firework-color", (value -> { if (value instanceof Integer i) { return settings -> settings.fireworkColor(Color.fromDecimal(i)); } else { - return settings -> settings.fireworkColor(Color.fromVector3f(MiscUtils.getAsVector3f(value, "firework-color"))); + return settings -> settings.fireworkColor(Color.fromVector3f(ResourceConfigUtils.getAsVector3f(value, "firework-color"))); } })); registerFactory("food", (value -> { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java index 2ce36e797..73cc1c0ae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java @@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.item.ItemDataModifierFactory; import net.momirealms.craftengine.core.util.Color; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; @@ -54,7 +55,7 @@ public class DyedColorModifier implements SimpleNetworkItemDataModifier { if (arg instanceof Integer integer) { return new DyedColorModifier<>(Color.fromDecimal(integer)); } else { - Vector3f vector3f = MiscUtils.getAsVector3f(arg, "dyed-color"); + Vector3f vector3f = ResourceConfigUtils.getAsVector3f(arg, "dyed-color"); return new DyedColorModifier<>(Color.fromVector3f(vector3f)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java index 31549e547..8b9b51f2e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.pack.model.generation.display; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.joml.Vector3f; import java.util.Map; @@ -10,15 +11,15 @@ public record DisplayMeta(Vector3f rotation, Vector3f translation, Vector3f scal public static DisplayMeta fromMap(Map map) { Vector3f rotation = null; if (map.containsKey("rotation")) { - rotation = MiscUtils.getAsVector3f(map.get("rotation"), "rotation"); + rotation = ResourceConfigUtils.getAsVector3f(map.get("rotation"), "rotation"); } Vector3f translation = null; if (map.containsKey("translation")) { - translation = MiscUtils.getAsVector3f(map.get("translation"), "translation"); + translation = ResourceConfigUtils.getAsVector3f(map.get("translation"), "translation"); } Vector3f scale = null; if (map.containsKey("scale")) { - scale = MiscUtils.getAsVector3f(map.get("scale"), "scale"); + scale = ResourceConfigUtils.getAsVector3f(map.get("scale"), "scale"); } return new DisplayMeta(rotation, translation, scale); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index ed0c90d9c..df5b8e924 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -78,42 +78,6 @@ public class MiscUtils { return List.of(); } - public static Vector3f getAsVector3f(Object o, String option) { - if (o == null) return new Vector3f(); - if (o instanceof List list && list.size() == 3) { - return new Vector3f(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString())); - } else { - String stringFormat = o.toString(); - String[] split = stringFormat.split(","); - if (split.length == 3) { - return new Vector3f(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2])); - } else if (split.length == 1) { - return new Vector3f(Float.parseFloat(split[0])); - } else { - throw new LocalizedResourceConfigException("warning.config.type.vector3f", stringFormat, option); - } - } - } - - public static Quaternionf getAsQuaternionf(Object o, String option) { - if (o == null) return new Quaternionf(); - if (o instanceof List list && list.size() == 4) { - return new Quaternionf(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString()), Float.parseFloat(list.get(3).toString())); - } else { - String stringFormat = o.toString(); - String[] split = stringFormat.split(","); - if (split.length == 4) { - return new Quaternionf(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2]), Float.parseFloat(split[3])); - } else if (split.length == 3) { - return QuaternionUtils.toQuaternionf((float) Math.toRadians(Float.parseFloat(split[2])), (float) Math.toRadians(Float.parseFloat(split[1])), (float) Math.toRadians(Float.parseFloat(split[0]))); - } else if (split.length == 1) { - return QuaternionUtils.toQuaternionf(0, (float) -Math.toRadians(Float.parseFloat(split[0])), 0); - } else { - throw new LocalizedResourceConfigException("warning.config.type.quaternionf", stringFormat, option); - } - } - } - @SuppressWarnings("unchecked") public static void deepMergeMaps(Map baseMap, Map mapToMerge) { for (Map.Entry entry : mapToMerge.entrySet()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index 9f240f7dd..b62402aa4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -4,6 +4,8 @@ import com.mojang.datafixers.util.Either; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; import java.util.*; import java.util.function.Function; @@ -223,4 +225,40 @@ public final class ResourceConfigUtils { } throw new LocalizedResourceConfigException("warning.config.type.map", String.valueOf(obj), option); } + + public static Vector3f getAsVector3f(Object o, String option) { + if (o == null) return new Vector3f(); + if (o instanceof List list && list.size() == 3) { + return new Vector3f(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString())); + } else { + String stringFormat = o.toString(); + String[] split = stringFormat.split(","); + if (split.length == 3) { + return new Vector3f(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2])); + } else if (split.length == 1) { + return new Vector3f(Float.parseFloat(split[0])); + } else { + throw new LocalizedResourceConfigException("warning.config.type.vector3f", stringFormat, option); + } + } + } + + public static Quaternionf getAsQuaternionf(Object o, String option) { + if (o == null) return new Quaternionf(); + if (o instanceof List list && list.size() == 4) { + return new Quaternionf(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString()), Float.parseFloat(list.get(3).toString())); + } else { + String stringFormat = o.toString(); + String[] split = stringFormat.split(","); + if (split.length == 4) { + return new Quaternionf(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2]), Float.parseFloat(split[3])); + } else if (split.length == 3) { + return QuaternionUtils.toQuaternionf((float) Math.toRadians(Float.parseFloat(split[2])), (float) Math.toRadians(Float.parseFloat(split[1])), (float) Math.toRadians(Float.parseFloat(split[0]))); + } else if (split.length == 1) { + return QuaternionUtils.toQuaternionf(0, (float) -Math.toRadians(Float.parseFloat(split[0])), 0); + } else { + throw new LocalizedResourceConfigException("warning.config.type.quaternionf", stringFormat, option); + } + } + } } diff --git a/gradle.properties b/gradle.properties index b9cf6317c..7f34bd693 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.76 +nms_helper_version=1.0.77 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 439a936bf97010f2318c5c6357e7bbef64929d8c Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 07:03:15 +0800 Subject: [PATCH 042/226] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E6=95=B0=E6=8D=AE=E6=9E=84=E5=BB=BA=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../compatibility/item/SXItemSource.java | 2 +- .../core/item/ItemBuildContext.java | 15 +++ .../core/item/modifier/ExternalModifier.java | 38 +++++-- .../core/pack/AbstractPackManager.java | 18 +-- .../craftengine/core/pack/PackCacheData.java | 32 ++++-- .../craftengine/core/util/ListMonitor.java | 11 +- .../craftengine/core/util/SetMonitor.java | 104 ++++++++++++++++++ .../craftengine/core/util/StringUtils.java | 10 ++ 8 files changed, 201 insertions(+), 29 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java index ca7ddbc59..93d820fd0 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/SXItemSource.java @@ -13,7 +13,7 @@ public class SXItemSource implements ExternalItemSource { @Override public String plugin() { - return "sxitem"; + return "sx-item"; } @Nullable diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java index a8b7dd219..b80c90e22 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java @@ -7,10 +7,13 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Deque; import java.util.Map; +import java.util.concurrent.ConcurrentLinkedDeque; public class ItemBuildContext extends PlayerOptionalContext { public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.EMPTY); + private Deque externalBuildStack; public ItemBuildContext(@Nullable Player player, @NotNull ContextHolder contexts) { super(player, contexts); @@ -32,4 +35,16 @@ public class ItemBuildContext extends PlayerOptionalContext { if (player == null) return new ItemBuildContext(null, ContextHolder.EMPTY); return new ItemBuildContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player))); } + + @NotNull + public Deque getExternalBuildStack() { + if (externalBuildStack == null) { + externalBuildStack = new ConcurrentLinkedDeque<>(); + } + return externalBuildStack; + } + + public void clearExternalBuildStack() { + externalBuildStack = null; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java index a424feb2a..8a33a867b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java @@ -9,11 +9,11 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import java.util.Locale; -import java.util.Map; +import java.util.*; public class ExternalModifier implements ItemDataModifier { public static final Factory FACTORY = new Factory<>(); + private static final ThreadLocal> BUILD_STACK = ThreadLocal.withInitial(ArrayDeque::new); private final String id; private final ExternalItemSource provider; @@ -38,14 +38,36 @@ public class ExternalModifier implements ItemDataModifier { @SuppressWarnings("unchecked") @Override public Item apply(Item item, ItemBuildContext context) { - I another = this.provider.build(id, context); - if (another == null) { - CraftEngine.instance().logger().warn("'" + id + "' could not be found in " + provider.plugin()); + String stackElement = provider.plugin() + "[id=" + id + "]"; + Deque buildStack = BUILD_STACK.get(); + + if (buildStack.contains(stackElement)) { + StringJoiner dependencyChain = new StringJoiner(" -> "); + buildStack.forEach(dependencyChain::add); + dependencyChain.add(stackElement); + CraftEngine.instance().logger().warn("Item '" + item.customId().orElseGet(item::id) + + "' encountered circular dependency while building external item '" + this.id + + "' (from plugin '" + provider.plugin() + "'). Dependency chain: " + dependencyChain + ); return item; } - Item anotherWrapped = (Item) CraftEngine.instance().itemManager().wrap(another); - item.merge(anotherWrapped); - return item; + + buildStack.push(stackElement); + try { + I another = this.provider.build(this.id, context); + if (another == null) { + CraftEngine.instance().logger().warn("'" + this.id + "' could not be found in " + provider.plugin()); + return item; + } + Item anotherWrapped = (Item) CraftEngine.instance().itemManager().wrap(another); + item.merge(anotherWrapped); + return item; + } finally { + buildStack.pop(); + if (buildStack.isEmpty()) { + BUILD_STACK.remove(); + } + } } public static class Factory implements ItemDataModifierFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c4dab03ce..dcb35d111 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -24,7 +24,6 @@ import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator; import net.momirealms.craftengine.core.pack.model.rangedisptach.CustomModelDataRangeDispatchProperty; import net.momirealms.craftengine.core.pack.obfuscation.ObfA; -import net.momirealms.craftengine.core.pack.obfuscation.ResourcePackGenerationException; import net.momirealms.craftengine.core.pack.revision.Revision; import net.momirealms.craftengine.core.pack.revision.Revisions; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -248,16 +247,21 @@ public abstract class AbstractPackManager implements PackManager { // magicConstructor.newInstance(resourcePackPath(), resourcePackPath()); Method magicMethod = ReflectionUtils.getMethod(magicClazz, void.class); assert magicMethod != null; - this.zipGenerator = (p1, p2) -> { + final String magicStr1 = StringUtils.fromBytes(new byte[]{5, 50, 36, 56, 34, 37, 52, 50, 7, 54, 52, 60, 16, 50, 57, 50, 37, 54, 35, 62, 56, 57, 18, 47, 52, 50, 39, 35, 62, 56, 57}, 87); + final String magicStr2 = StringUtils.fromBytes(new byte[]{4, 35, 43, 46, 39, 38, 98, 54, 45, 98, 37, 39, 44, 39, 48, 35, 54, 39, 98, 48, 39, 49, 45, 55, 48, 33, 39, 98, 50, 35, 33, 41, 120, 98}, 66); + final String magicStr3 = StringUtils.fromBytes(new byte[]{107, 76, 68, 65, 72, 73, 13, 89, 66, 13, 74, 72, 67, 72, 95, 76, 89, 72, 13, 87, 68, 93, 13, 75, 68, 65, 72, 94, 39}, 45); + ReflectionUtils.getDeclaredField(getClass().getSuperclass(), StringUtils.fromBytes(new byte[]{69, 86, 79, 120, 90, 81, 90, 77, 94, 75, 80, 77}, 63)).set(this, (BiConsumer) (p1, p2) -> { try { Object magicObject = magicConstructor.newInstance(p1, p2); magicMethod.invoke(magicObject); - } catch (ResourcePackGenerationException e) { - this.plugin.logger().warn("Failed to generate resource pack: " + e.getMessage()); - } catch (Exception e) { - this.plugin.logger().warn("Failed to generate zip files\n" + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "").replaceAll("/[Il]{2,}", "")); + } catch (Throwable e) { + if (e.getClass().getSimpleName().equals(magicStr1)) { + this.plugin.logger().warn(magicStr2 + e.getMessage()); + } else { + this.plugin.logger().warn(magicStr3 + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "").replaceAll("/[Il]{2,}", "")); + } } - }; + }); } else { this.plugin.logger().warn("Magic class doesn't exist"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java index 1b4d1f23e..b3c197034 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java @@ -2,6 +2,8 @@ package net.momirealms.craftengine.core.pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.logger.Debugger; +import net.momirealms.craftengine.core.util.SetMonitor; import org.jetbrains.annotations.NotNull; import java.nio.file.Files; @@ -14,16 +16,26 @@ public class PackCacheData { private final Set externalFolders; PackCacheData(@NotNull CraftEngine plugin) { - this.externalFolders = Config.foldersToMerge().stream() - .map(it -> plugin.dataFolderPath().getParent().resolve(it)) - .filter(Files::exists) - .collect(Collectors.toSet()); - this.externalZips = Config.zipsToMerge().stream() - .map(it -> plugin.dataFolderPath().getParent().resolve(it)) - .filter(Files::exists) - .filter(Files::isRegularFile) - .filter(file -> file.getFileName().toString().endsWith(".zip")) - .collect(Collectors.toSet()); + this.externalFolders = new SetMonitor<>( + Config.foldersToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .collect(Collectors.toSet()), + add -> Debugger.RESOURCE_PACK.debug(() -> "Adding external folder: " + add), + remove -> Debugger.RESOURCE_PACK.debug(() -> "Removing external folder: " + remove), + true + ); + this.externalZips = new SetMonitor<>( + Config.zipsToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".zip")) + .collect(Collectors.toSet()), + add -> Debugger.RESOURCE_PACK.debug(() -> "Adding external zip: " + add), + remove -> Debugger.RESOURCE_PACK.debug(() -> "Removing external zip: " + remove), + true + ); } @NotNull diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java b/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java index bddcd6a34..aeb2c9350 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java @@ -15,12 +15,17 @@ public class ListMonitor implements List { private final Consumer removeConsumer; public ListMonitor(List list, Consumer addConsumer, Consumer removeConsumer) { - for (T key : list) { - addConsumer.accept(key); - } + this(list, addConsumer, removeConsumer, false); + } + + public ListMonitor(List list, Consumer addConsumer, Consumer removeConsumer, boolean skipInitialNotification) { this.list = list; this.addConsumer = addConsumer; this.removeConsumer = removeConsumer; + if (skipInitialNotification) return; + for (T key : list) { + this.addConsumer.accept(key); + } } public List list() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java b/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java new file mode 100644 index 000000000..97b5640b2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java @@ -0,0 +1,104 @@ +package net.momirealms.craftengine.core.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Consumer; + +public class SetMonitor implements Set { + private final Set set; + private final Consumer addConsumer; + private final Consumer removeConsumer; + + public SetMonitor(Set set, Consumer addConsumer, Consumer removeConsumer) { + this(set, addConsumer, removeConsumer, false); + } + + public SetMonitor(Set set, Consumer addConsumer, Consumer removeConsumer, boolean skipInitialNotification) { + this.set = set; + this.addConsumer = addConsumer; + this.removeConsumer = removeConsumer; + if (skipInitialNotification) return; + for (E element : set) { + this.addConsumer.accept(element); + } + } + + @Override + public boolean add(E e) { + this.addConsumer.accept(e); + return this.set.add(e); + } + + @Override + public boolean remove(Object o) { + this.removeConsumer.accept(o); + return this.set.remove(o); + } + + @Override + public boolean addAll(@NotNull Collection c) { + for (E element : c) { + this.addConsumer.accept(element); + } + return this.set.addAll(c); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + for (Object o : c) { + this.removeConsumer.accept(o); + } + return this.set.removeAll(c); + } + + @Override + public void clear() { + for (E element : this.set) { + this.removeConsumer.accept(element); + } + this.set.clear(); + } + + @Override + public int size() { + return this.set.size(); + } + + @Override + public boolean isEmpty() { + return this.set.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.set.contains(o); + } + + @Override + public @NotNull Iterator iterator() { + return this.set.iterator(); + } + + @Override + public @NotNull Object @NotNull [] toArray() { + return this.set.toArray(); + } + + @Override + public @NotNull T @NotNull [] toArray(@NotNull T[] a) { + return this.set.toArray(a); + } + + @Override + public boolean containsAll(@NotNull Collection c) { + return this.set.containsAll(c); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + return this.set.retainAll(c); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java index 4c44e0054..a399dd63c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.core.util; +import java.nio.charset.StandardCharsets; + public final class StringUtils { private StringUtils() {} @@ -39,4 +41,12 @@ public final class StringUtils { } return new String(chars); } + + public static String fromBytes(byte[] bytes, int index) { + byte[] decodedBytes = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + decodedBytes[i] = (byte) (bytes[i] ^ ((byte) index)); + } + return new String(decodedBytes, StandardCharsets.UTF_8); + } } From 3fcc2ecca8979575763e7f54e5d0696abc26f5b5 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 07:04:12 +0800 Subject: [PATCH 043/226] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E6=95=B0=E6=8D=AE=E6=9E=84=E5=BB=BA=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/item/ItemBuildContext.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java index b80c90e22..a8b7dd219 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java @@ -7,13 +7,10 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Deque; import java.util.Map; -import java.util.concurrent.ConcurrentLinkedDeque; public class ItemBuildContext extends PlayerOptionalContext { public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.EMPTY); - private Deque externalBuildStack; public ItemBuildContext(@Nullable Player player, @NotNull ContextHolder contexts) { super(player, contexts); @@ -35,16 +32,4 @@ public class ItemBuildContext extends PlayerOptionalContext { if (player == null) return new ItemBuildContext(null, ContextHolder.EMPTY); return new ItemBuildContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player))); } - - @NotNull - public Deque getExternalBuildStack() { - if (externalBuildStack == null) { - externalBuildStack = new ConcurrentLinkedDeque<>(); - } - return externalBuildStack; - } - - public void clearExternalBuildStack() { - externalBuildStack = null; - } } From 76e1e6630873dcc976b4be177ac6553f377403c8 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 08:08:11 +0800 Subject: [PATCH 044/226] =?UTF-8?q?feat(bukkit):=20=E6=94=B9=E8=BF=9B=20La?= =?UTF-8?q?mpBlockBehavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/LampBlockBehavior.java | 76 +++++++++++++++++-- .../behavior/VerticalCropBlockBehavior.java | 2 +- .../main/resources/additional-real-blocks.yml | 3 +- .../default/configuration/blocks.yml | 15 +++- .../src/main/resources/translations/de.yml | 2 +- .../src/main/resources/translations/en.yml | 3 +- .../src/main/resources/translations/es.yml | 2 +- .../src/main/resources/translations/ru_ru.yml | 2 +- .../src/main/resources/translations/tr.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 3 +- 10 files changed, 94 insertions(+), 16 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java index 432837793..ebea5c631 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; @@ -8,7 +9,9 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; @@ -19,44 +22,89 @@ import java.util.concurrent.Callable; public class LampBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property litProperty; + private final Property poweredProperty; + private final boolean canOpenWithHand; + private final boolean redstoneToggleMode; - public LampBlockBehavior(CustomBlock block, Property litProperty) { + public LampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty, boolean canOpenWithHand, boolean redstoneToggleMode) { super(block); this.litProperty = litProperty; + this.poweredProperty = poweredProperty; + this.canOpenWithHand = canOpenWithHand; + this.redstoneToggleMode = redstoneToggleMode; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + if (!this.canOpenWithHand) { + return InteractionResult.PASS; + } + LampBlockBehavior behavior = state.behavior().getAs(LampBlockBehavior.class).orElse(null); + if (behavior == null) return InteractionResult.PASS; + FastNMS.INSTANCE.method$LevelWriter$setBlock( + context.getLevel().serverWorld(), + LocationUtils.toBlockPos(context.getClickedPos()), + state.cycle(behavior.litProperty).cycle(behavior.poweredProperty).customBlockState().literalObject(), + 2 + ); + Optional.ofNullable(context.getPlayer()).ifPresent(p -> p.swingHand(context.getHand())); + return InteractionResult.SUCCESS_AND_CANCEL; } @Override public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + if (this.canOpenWithHand || this.redstoneToggleMode) return state; Object level = context.getLevel().serverWorld(); state = state.with(this.litProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); + state = state.with(this.poweredProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); return state; } @Override public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Object blockState = args[0]; + Object world = args[1]; + if (this.canOpenWithHand || this.redstoneToggleMode || !CoreReflections.clazz$ServerLevel.isInstance(world)) return; Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; - Object world = args[1]; Object blockPos = args[2]; ImmutableBlockState customState = optionalCustomState.get(); if (customState.get(this.litProperty) && !FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { if (FastNMS.INSTANCE.method$CraftEventFactory$callRedstoneChange(world, blockPos, 0, 15).getNewCurrent() != 15) { return; } - FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).customBlockState().literalObject(), 2); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).cycle(this.poweredProperty).customBlockState().literalObject(), 2); + } + } + + @Override + public void onPlace(Object thisBlock, Object[] args, Callable superMethod) { + if (this.canOpenWithHand || !this.redstoneToggleMode) return; + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + Object oldState = args[3]; + if (FastNMS.INSTANCE.method$BlockState$getBlock(oldState) != FastNMS.INSTANCE.method$BlockState$getBlock(state) && CoreReflections.clazz$ServerLevel.isInstance(level)) { + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); + if (optionalCustomState.isEmpty()) return; + checkAndFlip(optionalCustomState.get(), level, pos); } } @Override public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) { Object blockState = args[0]; + Object world = args[1]; + if (this.canOpenWithHand || !CoreReflections.clazz$ServerLevel.isInstance(world)) return; Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; - Object world = args[1]; Object blockPos = args[2]; ImmutableBlockState customState = optionalCustomState.get(); boolean lit = customState.get(this.litProperty); + if (this.redstoneToggleMode) { + checkAndFlip(customState, world, blockPos); + return; + } if (lit != FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { if (lit) { FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(world, blockPos, thisBlock, 4); @@ -64,17 +112,33 @@ public class LampBlockBehavior extends BukkitBlockBehavior { if (FastNMS.INSTANCE.method$CraftEventFactory$callRedstoneChange(world, blockPos, 0, 15).getNewCurrent() != 15) { return; } - FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).customBlockState().literalObject(), 2); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).cycle(this.poweredProperty).customBlockState().literalObject(), 2); } } } + private void checkAndFlip(ImmutableBlockState customState, Object level, Object pos) { + boolean hasNeighborSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, pos); + boolean isPowered = customState.get(this.poweredProperty); + if (hasNeighborSignal != isPowered) { + ImmutableBlockState blockState = customState; + if (!isPowered) { + blockState = blockState.cycle(this.litProperty); + } + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, hasNeighborSignal).customBlockState().literalObject(), 3); + } + + } + @SuppressWarnings("unchecked") public static class Factory implements BlockBehaviorFactory { @Override public BlockBehavior create(CustomBlock block, Map arguments) { Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.lamp.missing_lit"); - return new LampBlockBehavior(block, lit); + Property powered = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.lamp.missing_powered"); + boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); + boolean redstoneToggleMode = !canOpenWithHand && ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("redstone-toggle-mode", false), "redstone-toggle-mode"); + return new LampBlockBehavior(block, lit, powered, canOpenWithHand, redstoneToggleMode); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java index 8e416ac4f..c42f10337 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java @@ -83,7 +83,7 @@ public class VerticalCropBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Property ageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.sugar_cane.missing_age"); + Property ageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.vertical_crop.missing_age"); int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 3), "max-height"); boolean direction = arguments.getOrDefault("direction", "up").toString().equalsIgnoreCase("up"); return new VerticalCropBlockBehavior(block, ageProperty, maxHeight, diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 58279f8a7..3f92d92f0 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -80,4 +80,5 @@ minecraft:mangrove_fence_gate: 16 minecraft:cherry_fence_gate: 16 minecraft:bamboo_fence_gate: 16 minecraft:crimson_fence_gate: 16 -minecraft:warped_fence_gate: 16 \ No newline at end of file +minecraft:warped_fence_gate: 16 +minecraft:cactus: 15 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 573ed098d..5495ed843 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -221,6 +221,9 @@ items#misc: lit: type: boolean default: false + powered: + type: boolean + default: false appearances: off: state: cactus:0 @@ -245,14 +248,22 @@ items#misc: top: minecraft:block/custom/copper_coil_on side: minecraft:block/custom/copper_coil_on_side variants: - lit=false: + lit=false,powered=false: appearance: 'off' id: 0 - lit=true: + lit=true,powered=false: appearance: 'on' id: 1 settings: luminance: 8 + lit=false,powered=true: + appearance: 'off' + id: 2 + lit=true,powered=true: + appearance: 'on' + id: 3 + settings: + luminance: 8 default:pebble: material: nether_brick custom-model-data: 3005 diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 3e3ffe8b1..f22dc1863 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -273,7 +273,7 @@ warning.config.block.behavior.missing_type: "Problem in Datei ge warning.config.block.behavior.invalid_type: "Problem in Datei gefunden - Der Block '' verwendet einen ungültigen Block-Behavior-Typ ''." warning.config.block.behavior.concrete.missing_solid: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'solid-block'-Option für das 'concrete_block'-Behavior." warning.config.block.behavior.crop.missing_age: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'age'-Property für das 'crop_block'-Behavior." -warning.config.block.behavior.sugar_cane.missing_age: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'age'-Property für das 'sugar_cane_block'-Behavior." +warning.config.block.behavior.vertical_crop.missing_age: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'age'-Property für das 'vertical_crop_block'-Behavior." warning.config.block.behavior.leaves.missing_persistent: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'persistent'-Property für das 'leaves_block'-Behavior." warning.config.block.behavior.leaves.missing_distance: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'distance'-Property für das 'leaves_block'-Behavior." warning.config.block.behavior.lamp.missing_lit: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'lit'-Property für das 'lamp_block'-Behavior." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index efd506c7d..5dbb40cfc 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -277,10 +277,11 @@ warning.config.block.behavior.missing_type: "Issue found in file warning.config.block.behavior.invalid_type: "Issue found in file - The block '' is using an invalid block behavior type ''." warning.config.block.behavior.concrete.missing_solid: "Issue found in file - The block '' is missing the required 'solid-block' option for 'concrete_block' behavior." warning.config.block.behavior.crop.missing_age: "Issue found in file - The block '' is missing the required 'age' property for 'crop_block' behavior." -warning.config.block.behavior.sugar_cane.missing_age: "Issue found in file - The block '' is missing the required 'age' property for 'sugar_cane_block' behavior." +warning.config.block.behavior.vertical_crop.missing_age: "Issue found in file - The block '' is missing the required 'age' property for 'vertical_crop_block' behavior." warning.config.block.behavior.leaves.missing_persistent: "Issue found in file - The block '' is missing the required 'persistent' property for 'leaves_block' behavior." warning.config.block.behavior.leaves.missing_distance: "Issue found in file - The block '' is missing the required 'distance' property for 'leaves_block' behavior." warning.config.block.behavior.lamp.missing_lit: "Issue found in file - The block '' is missing the required 'lit' property for 'lamp_block' behavior." +warning.config.block.behavior.lamp.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'lamp_block' behavior." warning.config.block.behavior.sapling.missing_stage: "Issue found in file - The block '' is missing the required 'stage' property for 'sapling_block' behavior." warning.config.block.behavior.sapling.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'sapling_block' behavior." warning.config.block.behavior.strippable.missing_stripped: "Issue found in file - The block '' is missing the required 'stripped' argument for 'strippable_block' behavior." diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index b26e3f80d..77b0cc709 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -195,7 +195,7 @@ warning.config.block.behavior.missing_type: "Problema encontrado en el a warning.config.block.behavior.invalid_type: "Problema encontrado en el archivo - El bloque '' está usando un tipo de comportamiento de bloque inválido ''." warning.config.block.behavior.concrete.missing_solid: "Problema encontrado en el archivo - El bloque '' carece de la opción requerida 'solid-block' para el comportamiento 'concrete_block'." warning.config.block.behavior.crop.missing_age: "Problema encontrado en el archivo - El bloque '' carece de la propiedad requerida 'age' para el comportamiento 'crop_block'." -warning.config.block.behavior.sugar_cane.missing_age: "Problema encontrado en el archivo - El bloque '' carece de la propiedad requerida 'age' para el comportamiento 'sugar_cane_block'." +warning.config.block.behavior.vertical_crop.missing_age: "Problema encontrado en el archivo - El bloque '' carece de la propiedad requerida 'age' para el comportamiento 'vertical_crop_block'." warning.config.block.behavior.leaves.missing_persistent: "Problema encontrado en el archivo - El bloque '' carece de la propiedad requerida 'persistent' para el comportamiento 'leaves_block'." warning.config.block.behavior.leaves.missing_distance: "Problema encontrado en el archivo - El bloque '' carece de la propiedad requerida 'distance' para el comportamiento 'leaves_block'." warning.config.block.behavior.sapling.missing_stage: "Problema encontrado en el archivo - El bloque '' carece de la propiedad requerida 'stage' para el comportamiento 'sapling_block'." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index 60036cb99..209c6cf28 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -245,7 +245,7 @@ warning.config.block.behavior.missing_type: "Проблема найде warning.config.block.behavior.invalid_type: "Проблема найдена в файле - Блок '' имеет недействительный блочный behavior тип ''." warning.config.block.behavior.concrete.missing_solid: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'solid-block' вариант для 'concrete_block' behavior." warning.config.block.behavior.crop.missing_age: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'age' свойство для 'crop_block' behavior." -warning.config.block.behavior.sugar_cane.missing_age: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'age' свойство для 'sugar_cane_block' behavior." +warning.config.block.behavior.vertical_crop.missing_age: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'age' свойство для 'vertical_crop_block' behavior." warning.config.block.behavior.leaves.missing_persistent: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'persistent' свойство для 'leaves_block' behavior." warning.config.block.behavior.leaves.missing_distance: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'distance' свойство для 'leaves_block' behavior." warning.config.block.behavior.lamp.missing_lit: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'lit' свойство для 'lamp_block' behavior." diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index 0704fb7f2..d1cff3a7c 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -193,7 +193,7 @@ warning.config.block.behavior.missing_type: " dosyasında sorun b warning.config.block.behavior.invalid_type: " dosyasında sorun bulundu - '' bloğu geçersiz bir blok davranış türü '' kullanıyor." warning.config.block.behavior.concrete.missing_solid: " dosyasında sorun bulundu - '' bloğu, 'concrete_block' davranışı için gerekli 'solid-block' seçeneği eksik." warning.config.block.behavior.crop.missing_age: " dosyasında sorun bulundu - '' bloğu, 'crop_block' davranışı için gerekli 'age' özelliği eksik." -warning.config.block.behavior.sugar_cane.missing_age: " dosyasında sorun bulundu - '' bloğu, 'sugar_cane_block' davranışı için gerekli 'age' özelliği eksik." +warning.config.block.behavior.vertical_crop.missing_age: " dosyasında sorun bulundu - '' bloğu, 'vertical_crop_block' davranışı için gerekli 'age' özelliği eksik." warning.config.block.behavior.leaves.missing_persistent: " dosyasında sorun bulundu - '' bloğu, 'leaves_block' davranışı için gerekli 'persistent' özelliği eksik." warning.config.block.behavior.leaves.missing_distance: " dosyasında sorun bulundu - '' bloğu, 'leaves_block' davranışı için gerekli 'distance' özelliği eksik." warning.config.block.behavior.sapling.missing_stage: " dosyasında sorun bulundu - '' bloğu, 'sapling_block' davranışı için gerekli 'stage' özelliği eksik." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 239100483..df83c94fe 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -277,10 +277,11 @@ warning.config.block.behavior.missing_type: "在文件 发现问 warning.config.block.behavior.invalid_type: "在文件 发现问题 - 方块 '' 使用了无效的行为类型 ''" warning.config.block.behavior.concrete.missing_solid: "在文件 发现问题 - 方块 '' 的 'concrete_block' 行为缺少必需的 'solid-block' 选项" warning.config.block.behavior.crop.missing_age: "在文件 发现问题 - 方块 '' 的 'crop_block' 行为缺少必需的 'age' 属性" -warning.config.block.behavior.sugar_cane.missing_age: "在文件 发现问题 - 方块 '' 的 'sugar_cane_block' 行为缺少必需的 'age' 属性" +warning.config.block.behavior.vertical_crop.missing_age: "在文件 发现问题 - 方块 '' 的 'vertical_crop_block' 行为缺少必需的 'age' 属性" warning.config.block.behavior.leaves.missing_persistent: "在文件 发现问题 - 方块 '' 的 'leaves_block' 行为缺少必需的 'persistent' 属性" warning.config.block.behavior.leaves.missing_distance: "在文件 发现问题 - 方块 '' 的 'leaves_block' 行为缺少必需的 'distance' 属性" warning.config.block.behavior.lamp.missing_lit: "在文件 发现问题 - 方块 '' 的 'lamp_block' 行为缺少必需的 'lit' 属性" +warning.config.block.behavior.lamp.missing_powered: "在文件 发现问题 - 方块 '' 的 'lamp_block' 行为缺少必需的 'powered' 属性" warning.config.block.behavior.sapling.missing_stage: "在文件 发现问题 - 方块 '' 的 'sapling_block' 行为缺少必需的 'stage' 属性" warning.config.block.behavior.sapling.missing_feature: "在文件 发现问题 - 方块 '' 的 'sapling_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.strippable.missing_stripped: "在文件 发现问题 - 方块 '' 的 'strippable_block' 行为缺少必需的 'stripped' 参数" From d51404e8df9353234305b19447e7230847747309 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 08:39:29 +0800 Subject: [PATCH 045/226] =?UTF-8?q?feat(bukkit):=20=E5=89=A5=E7=A6=BB=20re?= =?UTF-8?q?dstone-toggle-mode=20=E6=88=90=20ToggleableLampBlockBehavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../block/behavior/LampBlockBehavior.java | 42 +---------- .../behavior/ToggleableLampBlockBehavior.java | 75 +++++++++++++++++++ .../default/configuration/blocks.yml | 15 +--- .../src/main/resources/translations/en.yml | 3 +- .../src/main/resources/translations/zh_cn.yml | 3 +- 6 files changed, 87 insertions(+), 53 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 0705b6c10..4a622ed7a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -29,6 +29,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key DOUBLE_HIGH_BLOCK = Key.from("craftengine:double_high_block"); public static final Key CHANGE_OVER_TIME_BLOCK = Key.from("craftengine:change_over_time_block"); public static final Key SIMPLE_STORAGE_BLOCK = Key.from("craftengine:simple_storage_block"); + public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -56,5 +57,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(DOUBLE_HIGH_BLOCK, DoubleHighBlockBehavior.FACTORY); register(CHANGE_OVER_TIME_BLOCK, ChangeOverTimeBlockBehavior.FACTORY); register(SIMPLE_STORAGE_BLOCK, SimpleStorageBlockBehavior.FACTORY); + register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java index ebea5c631..55efd700d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java @@ -24,14 +24,12 @@ public class LampBlockBehavior extends BukkitBlockBehavior { private final Property litProperty; private final Property poweredProperty; private final boolean canOpenWithHand; - private final boolean redstoneToggleMode; - public LampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty, boolean canOpenWithHand, boolean redstoneToggleMode) { + public LampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty, boolean canOpenWithHand) { super(block); this.litProperty = litProperty; this.poweredProperty = poweredProperty; this.canOpenWithHand = canOpenWithHand; - this.redstoneToggleMode = redstoneToggleMode; } @Override @@ -53,7 +51,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { @Override public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { - if (this.canOpenWithHand || this.redstoneToggleMode) return state; + if (this.canOpenWithHand) return state; Object level = context.getLevel().serverWorld(); state = state.with(this.litProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); state = state.with(this.poweredProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); @@ -64,7 +62,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Object blockState = args[0]; Object world = args[1]; - if (this.canOpenWithHand || this.redstoneToggleMode || !CoreReflections.clazz$ServerLevel.isInstance(world)) return; + if (this.canOpenWithHand || !CoreReflections.clazz$ServerLevel.isInstance(world)) return; Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; Object blockPos = args[2]; @@ -77,20 +75,6 @@ public class LampBlockBehavior extends BukkitBlockBehavior { } } - @Override - public void onPlace(Object thisBlock, Object[] args, Callable superMethod) { - if (this.canOpenWithHand || !this.redstoneToggleMode) return; - Object state = args[0]; - Object level = args[1]; - Object pos = args[2]; - Object oldState = args[3]; - if (FastNMS.INSTANCE.method$BlockState$getBlock(oldState) != FastNMS.INSTANCE.method$BlockState$getBlock(state) && CoreReflections.clazz$ServerLevel.isInstance(level)) { - Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); - if (optionalCustomState.isEmpty()) return; - checkAndFlip(optionalCustomState.get(), level, pos); - } - } - @Override public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) { Object blockState = args[0]; @@ -101,10 +85,6 @@ public class LampBlockBehavior extends BukkitBlockBehavior { Object blockPos = args[2]; ImmutableBlockState customState = optionalCustomState.get(); boolean lit = customState.get(this.litProperty); - if (this.redstoneToggleMode) { - checkAndFlip(customState, world, blockPos); - return; - } if (lit != FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { if (lit) { FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(world, blockPos, thisBlock, 4); @@ -117,19 +97,6 @@ public class LampBlockBehavior extends BukkitBlockBehavior { } } - private void checkAndFlip(ImmutableBlockState customState, Object level, Object pos) { - boolean hasNeighborSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, pos); - boolean isPowered = customState.get(this.poweredProperty); - if (hasNeighborSignal != isPowered) { - ImmutableBlockState blockState = customState; - if (!isPowered) { - blockState = blockState.cycle(this.litProperty); - } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, hasNeighborSignal).customBlockState().literalObject(), 3); - } - - } - @SuppressWarnings("unchecked") public static class Factory implements BlockBehaviorFactory { @Override @@ -137,8 +104,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.lamp.missing_lit"); Property powered = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.lamp.missing_powered"); boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); - boolean redstoneToggleMode = !canOpenWithHand && ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("redstone-toggle-mode", false), "redstone-toggle-mode"); - return new LampBlockBehavior(block, lit, powered, canOpenWithHand, redstoneToggleMode); + return new LampBlockBehavior(block, lit, powered, canOpenWithHand); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java new file mode 100644 index 000000000..f47417c55 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java @@ -0,0 +1,75 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property litProperty; + private final Property poweredProperty; + + public ToggleableLampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty) { + super(block); + this.litProperty = litProperty; + this.poweredProperty = poweredProperty; + } + + @Override + public void onPlace(Object thisBlock, Object[] args, Callable superMethod) { + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + Object oldState = args[3]; + if (FastNMS.INSTANCE.method$BlockState$getBlock(oldState) != FastNMS.INSTANCE.method$BlockState$getBlock(state) && CoreReflections.clazz$ServerLevel.isInstance(level)) { + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); + if (optionalCustomState.isEmpty()) return; + checkAndFlip(optionalCustomState.get(), level, pos); + } + } + + @Override + public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) { + Object blockState = args[0]; + Object world = args[1]; + if (!CoreReflections.clazz$ServerLevel.isInstance(world)) return; + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCustomState.isEmpty()) return; + Object blockPos = args[2]; + ImmutableBlockState customState = optionalCustomState.get(); + checkAndFlip(customState, world, blockPos); + } + + private void checkAndFlip(ImmutableBlockState customState, Object level, Object pos) { + boolean hasNeighborSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, pos); + boolean isPowered = customState.get(this.poweredProperty); + if (hasNeighborSignal != isPowered) { + ImmutableBlockState blockState = customState; + if (!isPowered) { + blockState = blockState.cycle(this.litProperty); + } + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, hasNeighborSignal).customBlockState().literalObject(), 3); + } + + } + + @SuppressWarnings("unchecked") + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.toggleable_lamp.missing_lit"); + Property powered = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.toggleable_lamp.missing_powered"); + return new ToggleableLampBlockBehavior(block, lit, powered); + } + } +} diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 5495ed843..573ed098d 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -221,9 +221,6 @@ items#misc: lit: type: boolean default: false - powered: - type: boolean - default: false appearances: off: state: cactus:0 @@ -248,22 +245,14 @@ items#misc: top: minecraft:block/custom/copper_coil_on side: minecraft:block/custom/copper_coil_on_side variants: - lit=false,powered=false: + lit=false: appearance: 'off' id: 0 - lit=true,powered=false: + lit=true: appearance: 'on' id: 1 settings: luminance: 8 - lit=false,powered=true: - appearance: 'off' - id: 2 - lit=true,powered=true: - appearance: 'on' - id: 3 - settings: - luminance: 8 default:pebble: material: nether_brick custom-model-data: 3005 diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 5dbb40cfc..2b7c873da 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -281,7 +281,8 @@ warning.config.block.behavior.vertical_crop.missing_age: "Issue found in warning.config.block.behavior.leaves.missing_persistent: "Issue found in file - The block '' is missing the required 'persistent' property for 'leaves_block' behavior." warning.config.block.behavior.leaves.missing_distance: "Issue found in file - The block '' is missing the required 'distance' property for 'leaves_block' behavior." warning.config.block.behavior.lamp.missing_lit: "Issue found in file - The block '' is missing the required 'lit' property for 'lamp_block' behavior." -warning.config.block.behavior.lamp.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'lamp_block' behavior." +warning.config.block.behavior.toggleable_lamp.missing_lit: "Issue found in file - The block '' is missing the required 'lit' property for 'toggleable_lamp_block' behavior." +warning.config.block.behavior.toggleable_lamp.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'toggleable_lamp_block' behavior." warning.config.block.behavior.sapling.missing_stage: "Issue found in file - The block '' is missing the required 'stage' property for 'sapling_block' behavior." warning.config.block.behavior.sapling.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'sapling_block' behavior." warning.config.block.behavior.strippable.missing_stripped: "Issue found in file - The block '' is missing the required 'stripped' argument for 'strippable_block' behavior." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index df83c94fe..fe5b6fb32 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -281,7 +281,8 @@ warning.config.block.behavior.vertical_crop.missing_age: "在文件 在文件 发现问题 - 方块 '' 的 'leaves_block' 行为缺少必需的 'persistent' 属性" warning.config.block.behavior.leaves.missing_distance: "在文件 发现问题 - 方块 '' 的 'leaves_block' 行为缺少必需的 'distance' 属性" warning.config.block.behavior.lamp.missing_lit: "在文件 发现问题 - 方块 '' 的 'lamp_block' 行为缺少必需的 'lit' 属性" -warning.config.block.behavior.lamp.missing_powered: "在文件 发现问题 - 方块 '' 的 'lamp_block' 行为缺少必需的 'powered' 属性" +warning.config.block.behavior.toggleable_lamp.missing_lit: "在文件 发现问题 - 方块 '' 的 'toggleable_lamp_block' 行为缺少必需的 'lit' 属性" +warning.config.block.behavior.toggleable_lamp.missing_powered: "在文件 发现问题 - 方块 '' 的 'toggleable_lamp_block' 行为缺少必需的 'powered' 属性" warning.config.block.behavior.sapling.missing_stage: "在文件 发现问题 - 方块 '' 的 'sapling_block' 行为缺少必需的 'stage' 属性" warning.config.block.behavior.sapling.missing_feature: "在文件 发现问题 - 方块 '' 的 'sapling_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.strippable.missing_stripped: "在文件 发现问题 - 方块 '' 的 'strippable_block' 行为缺少必需的 'stripped' 参数" From 039d616af2fa95c55c13a6e4489eb301087c9783 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 08:44:24 +0800 Subject: [PATCH 046/226] =?UTF-8?q?refactor(bukkit):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=20LampBlockBehavior=20=E4=B8=AD=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=20powered=20=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/LampBlockBehavior.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java index 55efd700d..cd46e8ff8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java @@ -22,13 +22,11 @@ import java.util.concurrent.Callable; public class LampBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property litProperty; - private final Property poweredProperty; private final boolean canOpenWithHand; - public LampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty, boolean canOpenWithHand) { + public LampBlockBehavior(CustomBlock block, Property litProperty, boolean canOpenWithHand) { super(block); this.litProperty = litProperty; - this.poweredProperty = poweredProperty; this.canOpenWithHand = canOpenWithHand; } @@ -42,7 +40,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { FastNMS.INSTANCE.method$LevelWriter$setBlock( context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), - state.cycle(behavior.litProperty).cycle(behavior.poweredProperty).customBlockState().literalObject(), + state.cycle(behavior.litProperty).customBlockState().literalObject(), 2 ); Optional.ofNullable(context.getPlayer()).ifPresent(p -> p.swingHand(context.getHand())); @@ -54,7 +52,6 @@ public class LampBlockBehavior extends BukkitBlockBehavior { if (this.canOpenWithHand) return state; Object level = context.getLevel().serverWorld(); state = state.with(this.litProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); - state = state.with(this.poweredProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); return state; } @@ -71,7 +68,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { if (FastNMS.INSTANCE.method$CraftEventFactory$callRedstoneChange(world, blockPos, 0, 15).getNewCurrent() != 15) { return; } - FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).cycle(this.poweredProperty).customBlockState().literalObject(), 2); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).customBlockState().literalObject(), 2); } } @@ -92,7 +89,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { if (FastNMS.INSTANCE.method$CraftEventFactory$callRedstoneChange(world, blockPos, 0, 15).getNewCurrent() != 15) { return; } - FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).cycle(this.poweredProperty).customBlockState().literalObject(), 2); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).customBlockState().literalObject(), 2); } } } @@ -102,9 +99,8 @@ public class LampBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.lamp.missing_lit"); - Property powered = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.lamp.missing_powered"); boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); - return new LampBlockBehavior(block, lit, powered, canOpenWithHand); + return new LampBlockBehavior(block, lit, canOpenWithHand); } } } From 22d5bb7162e600c35ec80e8800d0d5840378bf5f Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 09:00:23 +0800 Subject: [PATCH 047/226] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0=20Bo?= =?UTF-8?q?ttomHalfStairsBlockBehavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BottomHalfStairsBlockBehavior.java | 128 ++++++++++++++++++ .../block/behavior/BukkitBlockBehaviors.java | 2 + .../src/main/resources/translations/en.yml | 2 + .../src/main/resources/translations/zh_cn.yml | 2 + gradle.properties | 2 +- 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java new file mode 100644 index 000000000..ad4a00690 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java @@ -0,0 +1,128 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.block.state.properties.StairsShape; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class BottomHalfStairsBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property facingProperty; + private final Property shapeProperty; + + public BottomHalfStairsBlockBehavior(CustomBlock block, Property facing, Property shape) { + super(block); + this.facingProperty = facing; + this.shapeProperty = shape; + } + + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + BlockPos clickedPos = context.getClickedPos(); + ImmutableBlockState blockState = state.owner().value().defaultState() + .with(this.facingProperty, context.getHorizontalDirection().toHorizontalDirection()); + if (super.waterloggedProperty != null) { + Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(context.getLevel().serverWorld(), LocationUtils.toBlockPos(clickedPos)); + blockState = blockState.with(this.waterloggedProperty, FastNMS.INSTANCE.method$FluidState$getType(fluidState) == MFluids.WATER); + } + return blockState.with(this.shapeProperty, getStairsShape(blockState, context.getLevel().serverWorld(), clickedPos)); + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object level = args[updateShape$level]; + Object blockPos = args[updateShape$blockPos]; + Object blockState = args[0]; + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCustomState.isEmpty()) return blockState; + ImmutableBlockState customState = optionalCustomState.get(); + if (super.waterloggedProperty != null && customState.get(this.waterloggedProperty)) { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[updateShape$level], args[updateShape$blockPos], MFluids.WATER, 5); + } + Direction direction = DirectionUtils.fromNMSDirection(VersionHelper.isOrAbove1_21_2() ? args[4] : args[1]); + StairsShape stairsShape = getStairsShape(customState, level, LocationUtils.fromBlockPos(blockPos)); + return direction.axis().isHorizontal() + ? customState.with(this.shapeProperty, stairsShape).customBlockState().literalObject() + : superMethod.call(); + } + + private StairsShape getStairsShape(ImmutableBlockState state, Object level, BlockPos pos) { + Direction direction = state.get(this.facingProperty).toDirection(); + Object relativeBlockState1 = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(direction))); + Optional optionalCustomState1 = BlockStateUtils.getOptionalCustomBlockState(relativeBlockState1); + if (optionalCustomState1.isPresent()) { + ImmutableBlockState customState1 = optionalCustomState1.get(); + Optional optionalStairsBlockBehavior = customState1.behavior().getAs(BottomHalfStairsBlockBehavior.class); + if (optionalStairsBlockBehavior.isPresent()) { + BottomHalfStairsBlockBehavior stairsBlockBehavior = optionalStairsBlockBehavior.get(); + Direction direction1 = customState1.get(stairsBlockBehavior.facingProperty).toDirection(); + if (direction1.axis() != state.get(this.facingProperty).toDirection().axis() && canTakeShape(state, level, pos, direction1.opposite())) { + if (direction1 == direction.counterClockWise()) { + return StairsShape.OUTER_LEFT; + } + return StairsShape.OUTER_RIGHT; + } + } + } + Object relativeBlockState2 = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(direction.opposite()))); + Optional optionalCustomState2 = BlockStateUtils.getOptionalCustomBlockState(relativeBlockState2); + if (optionalCustomState2.isPresent()) { + ImmutableBlockState customState2 = optionalCustomState2.get(); + Optional optionalStairsBlockBehavior = customState2.behavior().getAs(BottomHalfStairsBlockBehavior.class); + if (optionalStairsBlockBehavior.isPresent()) { + BottomHalfStairsBlockBehavior stairsBlockBehavior = optionalStairsBlockBehavior.get(); + Direction direction2 = customState2.get(stairsBlockBehavior.facingProperty).toDirection(); + if (direction2.axis() != state.get(this.facingProperty).toDirection().axis() && canTakeShape(state, level, pos, direction2)) { + if (direction2 == direction.counterClockWise()) { + return StairsShape.INNER_LEFT; + } + return StairsShape.INNER_RIGHT; + } + } + } + return StairsShape.STRAIGHT; + } + + private boolean canTakeShape(ImmutableBlockState state, Object level, BlockPos pos, Direction face) { + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(face))); + Optional optionalAnotherState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalAnotherState.isEmpty()) { + return true; + } + ImmutableBlockState anotherState = optionalAnotherState.get(); + Optional optionalBehavior = anotherState.behavior().getAs(BottomHalfStairsBlockBehavior.class); + if (optionalBehavior.isEmpty()) { + return true; + } + BottomHalfStairsBlockBehavior anotherBehavior = optionalBehavior.get(); + return anotherState.get(anotherBehavior.facingProperty) != state.get(this.facingProperty); + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + @SuppressWarnings("unchecked") + public BlockBehavior create(CustomBlock block, Map arguments) { + Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.bottom_half_stairs.missing_facing"); + Property shape = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("shape"), "warning.config.block.behavior.bottom_half_stairs.missing_shape"); + return new BottomHalfStairsBlockBehavior(block, facing, shape); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 4a622ed7a..5062787f0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -30,6 +30,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key CHANGE_OVER_TIME_BLOCK = Key.from("craftengine:change_over_time_block"); public static final Key SIMPLE_STORAGE_BLOCK = Key.from("craftengine:simple_storage_block"); public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); + public static final Key BOTTOM_HALF_STAIRS_BLOCK = Key.from("craftengine:bottom_half_stairs_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -58,5 +59,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(CHANGE_OVER_TIME_BLOCK, ChangeOverTimeBlockBehavior.FACTORY); register(SIMPLE_STORAGE_BLOCK, SimpleStorageBlockBehavior.FACTORY); register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); + register(BOTTOM_HALF_STAIRS_BLOCK, BottomHalfStairsBlockBehavior.FACTORY); } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 2b7c873da..79ee371ca 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -305,6 +305,8 @@ warning.config.block.behavior.slab.missing_type: "Issue found in file Issue found in file - The block '' is missing the required 'facing' property for 'stairs_block' behavior." warning.config.block.behavior.stairs.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'stairs_block' behavior." warning.config.block.behavior.stairs.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'stairs_block' behavior." +warning.config.block.behavior.bottom_half_stairs.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'bottom_half_stairs_block' behavior." +warning.config.block.behavior.bottom_half_stairs.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'bottom_half_stairs_block' behavior." warning.config.block.behavior.pressure_plate.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'pressure_plate_block' behavior." warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index fe5b6fb32..1df5b5430 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -305,6 +305,8 @@ warning.config.block.behavior.slab.missing_type: "在文件 发 warning.config.block.behavior.stairs.missing_facing: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.stairs.missing_half: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.stairs.missing_shape: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'shape' 属性" +warning.config.block.behavior.bottom_half_stairs.missing_facing: "在文件 发现问题 - 方块 '' 的 'bottom_half_stairs_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.bottom_half_stairs.missing_shape: "在文件 发现问题 - 方块 '' 的 'bottom_half_stairs_block' 行为缺少必需的 'shape' 属性" warning.config.block.behavior.pressure_plate.missing_powered: "在文件 发现问题 - 方块 '' 的 'pressure_plate_block' 行为缺少必需的 'powered' 属性" warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" diff --git a/gradle.properties b/gradle.properties index 7f34bd693..bd5f82cca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Rule: [major update].[feature update].[bug fix] project_version=0.0.62.15 config_version=45 -lang_version=25 +lang_version=26 project_group=net.momirealms latest_supported_version=1.21.8 From ea6148cef09d0d1abd1b0eecff19ecc9ff1fc95c Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 7 Sep 2025 13:19:57 +0800 Subject: [PATCH 048/226] =?UTF-8?q?fix(build):=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E6=BC=8F=E6=8E=89=E7=9A=84=E8=BD=AF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/paper-loader/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index 94abf7189..0e848216c 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -86,6 +86,9 @@ paper { register("MythicMobs") { required = false } register("CustomFishing") { required = false } register("Zaphkiel") { required = false } + register("HeadDatabase") { required = false } + register("SX-Item") { required = false } + register("Slimefun") { required = false } // leveler register("AuraSkills") { required = false } From ab2d02afadeb137a41545c3074d6375ebbc331c9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 17:54:46 +0800 Subject: [PATCH 049/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D1.21.2=E9=87=8D?= =?UTF-8?q?=E5=8F=A0item=20model=20json=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/AbstractPackManager.java | 36 +++++++++++++++---- .../core/pack/model/LegacyOverridesModel.java | 2 +- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c4dab03ce..60433897e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -1851,9 +1851,13 @@ public abstract class AbstractPackManager implements PackManager { } JsonArray overrides = new JsonArray(); for (LegacyOverridesModel legacyOverridesModel : legacyOverridesModels) { - overrides.add(legacyOverridesModel.toLegacyPredicateElement()); + if (legacyOverridesModel.hasPredicate()) { + overrides.add(legacyOverridesModel.toLegacyPredicateElement()); + } + } + if (!overrides.isEmpty()) { + itemJson.add("overrides", overrides); } - itemJson.add("overrides", overrides); } catch (IOException e) { this.plugin.logger().warn("Failed to read item json " + itemPath.toAbsolutePath()); continue; @@ -1861,13 +1865,31 @@ public abstract class AbstractPackManager implements PackManager { } else { // 如果路径不存在,则需要我们创建一个json对象,并对接model的路径 itemJson = new JsonObject(); - LegacyOverridesModel firstModel = legacyOverridesModels.getFirst(); - itemJson.addProperty("parent", firstModel.model()); - JsonArray overrides = new JsonArray(); + + LegacyOverridesModel firstBaseModel = null; + List overrideJsons = new ArrayList<>(); for (LegacyOverridesModel legacyOverridesModel : legacyOverridesModels) { - overrides.add(legacyOverridesModel.toLegacyPredicateElement()); + if (!legacyOverridesModel.hasPredicate()) { + if (firstBaseModel == null) { + firstBaseModel = legacyOverridesModel; + } + } else { + JsonObject legacyPredicateElement = legacyOverridesModel.toLegacyPredicateElement(); + overrideJsons.add(legacyPredicateElement); + } + } + if (firstBaseModel == null) { + firstBaseModel = legacyOverridesModels.getFirst(); + } + + itemJson.addProperty("parent", firstBaseModel.model()); + if (!overrideJsons.isEmpty()) { + JsonArray overrides = new JsonArray(); + for (JsonObject override : overrideJsons) { + overrides.add(override); + } + itemJson.add("overrides", overrides); } - itemJson.add("overrides", overrides); } try { Files.createDirectories(itemPath.getParent()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java index cbb4f5179..6f93c7777 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java @@ -27,7 +27,7 @@ public class LegacyOverridesModel implements Comparable { } public boolean hasPredicate() { - return !predicate.isEmpty(); + return predicate != null && !predicate.isEmpty(); } public String model() { From 447e7c2a0a9cd5362e049421d511df41c167c645 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 20:41:04 +0800 Subject: [PATCH 050/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BF=AE=E5=A4=8D=E9=85=8D=E6=96=B9=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/entity/SimpleStorageBlockEntity.java | 2 -- .../bukkit/entity/furniture/BukkitFurnitureManager.java | 1 - .../bukkit/entity/furniture/hitbox/CustomHitBox.java | 1 - .../bukkit/entity/furniture/hitbox/HappyGhastHitBox.java | 1 - .../bukkit/entity/furniture/hitbox/InteractionHitBox.java | 1 - .../craftengine/bukkit/item/factory/UniversalItemFactory.java | 1 - .../craftengine/bukkit/item/recipe/RecipeEventListener.java | 1 - .../resources/resources/default/configuration/templates.yml | 2 +- .../craftengine/core/block/AbstractBlockManager.java | 1 - .../craftengine/core/entity/furniture/HitBoxFactory.java | 1 - .../craftengine/core/item/modifier/DyedColorModifier.java | 1 - .../core/pack/model/generation/display/DisplayMeta.java | 1 - .../java/net/momirealms/craftengine/core/util/MiscUtils.java | 4 ---- gradle.properties | 4 ++-- 14 files changed, 3 insertions(+), 19 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index 742295096..c15a2d687 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.ListTag; -import net.momirealms.sparrow.nbt.Tag; import org.bukkit.Bukkit; import org.bukkit.GameEvent; import org.bukkit.GameMode; @@ -31,7 +30,6 @@ import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Map; import java.util.Optional; public class SimpleStorageBlockEntity extends BlockEntity { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index 3a8f53497..7cc579dce 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -15,7 +15,6 @@ import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java index 763c9a654..f5b3d87c1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java @@ -8,7 +8,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkRefl import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java index fde041616..91754fd9b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java @@ -9,7 +9,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityType import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.World; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java index cd0d1a523..f8ae1852e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java @@ -7,7 +7,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldPosition; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java index 172279912..83520ce08 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java @@ -190,7 +190,6 @@ public class UniversalItemFactory extends BukkitItemFactory { @Override protected void maxDamage(LegacyItemWrapper item, Integer damage) { - throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 78bf6203f..0b9f047cd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -318,7 +318,6 @@ public class RecipeEventListener implements Listener { return; } - if (firstCustom.isPresent()) { CustomItem firstCustomItem = firstCustom.get(); if (firstCustomItem.settings().canRepair() == Tristate.FALSE) { diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 5760c9c18..8e6a62609 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -3142,7 +3142,7 @@ templates#recipes: category: building group: planks ingredients: - A: '#default:${wood_type}_logs' + - '#default:${wood_type}_logs' result: id: default:${wood_type}_planks count: 4 diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 2ecd81814..96916ea58 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -5,7 +5,6 @@ import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java index 1f06c8846..6b85ae871 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxFactory.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.entity.furniture; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.List; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java index 73cc1c0ae..81e964094 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DyedColorModifier.java @@ -6,7 +6,6 @@ import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; import net.momirealms.craftengine.core.util.Color; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java index 8b9b51f2e..fb416275d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/generation/display/DisplayMeta.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.pack.model.generation.display; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.joml.Vector3f; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index df5b8e924..8e5a2864a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -1,9 +1,5 @@ package net.momirealms.craftengine.core.util; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import org.joml.Quaternionf; -import org.joml.Vector3f; - import java.util.*; public class MiscUtils { diff --git a/gradle.properties b/gradle.properties index 7f34bd693..73e1ce710 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.15 +project_version=0.0.62.16 config_version=45 lang_version=25 project_group=net.momirealms @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.77 +nms_helper_version=1.0.79 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From c205da109b909eb2659e88e6b6efa19cf288813c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 7 Sep 2025 21:43:31 +0800 Subject: [PATCH 051/226] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=89=A9=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/recipe/RecipeEventListener.java | 7 +++--- .../bukkit/plugin/BukkitCraftEngine.java | 12 +++++----- .../plugin/injector/RecipeInjector.java | 2 +- .../craftengine/core/item/ItemSettings.java | 22 ++++++++++++------- .../core/item/setting/Repairable.java | 20 +++++++++++++++++ gradle.properties | 2 +- 6 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/setting/Repairable.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 0b9f047cd..ef458eacb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -261,7 +261,6 @@ public class RecipeEventListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onAnvilEvent(PrepareAnvilEvent event) { - if (event.getResult() == null) return; preProcess(event); processRepairable(event); processRename(event); @@ -271,6 +270,7 @@ public class RecipeEventListener implements Listener { 预处理会阻止一些不合理的原版材质造成的合并问题 */ private void preProcess(PrepareAnvilEvent event) { + if (event.getResult() == null) return; AnvilInventory inventory = event.getInventory(); ItemStack first = inventory.getFirstItem(); ItemStack second = inventory.getSecondItem(); @@ -320,7 +320,7 @@ public class RecipeEventListener implements Listener { if (firstCustom.isPresent()) { CustomItem firstCustomItem = firstCustom.get(); - if (firstCustomItem.settings().canRepair() == Tristate.FALSE) { + if (firstCustomItem.settings().repairable().anvilCombine() == Tristate.FALSE) { event.setResult(null); return; } @@ -372,7 +372,7 @@ public class RecipeEventListener implements Listener { Key firstId = wrappedFirst.id(); Optional> optionalCustomTool = wrappedFirst.getCustomItem(); // 物品无法被修复 - if (optionalCustomTool.isPresent() && optionalCustomTool.get().settings().canRepair() == Tristate.FALSE) { + if (optionalCustomTool.isPresent() && optionalCustomTool.get().settings().repairable().anvilRepair() == Tristate.FALSE) { return; } @@ -493,6 +493,7 @@ public class RecipeEventListener implements Listener { */ @SuppressWarnings("UnstableApiUsage") private void processRename(PrepareAnvilEvent event) { + if (event.getResult() == null) return; AnvilInventory inventory = event.getInventory(); ItemStack first = inventory.getFirstItem(); if (ItemStackUtils.isEmpty(first)) { 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 b191553b8..e69ac269a 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 @@ -89,11 +89,13 @@ public class BukkitCraftEngine extends CraftEngine { super.logger = logger; super.platform = new BukkitPlatform(); super.scheduler = new BukkitSchedulerAdapter(this); - Class compatibilityClass = Objects.requireNonNull(ReflectionUtils.getClazz(COMPATIBILITY_CLASS), "Compatibility class not found"); - try { - super.compatibilityManager = (CompatibilityManager) Objects.requireNonNull(ReflectionUtils.getConstructor(compatibilityClass, 0)).newInstance(this); - } catch (ReflectiveOperationException e) { - logger().warn("Compatibility class could not be instantiated: " + compatibilityClass.getName()); + Class compatibilityClass = ReflectionUtils.getClazz(COMPATIBILITY_CLASS); + if (compatibilityClass != null) { + try { + super.compatibilityManager = (CompatibilityManager) Objects.requireNonNull(ReflectionUtils.getConstructor(compatibilityClass, 0)).newInstance(this); + } catch (ReflectiveOperationException e) { + logger().warn("Compatibility class could not be instantiated: " + compatibilityClass.getName()); + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java index 1149aa9b9..d474df0f8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java @@ -240,7 +240,7 @@ public final class RecipeInjector { if (input2.count() != 1 || !isDamageableItem(input2)) return false; if (!input1.id().equals(input2.id())) return false; Optional> customItem = input1.getCustomItem(); - return customItem.isEmpty() || customItem.get().settings().canRepair() != Tristate.FALSE; + return customItem.isEmpty() || customItem.get().settings().repairable().craftingTable() != Tristate.FALSE; } private static boolean isDamageableItem(Item item) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java index b53902ccf..03c355356 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java @@ -25,7 +25,7 @@ import java.util.stream.Collectors; public class ItemSettings { int fuelTime; Set tags = Set.of(); - Tristate canRepair = Tristate.UNDEFINED; + Repairable repairable = Repairable.UNDEFINED; List anvilRepairItems = List.of(); boolean renameable = true; boolean canPlaceRelatedVanillaBlock = false; @@ -89,7 +89,7 @@ public class ItemSettings { newSettings.fuelTime = settings.fuelTime; newSettings.tags = settings.tags; newSettings.equipment = settings.equipment; - newSettings.canRepair = settings.canRepair; + newSettings.repairable = settings.repairable; newSettings.anvilRepairItems = settings.anvilRepairItems; newSettings.renameable = settings.renameable; newSettings.canPlaceRelatedVanillaBlock = settings.canPlaceRelatedVanillaBlock; @@ -128,8 +128,8 @@ public class ItemSettings { return canPlaceRelatedVanillaBlock; } - public Tristate canRepair() { - return canRepair; + public Repairable repairable() { + return repairable; } public int fuelTime() { @@ -233,8 +233,8 @@ public class ItemSettings { return this; } - public ItemSettings canRepair(Tristate canRepair) { - this.canRepair = canRepair; + public ItemSettings repairable(Repairable repairable) { + this.repairable = repairable; return this; } @@ -315,8 +315,14 @@ public class ItemSettings { static { registerFactory("repairable", (value -> { - boolean bool = ResourceConfigUtils.getAsBoolean(value, "repairable"); - return settings -> settings.canRepair(bool ? Tristate.TRUE : Tristate.FALSE); + if (value instanceof Map mapValue) { + Map repairableData = ResourceConfigUtils.getAsMap(mapValue, "repairable"); + Repairable repairable = Repairable.fromMap(repairableData); + return settings -> settings.repairable(repairable); + } else { + boolean bool = ResourceConfigUtils.getAsBoolean(value, "repairable"); + return settings -> settings.repairable(bool ? Repairable.TRUE : Repairable.FALSE); + } })); registerFactory("enchantable", (value -> { boolean bool = ResourceConfigUtils.getAsBoolean(value, "enchantable"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/setting/Repairable.java b/core/src/main/java/net/momirealms/craftengine/core/item/setting/Repairable.java new file mode 100644 index 000000000..36ff26fe3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/setting/Repairable.java @@ -0,0 +1,20 @@ +package net.momirealms.craftengine.core.item.setting; + +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.Tristate; + +import java.util.Map; + +public record Repairable(Tristate craftingTable, Tristate anvilRepair, Tristate anvilCombine) { + + public static final Repairable UNDEFINED = new Repairable(Tristate.UNDEFINED, Tristate.UNDEFINED, Tristate.UNDEFINED); + public static final Repairable TRUE = new Repairable(Tristate.TRUE, Tristate.TRUE, Tristate.TRUE); + public static final Repairable FALSE = new Repairable(Tristate.FALSE, Tristate.FALSE, Tristate.FALSE); + + public static Repairable fromMap(Map map) { + Tristate craftingTable = map.containsKey("crafting-table") ? Tristate.of(ResourceConfigUtils.getAsBoolean(map.get("crafting-table"), "crafting-table")) : Tristate.UNDEFINED; + Tristate anvilRepair = map.containsKey("anvil-repair") ? Tristate.of(ResourceConfigUtils.getAsBoolean(map.get("anvil-repair"), "anvil-repair")) : Tristate.UNDEFINED; + Tristate anvilCombine = map.containsKey("anvil-combine") ? Tristate.of(ResourceConfigUtils.getAsBoolean(map.get("anvil-combine"), "anvil-combine")) : Tristate.UNDEFINED; + return new Repairable(craftingTable, anvilRepair, anvilCombine); + } +} diff --git a/gradle.properties b/gradle.properties index 73e1ce710..90bc010eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.79 +nms_helper_version=1.0.80 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From a4a812d14f69c1aee88c7405fe065ad97154aaf4 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 8 Sep 2025 19:49:18 +0800 Subject: [PATCH 052/226] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B2=99=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 4 +- .../block/behavior/LampBlockBehavior.java | 34 +- ...ckBehavior.java => SofaBlockBehavior.java} | 64 ++-- .../behavior/ToggleableLampBlockBehavior.java | 29 +- .../main/resources/additional-real-blocks.yml | 2 +- .../default/configuration/blocks.yml | 347 ++++++++++++++++++ .../default/configuration/categories.yml | 7 +- .../resources/default/configuration/i18n.yml | 2 + .../minecraft/models/block/custom/sofa.json | 118 ++++++ .../models/item/custom/sofa_inner.json | 144 ++++++++ .../models/item/custom/sofa_straight.json | 132 +++++++ .../minecraft/textures/item/custom/sofa.png | Bin 0 -> 1663 bytes .../src/main/resources/translations/en.yml | 4 +- .../src/main/resources/translations/zh_cn.yml | 4 +- .../core/block/properties/Properties.java | 3 + .../block/state/properties/SofaShape.java | 7 + .../core/pack/AbstractPackManager.java | 4 + 17 files changed, 823 insertions(+), 82 deletions(-) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/{BottomHalfStairsBlockBehavior.java => SofaBlockBehavior.java} (56%) create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 5062787f0..71f3de6ed 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -30,7 +30,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key CHANGE_OVER_TIME_BLOCK = Key.from("craftengine:change_over_time_block"); public static final Key SIMPLE_STORAGE_BLOCK = Key.from("craftengine:simple_storage_block"); public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); - public static final Key BOTTOM_HALF_STAIRS_BLOCK = Key.from("craftengine:bottom_half_stairs_block"); + public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -59,6 +59,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(CHANGE_OVER_TIME_BLOCK, ChangeOverTimeBlockBehavior.FACTORY); register(SIMPLE_STORAGE_BLOCK, SimpleStorageBlockBehavior.FACTORY); register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); - register(BOTTOM_HALF_STAIRS_BLOCK, BottomHalfStairsBlockBehavior.FACTORY); + register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java index cd46e8ff8..432837793 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; @@ -9,9 +8,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; -import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; @@ -22,34 +19,14 @@ import java.util.concurrent.Callable; public class LampBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property litProperty; - private final boolean canOpenWithHand; - public LampBlockBehavior(CustomBlock block, Property litProperty, boolean canOpenWithHand) { + public LampBlockBehavior(CustomBlock block, Property litProperty) { super(block); this.litProperty = litProperty; - this.canOpenWithHand = canOpenWithHand; - } - - @Override - public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { - if (!this.canOpenWithHand) { - return InteractionResult.PASS; - } - LampBlockBehavior behavior = state.behavior().getAs(LampBlockBehavior.class).orElse(null); - if (behavior == null) return InteractionResult.PASS; - FastNMS.INSTANCE.method$LevelWriter$setBlock( - context.getLevel().serverWorld(), - LocationUtils.toBlockPos(context.getClickedPos()), - state.cycle(behavior.litProperty).customBlockState().literalObject(), - 2 - ); - Optional.ofNullable(context.getPlayer()).ifPresent(p -> p.swingHand(context.getHand())); - return InteractionResult.SUCCESS_AND_CANCEL; } @Override public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { - if (this.canOpenWithHand) return state; Object level = context.getLevel().serverWorld(); state = state.with(this.litProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); return state; @@ -58,10 +35,9 @@ public class LampBlockBehavior extends BukkitBlockBehavior { @Override public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Object blockState = args[0]; - Object world = args[1]; - if (this.canOpenWithHand || !CoreReflections.clazz$ServerLevel.isInstance(world)) return; Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; + Object world = args[1]; Object blockPos = args[2]; ImmutableBlockState customState = optionalCustomState.get(); if (customState.get(this.litProperty) && !FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { @@ -75,10 +51,9 @@ public class LampBlockBehavior extends BukkitBlockBehavior { @Override public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) { Object blockState = args[0]; - Object world = args[1]; - if (this.canOpenWithHand || !CoreReflections.clazz$ServerLevel.isInstance(world)) return; Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; + Object world = args[1]; Object blockPos = args[2]; ImmutableBlockState customState = optionalCustomState.get(); boolean lit = customState.get(this.litProperty); @@ -99,8 +74,7 @@ public class LampBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.lamp.missing_lit"); - boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); - return new LampBlockBehavior(block, lit, canOpenWithHand); + return new LampBlockBehavior(block, lit); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java similarity index 56% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java index ad4a00690..7d7498c36 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BottomHalfStairsBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java @@ -10,7 +10,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.StairsShape; +import net.momirealms.craftengine.core.block.state.properties.SofaShape; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; @@ -22,12 +22,12 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -public class BottomHalfStairsBlockBehavior extends BukkitBlockBehavior { +public class SofaBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; - private final Property shapeProperty; + private final Property shapeProperty; - public BottomHalfStairsBlockBehavior(CustomBlock block, Property facing, Property shape) { + public SofaBlockBehavior(CustomBlock block, Property facing, Property shape) { super(block); this.facingProperty = facing; this.shapeProperty = shape; @@ -42,7 +42,7 @@ public class BottomHalfStairsBlockBehavior extends BukkitBlockBehavior { Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(context.getLevel().serverWorld(), LocationUtils.toBlockPos(clickedPos)); blockState = blockState.with(this.waterloggedProperty, FastNMS.INSTANCE.method$FluidState$getType(fluidState) == MFluids.WATER); } - return blockState.with(this.shapeProperty, getStairsShape(blockState, context.getLevel().serverWorld(), clickedPos)); + return blockState.with(this.shapeProperty, getSofaShape(blockState, context.getLevel().serverWorld(), clickedPos)); } @Override @@ -57,47 +57,31 @@ public class BottomHalfStairsBlockBehavior extends BukkitBlockBehavior { FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[updateShape$level], args[updateShape$blockPos], MFluids.WATER, 5); } Direction direction = DirectionUtils.fromNMSDirection(VersionHelper.isOrAbove1_21_2() ? args[4] : args[1]); - StairsShape stairsShape = getStairsShape(customState, level, LocationUtils.fromBlockPos(blockPos)); + SofaShape sofaShape = getSofaShape(customState, level, LocationUtils.fromBlockPos(blockPos)); return direction.axis().isHorizontal() - ? customState.with(this.shapeProperty, stairsShape).customBlockState().literalObject() + ? customState.with(this.shapeProperty, sofaShape).customBlockState().literalObject() : superMethod.call(); } - private StairsShape getStairsShape(ImmutableBlockState state, Object level, BlockPos pos) { + private SofaShape getSofaShape(ImmutableBlockState state, Object level, BlockPos pos) { Direction direction = state.get(this.facingProperty).toDirection(); - Object relativeBlockState1 = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(direction))); - Optional optionalCustomState1 = BlockStateUtils.getOptionalCustomBlockState(relativeBlockState1); - if (optionalCustomState1.isPresent()) { - ImmutableBlockState customState1 = optionalCustomState1.get(); - Optional optionalStairsBlockBehavior = customState1.behavior().getAs(BottomHalfStairsBlockBehavior.class); + Object relativeBlockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(direction.opposite()))); + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(relativeBlockState); + if (optionalCustomState.isPresent()) { + ImmutableBlockState customState = optionalCustomState.get(); + Optional optionalStairsBlockBehavior = customState.behavior().getAs(SofaBlockBehavior.class); if (optionalStairsBlockBehavior.isPresent()) { - BottomHalfStairsBlockBehavior stairsBlockBehavior = optionalStairsBlockBehavior.get(); - Direction direction1 = customState1.get(stairsBlockBehavior.facingProperty).toDirection(); - if (direction1.axis() != state.get(this.facingProperty).toDirection().axis() && canTakeShape(state, level, pos, direction1.opposite())) { + SofaBlockBehavior stairsBlockBehavior = optionalStairsBlockBehavior.get(); + Direction direction1 = customState.get(stairsBlockBehavior.facingProperty).toDirection(); + if (direction1.axis() != state.get(this.facingProperty).toDirection().axis() && canTakeShape(state, level, pos, direction1)) { if (direction1 == direction.counterClockWise()) { - return StairsShape.OUTER_LEFT; + return SofaShape.INNER_LEFT; } - return StairsShape.OUTER_RIGHT; + return SofaShape.INNER_RIGHT; } } } - Object relativeBlockState2 = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(pos.relative(direction.opposite()))); - Optional optionalCustomState2 = BlockStateUtils.getOptionalCustomBlockState(relativeBlockState2); - if (optionalCustomState2.isPresent()) { - ImmutableBlockState customState2 = optionalCustomState2.get(); - Optional optionalStairsBlockBehavior = customState2.behavior().getAs(BottomHalfStairsBlockBehavior.class); - if (optionalStairsBlockBehavior.isPresent()) { - BottomHalfStairsBlockBehavior stairsBlockBehavior = optionalStairsBlockBehavior.get(); - Direction direction2 = customState2.get(stairsBlockBehavior.facingProperty).toDirection(); - if (direction2.axis() != state.get(this.facingProperty).toDirection().axis() && canTakeShape(state, level, pos, direction2)) { - if (direction2 == direction.counterClockWise()) { - return StairsShape.INNER_LEFT; - } - return StairsShape.INNER_RIGHT; - } - } - } - return StairsShape.STRAIGHT; + return SofaShape.STRAIGHT; } private boolean canTakeShape(ImmutableBlockState state, Object level, BlockPos pos, Direction face) { @@ -107,11 +91,11 @@ public class BottomHalfStairsBlockBehavior extends BukkitBlockBehavior { return true; } ImmutableBlockState anotherState = optionalAnotherState.get(); - Optional optionalBehavior = anotherState.behavior().getAs(BottomHalfStairsBlockBehavior.class); + Optional optionalBehavior = anotherState.behavior().getAs(SofaBlockBehavior.class); if (optionalBehavior.isEmpty()) { return true; } - BottomHalfStairsBlockBehavior anotherBehavior = optionalBehavior.get(); + SofaBlockBehavior anotherBehavior = optionalBehavior.get(); return anotherState.get(anotherBehavior.facingProperty) != state.get(this.facingProperty); } @@ -120,9 +104,9 @@ public class BottomHalfStairsBlockBehavior extends BukkitBlockBehavior { @Override @SuppressWarnings("unchecked") public BlockBehavior create(CustomBlock block, Map arguments) { - Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.bottom_half_stairs.missing_facing"); - Property shape = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("shape"), "warning.config.block.behavior.bottom_half_stairs.missing_shape"); - return new BottomHalfStairsBlockBehavior(block, facing, shape); + Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.sofa.missing_facing"); + Property shape = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("shape"), "warning.config.block.behavior.sofa.missing_shape"); + return new SofaBlockBehavior(block, facing, shape); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java index f47417c55..92894f798 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java @@ -3,11 +3,14 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; @@ -18,15 +21,35 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property litProperty; private final Property poweredProperty; + private final boolean canOpenWithHand; - public ToggleableLampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty) { + public ToggleableLampBlockBehavior(CustomBlock block, Property litProperty, Property poweredProperty, boolean canOpenWithHand) { super(block); this.litProperty = litProperty; this.poweredProperty = poweredProperty; + this.canOpenWithHand = canOpenWithHand; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + if (!this.canOpenWithHand) { + return InteractionResult.PASS; + } + ToggleableLampBlockBehavior behavior = state.behavior().getAs(ToggleableLampBlockBehavior.class).orElse(null); + if (behavior == null) return InteractionResult.PASS; + FastNMS.INSTANCE.method$LevelWriter$setBlock( + context.getLevel().serverWorld(), + LocationUtils.toBlockPos(context.getClickedPos()), + state.cycle(behavior.litProperty).customBlockState().literalObject(), + 2 + ); + Optional.ofNullable(context.getPlayer()).ifPresent(p -> p.swingHand(context.getHand())); + return InteractionResult.SUCCESS_AND_CANCEL; } @Override public void onPlace(Object thisBlock, Object[] args, Callable superMethod) { + if (this.canOpenWithHand) return; Object state = args[0]; Object level = args[1]; Object pos = args[2]; @@ -40,6 +63,7 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { @Override public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) { + if (this.canOpenWithHand) return; Object blockState = args[0]; Object world = args[1]; if (!CoreReflections.clazz$ServerLevel.isInstance(world)) return; @@ -69,7 +93,8 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { public BlockBehavior create(CustomBlock block, Map arguments) { Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.toggleable_lamp.missing_lit"); Property powered = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.toggleable_lamp.missing_powered"); - return new ToggleableLampBlockBehavior(block, lit, powered); + boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); + return new ToggleableLampBlockBehavior(block, lit, powered, canOpenWithHand); } } } diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 3f92d92f0..afdf2f7ea 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -81,4 +81,4 @@ minecraft:cherry_fence_gate: 16 minecraft:bamboo_fence_gate: 16 minecraft:crimson_fence_gate: 16 minecraft:warped_fence_gate: 16 -minecraft:cactus: 15 \ No newline at end of file +minecraft:barrier: 24 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 573ed098d..d1fa2dbd4 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -555,6 +555,353 @@ items#misc: facing=west,open=true: appearance: west_open id: 29 + default:sofa: + material: nether_brick + custom-model-data: 3008 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/sofa + generation: + parent: minecraft:block/custom/sofa + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 2 + resistance: 3 + map-color: 56 + burn-chance: 5 + fire-spread-chance: 20 + burnable: true + is-suffocating: false + is-redstone-conductor: false + push-reaction: BLOCK + instrument: BASS + sounds: + break: minecraft:block.wood.break + fall: minecraft:block.wood.fall + hit: minecraft:block.wood.hit + place: minecraft:block.wood.place + step: minecraft:block.wood.step + states: + properties: + waterlogged: + type: boolean + default: false + appearances: + waterlogged=false: + state: sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=false] + model: + path: minecraft:block/custom/sofa + waterlogged=true: + state: sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=true] + model: + path: minecraft:block/custom/sofa + variants: + waterlogged=false: + appearance: waterlogged=false + id: 0 + waterlogged=true: + appearance: waterlogged=true + id: 1 + + default:connectable_sofa_straight: + material: nether_brick + custom-model-data: 3009 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/sofa_straight + default:connectable_sofa_inner: + material: nether_brick + custom-model-data: 3010 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/sofa_inner + default:connectable_sofa: + material: nether_brick + custom-model-data: 3011 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/sofa_straight + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 2 + resistance: 3 + map-color: 56 + burn-chance: 5 + fire-spread-chance: 20 + burnable: true + is-suffocating: false + is-redstone-conductor: false + push-reaction: BLOCK + instrument: BASS + sounds: + break: minecraft:block.wood.break + fall: minecraft:block.wood.fall + hit: minecraft:block.wood.hit + place: minecraft:block.wood.place + step: minecraft:block.wood.step + behavior: + type: sofa_block + states: + properties: + facing: + type: horizontal_direction + shape: + type: sofa_shape + waterlogged: + type: boolean + default: false + appearances: + facing=east,shape=straight,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_straight + yaw: 90 + facing=north,shape=straight,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_straight + facing=south,shape=straight,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_straight + yaw: 180 + facing=west,shape=straight,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_straight + yaw: 270 + facing=east,shape=straight,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_straight + yaw: 90 + facing=north,shape=straight,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_straight + facing=south,shape=straight,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_straight + yaw: 180 + facing=west,shape=straight,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_straight + yaw: 270 + facing=east,shape=inner_left,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + facing=north,shape=inner_left,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 270 + facing=south,shape=inner_left,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 90 + facing=west,shape=inner_left,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 180 + facing=east,shape=inner_left,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + facing=north,shape=inner_left,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 270 + facing=south,shape=inner_left,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 90 + facing=west,shape=inner_left,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 180 + facing=east,shape=inner_right,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 90 + facing=north,shape=inner_right,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + facing=south,shape=inner_right,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 180 + facing=west,shape=inner_right,waterlogged=false: + state: barrier[waterlogged=false] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 270 + facing=east,shape=inner_right,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 90 + facing=north,shape=inner_right,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + facing=south,shape=inner_right,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 180 + facing=west,shape=inner_right,waterlogged=true: + state: barrier[waterlogged=true] + entity-renderer: + item: default:connectable_sofa_inner + yaw: 270 + variants: + facing=east,shape=inner_left,waterlogged=false: + appearance: facing=east,shape=inner_left,waterlogged=false + id: 0 + facing=east,shape=inner_right,waterlogged=false: + appearance: facing=east,shape=inner_right,waterlogged=false + id: 1 + facing=east,shape=straight,waterlogged=false: + appearance: facing=east,shape=straight,waterlogged=false + id: 2 + facing=north,shape=inner_left,waterlogged=false: + appearance: facing=north,shape=inner_left,waterlogged=false + id: 3 + facing=north,shape=inner_right,waterlogged=false: + appearance: facing=north,shape=inner_right,waterlogged=false + id: 4 + facing=north,shape=straight,waterlogged=false: + appearance: facing=north,shape=straight,waterlogged=false + id: 5 + facing=south,shape=inner_left,waterlogged=false: + appearance: facing=south,shape=inner_left,waterlogged=false + id: 6 + facing=south,shape=inner_right,waterlogged=false: + appearance: facing=south,shape=inner_right,waterlogged=false + id: 7 + facing=south,shape=straight,waterlogged=false: + appearance: facing=south,shape=straight,waterlogged=false + id: 8 + facing=west,shape=inner_left,waterlogged=false: + appearance: facing=west,shape=inner_left,waterlogged=false + id: 9 + facing=west,shape=inner_right,waterlogged=false: + appearance: facing=west,shape=inner_right,waterlogged=false + id: 10 + facing=west,shape=straight,waterlogged=false: + appearance: facing=west,shape=straight,waterlogged=false + id: 11 + facing=east,shape=inner_left,waterlogged=true: + appearance: facing=east,shape=inner_left,waterlogged=true + id: 12 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=east,shape=inner_right,waterlogged=true: + appearance: facing=east,shape=inner_right,waterlogged=true + id: 13 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=east,shape=straight,waterlogged=true: + appearance: facing=east,shape=straight,waterlogged=true + id: 14 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=north,shape=inner_left,waterlogged=true: + appearance: facing=north,shape=inner_left,waterlogged=true + id: 15 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=north,shape=inner_right,waterlogged=true: + appearance: facing=north,shape=inner_right,waterlogged=true + id: 16 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=north,shape=straight,waterlogged=true: + appearance: facing=north,shape=straight,waterlogged=true + id: 17 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=south,shape=inner_left,waterlogged=true: + appearance: facing=south,shape=inner_left,waterlogged=true + id: 18 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=south,shape=inner_right,waterlogged=true: + appearance: facing=south,shape=inner_right,waterlogged=true + id: 19 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=south,shape=straight,waterlogged=true: + appearance: facing=south,shape=straight,waterlogged=true + id: 20 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=west,shape=inner_left,waterlogged=true: + appearance: facing=west,shape=inner_left,waterlogged=true + id: 21 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=west,shape=inner_right,waterlogged=true: + appearance: facing=west,shape=inner_right,waterlogged=true + id: 22 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + facing=west,shape=straight,waterlogged=true: + appearance: facing=west,shape=straight,waterlogged=true + id: 23 + settings: + resistance: 1200.0 + burnable: false + fluid-state: water recipes#misc: default:chinese_lantern: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index abe2f99fd..8614b02f7 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -72,11 +72,12 @@ categories: - default:solid_gunpowder_block - default:ender_pearl_flower_seeds - default:gui_head_size_1 - - default:gui_head_size_4 - - minecraft:air - default:copper_coil - default:flame_elytra - default:cap - default:pebble - default:chessboard_block - - default:safe_block \ No newline at end of file + - default:safe_block + - default:sofa + - default:connectable_sofa + - default:gui_head_size_4 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index cf3e1cb2e..6af2e7556 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -47,6 +47,7 @@ i18n: item.flower_basket: Flower Basket item.chessboard_block: Chessboard Block item.safe_block: Safe Block + item.sofa: Sofa category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -104,6 +105,7 @@ i18n: item.flower_basket: 花篮 item.chessboard_block: 棋盘方块 item.safe_block: 保险柜 + item.sofa: 沙发 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json new file mode 100644 index 000000000..07bbf9eaa --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json @@ -0,0 +1,118 @@ +{ + "credit": "Made with Blockbench", + "textures": { + "0": "item/custom/sofa", + "particle": "item/custom/sofa" + }, + "elements": [ + { + "from": [1, 0, 1], + "to": [3, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]}, + "faces": { + "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "up": {"uv": [16, 15.5, 15.5, 15], "texture": "#0"}, + "down": {"uv": [16, 15.5, 15.5, 16], "texture": "#0"} + } + }, + { + "from": [13, 0, 1], + "to": [15, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 1]}, + "faces": { + "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15.5, 16, 15], "texture": "#0"}, + "down": {"uv": [15.5, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "from": [13, 0, 13], + "to": [15, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 15]}, + "faces": { + "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "south": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15, 16, 15.5], "texture": "#0"}, + "down": {"uv": [15.5, 16, 16, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 13], + "to": [3, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, + "faces": { + "north": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "west": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [16, 15, 15.5, 15.5], "texture": "#0"}, + "down": {"uv": [16, 16, 15.5, 15.5], "texture": "#0"} + } + }, + { + "from": [0.01, 2.51, 0.01], + "to": [15.99, 8.49, 15.99], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, + "faces": { + "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "east": {"uv": [8, 11.5, 4, 13], "texture": "#0"}, + "south": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "west": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "up": {"uv": [4, 4, 0, 0], "texture": "#0"}, + "down": {"uv": [4, 12, 0, 16], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [60, -34, 0], + "translation": [-0.5, 3, 0.25], + "scale": [0.25, 0.25, 0.25] + }, + "thirdperson_lefthand": { + "rotation": [60, -34, 0], + "translation": [-0.5, 3, 0.25], + "scale": [0.25, 0.25, 0.25] + }, + "firstperson_righthand": { + "rotation": [0, 180, 0], + "translation": [0.5, 0.5, 0], + "scale": [0.5, 0.5, 0.5] + }, + "firstperson_lefthand": { + "rotation": [0, 180, 0], + "translation": [0.5, 0.5, 0], + "scale": [0.5, 0.5, 0.5] + }, + "ground": { + "translation": [0, 2.75, 0], + "scale": [0.35, 0.35, 0.35] + }, + "gui": { + "rotation": [25, -135, 0], + "translation": [0.25, -1, 0], + "scale": [0.5, 0.5, 0.5] + }, + "fixed": { + "rotation": [-90, 0, 0], + "translation": [0, 0, -16], + "scale": [2, 2, 2] + } + }, + "groups": [ + { + "name": "group", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4] + } + ] +} \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json new file mode 100644 index 000000000..595bdfb4f --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json @@ -0,0 +1,144 @@ +{ + "credit": "Made with Blockbench", + "textures": { + "0": "item/custom/sofa", + "particle": "item/custom/sofa" + }, + "elements": [ + { + "from": [1, 0, 1], + "to": [3, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]}, + "faces": { + "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "up": {"uv": [16, 15.5, 15.5, 15], "texture": "#0"}, + "down": {"uv": [16, 15.5, 15.5, 16], "texture": "#0"} + } + }, + { + "from": [13, 0, 1], + "to": [15, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 1]}, + "faces": { + "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15.5, 16, 15], "texture": "#0"}, + "down": {"uv": [15.5, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "from": [13, 0, 13], + "to": [15, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 15]}, + "faces": { + "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "south": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15, 16, 15.5], "texture": "#0"}, + "down": {"uv": [15.5, 16, 16, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 13], + "to": [3, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, + "faces": { + "north": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "west": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [16, 15, 15.5, 15.5], "texture": "#0"}, + "down": {"uv": [16, 16, 15.5, 15.5], "texture": "#0"} + } + }, + { + "from": [0.01, 2.51, 0.01], + "to": [15.99, 8.49, 15.99], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, + "faces": { + "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "east": {"uv": [8, 14.5, 4, 16], "texture": "#0"}, + "south": {"uv": [4, 13, 8, 14.5], "texture": "#0"}, + "west": {"uv": [4, 14.5, 8, 16], "texture": "#0"}, + "up": {"uv": [4, 8, 0, 4], "texture": "#0"}, + "down": {"uv": [4, 12, 0, 16], "texture": "#0"} + } + }, + { + "from": [0.02, 6.02, 12.02], + "to": [15.98, 19.98, 15.98], + "rotation": {"angle": 22.5, "axis": "x", "origin": [7, 8, 14]}, + "faces": { + "north": {"uv": [13, 8.5, 9, 12], "texture": "#0"}, + "east": {"uv": [8, 12.5, 9, 16], "texture": "#0"}, + "south": {"uv": [9, 12.5, 13, 16], "texture": "#0"}, + "west": {"uv": [9, 12.5, 8, 16], "texture": "#0"}, + "up": {"uv": [9, 12, 8, 8.5], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7.75, 10.5, 4.25, 11.5], "texture": "#0"} + } + }, + { + "from": [0.02, 6.02, 0.02], + "to": [3.98, 19.98, 15.98], + "rotation": {"angle": 22.5, "axis": "z", "origin": [2, 8, 8]}, + "faces": { + "north": {"uv": [9, 8.5, 8, 12], "texture": "#0"}, + "east": {"uv": [9, 8.5, 13, 12], "texture": "#0"}, + "south": {"uv": [8, 12.5, 9, 16], "texture": "#0"}, + "west": {"uv": [13, 12.5, 9, 16], "texture": "#0"}, + "up": {"uv": [8, 12, 9, 8.5], "texture": "#0"}, + "down": {"uv": [4.25, 11.5, 7.75, 10.5], "rotation": 90, "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [60, 180, 0], + "translation": [-0.5, 3, 0.25], + "scale": [0.25, 0.25, 0.25] + }, + "thirdperson_lefthand": { + "rotation": [60, 0, 0], + "translation": [-0.5, 3, 0.25], + "scale": [0.25, 0.25, 0.25] + }, + "firstperson_righthand": { + "rotation": [0, 180, 0], + "translation": [0.5, 0.5, 0], + "scale": [0.5, 0.5, 0.5] + }, + "firstperson_lefthand": { + "rotation": [0, 90, 0], + "translation": [0.5, 0.5, 0], + "scale": [0.5, 0.5, 0.5] + }, + "ground": { + "translation": [0, 2.75, 0], + "scale": [0.35, 0.35, 0.35] + }, + "gui": { + "rotation": [25, -135, 0], + "translation": [0.25, -1, 0], + "scale": [0.5, 0.5, 0.5] + }, + "fixed": { + "rotation": [-90, 0, 0], + "translation": [0, 0, -16], + "scale": [2, 2, 2] + } + }, + "groups": [ + { + "name": "group", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4, 5, 6] + } + ] +} \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json new file mode 100644 index 000000000..4c5174513 --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json @@ -0,0 +1,132 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "0": "item/custom/sofa", + "particle": "item/custom/sofa" + }, + "elements": [ + { + "from": [1, 0, 1], + "to": [3, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]}, + "faces": { + "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "up": {"uv": [16, 15.5, 15.5, 15], "texture": "#0"}, + "down": {"uv": [16, 15.5, 15.5, 16], "texture": "#0"} + } + }, + { + "from": [13, 0, 1], + "to": [15, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 1]}, + "faces": { + "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15.5, 16, 15], "texture": "#0"}, + "down": {"uv": [15.5, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "from": [13, 0, 13], + "to": [15, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 15]}, + "faces": { + "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "south": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15, 16, 15.5], "texture": "#0"}, + "down": {"uv": [15.5, 16, 16, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 13], + "to": [3, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, + "faces": { + "north": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "west": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [16, 15, 15.5, 15.5], "texture": "#0"}, + "down": {"uv": [16, 16, 15.5, 15.5], "texture": "#0"} + } + }, + { + "from": [0.01, 2.51, 0.01], + "to": [15.99, 8.49, 15.99], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, + "faces": { + "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "east": {"uv": [8, 14.5, 4, 16], "texture": "#0"}, + "south": {"uv": [4, 13, 8, 14.5], "texture": "#0"}, + "west": {"uv": [4, 14.5, 8, 16], "texture": "#0"}, + "up": {"uv": [4, 12, 0, 8], "texture": "#0"}, + "down": {"uv": [4, 12, 0, 16], "texture": "#0"} + } + }, + { + "from": [0.02, 6.02, 12.02], + "to": [15.98, 19.98, 15.98], + "rotation": {"angle": 22.5, "axis": "x", "origin": [7, 8, 14]}, + "faces": { + "north": {"uv": [9, 8.5, 13, 12], "texture": "#0"}, + "east": {"uv": [8, 12.5, 9, 16], "texture": "#0"}, + "south": {"uv": [9, 12.5, 13, 16], "texture": "#0"}, + "west": {"uv": [9, 12.5, 8, 16], "texture": "#0"}, + "up": {"uv": [9, 12, 8, 8.5], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7.75, 10.5, 4.25, 11.5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [60, -34, 0], + "translation": [-0.5, 3, 0.25], + "scale": [0.25, 0.25, 0.25] + }, + "thirdperson_lefthand": { + "rotation": [60, -34, 0], + "translation": [-0.5, 3, 0.25], + "scale": [0.25, 0.25, 0.25] + }, + "firstperson_righthand": { + "rotation": [0, 180, 0], + "translation": [0.5, 0.5, 0], + "scale": [0.5, 0.5, 0.5] + }, + "firstperson_lefthand": { + "rotation": [0, 180, 0], + "translation": [0.5, 0.5, 0], + "scale": [0.5, 0.5, 0.5] + }, + "ground": { + "translation": [0, 2.75, 0], + "scale": [0.35, 0.35, 0.35] + }, + "gui": { + "rotation": [25, -135, 0], + "translation": [0.5, -1, 0], + "scale": [0.5, 0.5, 0.5] + }, + "fixed": { + "rotation": [-90, 0, 0], + "translation": [0, 0, -16], + "scale": [2, 2, 2] + } + }, + "groups": [ + { + "name": "group", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4, 5] + } + ] +} \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png new file mode 100644 index 0000000000000000000000000000000000000000..25e76651cb36bacfd271de2676ea8a4967f2b1a0 GIT binary patch literal 1663 zcmV-_27vjAP)Px*IY~r8RCt`_TRm^vMi_ofQkD==GC>Il-~!ZOBxvKwaaWI7IyG~CLYAUGr09~L z&^cR|4jr<#@f5d6;Xxh(6tDFKiBZ2$n;W(zV1SO@@^PUiUe$Bze6lRi@z&29r9FXKRTYh-G|H1=+!QGx){`Mxayc6aIPA7Bto(2HuSo}NLE%P}; zqX)9SJJQiMTe*MsaVK{FX17tfP4)>IqCMX>Tj*GKIJ-!!O2LkBcF{uL(Q9FOf+l_- zFCf%O&@yNeV``0+3J6%3+X{7plm|>F^WCrHfv{2mlmQ5JQ-+2p^#v@%?acz0?}}YM zU?DEwh44MCR$C%wg50ti?A`*?$sMMXc`7SRCv(_6Eq-=K4GbP;&0B>bTC?wPqEkeL z+hz;RZkSR`9XuiS0OIy`Z-H9`xq(R$RF4{fU`!|TxT>onKBBvew=m zXW>!B14M5?MoD!-P62hO@KlgffVc&+UQm~_01>eQzBJ1dQXQdez!M3PJEF8$FE20x z7N(k>BJAVjxzFhQuwHD?c>nthzzn8!C^>~Ipe{9mW?F|S;orXbP=Z9wpDyj*0)0o18?Nh~NxGkWJw6p7D14@G5{i(&G|+Rgk`X7Xnv7JYNL{X;yVV zGjFyvdhB~z?zi|o1-Y$ocG1dtj4X_1w*k{yhvglj*Xyb-e{XG*u5YYx+(>U zFW~oz)Ps%;+>wsHle|x6O*BUpu?J*dBD!31J&tb;`?#~lDUupw4`Gw~WxW<&E(Xxc z#K_#S?xMx){)QMAs1YfBo?D9uK1R#TRYR^NMn6QCAQ911RWZ8Cemw-8<-5cX3qbf-X|Rm8EF-4D<1XlX?&4+`RuTPg%za=xK3I{JnObb?FlkP|d^Je(%mwz7g z{~H((g#VApxzZ!^5W$$CePkjSGX!I<5a!9hI2e!DT=I5dh?ak3sue`RnBkk}N2%{t zWru`L^LdX4_L44W2Q0+LOYTXkS%$|YePu%@3j>kmRfVTHR7$VAg-*}!FudZwF?iO- z;Kav=KlzrHEw}j^7C1duPt7?ZMB6}CAvEfK+(q3_sIzw_7oaVjRM zK)NGTrkG9c3~>IgV|bk?pxJG}Iax>x4*(b6+tKm;7CfNH@>Ri+c{4C?y@m=uV`kq*zO8;TwYOo4b@!;_wan;p4fJgr9 zwju5>5~=&Tjd=01PT20orrov6@OfK=beGgxUFu(si1${|{P2Issue found in file Issue found in file - The block '' is missing the required 'facing' property for 'stairs_block' behavior." warning.config.block.behavior.stairs.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'stairs_block' behavior." warning.config.block.behavior.stairs.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'stairs_block' behavior." -warning.config.block.behavior.bottom_half_stairs.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'bottom_half_stairs_block' behavior." -warning.config.block.behavior.bottom_half_stairs.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'bottom_half_stairs_block' behavior." +warning.config.block.behavior.sofa.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'sofa_block' behavior." +warning.config.block.behavior.sofa.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'sofa_block' behavior." warning.config.block.behavior.pressure_plate.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'pressure_plate_block' behavior." warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 1df5b5430..d78d52562 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -305,8 +305,8 @@ warning.config.block.behavior.slab.missing_type: "在文件 发 warning.config.block.behavior.stairs.missing_facing: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.stairs.missing_half: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.stairs.missing_shape: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'shape' 属性" -warning.config.block.behavior.bottom_half_stairs.missing_facing: "在文件 发现问题 - 方块 '' 的 'bottom_half_stairs_block' 行为缺少必需的 'facing' 属性" -warning.config.block.behavior.bottom_half_stairs.missing_shape: "在文件 发现问题 - 方块 '' 的 'bottom_half_stairs_block' 行为缺少必需的 'shape' 属性" +warning.config.block.behavior.sofa.missing_facing: "在文件 发现问题 - 方块 '' 的 'sofa_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.sofa.missing_shape: "在文件 发现问题 - 方块 '' 的 'sofa_block' 行为缺少必需的 'shape' 属性" warning.config.block.behavior.pressure_plate.missing_powered: "在文件 发现问题 - 方块 '' 的 'pressure_plate_block' 行为缺少必需的 'powered' 属性" warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java index a54c56afa..e5a143264 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java @@ -21,6 +21,7 @@ public final class Properties { public static final Key HINGE = Key.of("craftengine:hinge"); public static final Key STAIRS_SHAPE = Key.of("craftengine:stairs_shape"); public static final Key SLAB_TYPE = Key.of("craftengine:slab_type"); + public static final Key SOFA_SHAPE = Key.of("craftengine:sofa_shape"); static { register(BOOLEAN, BooleanProperty.FACTORY); @@ -36,6 +37,8 @@ public final class Properties { register(HINGE, new EnumProperty.Factory<>(DoorHinge.class)); register(STAIRS_SHAPE, new EnumProperty.Factory<>(StairsShape.class)); register(SLAB_TYPE, new EnumProperty.Factory<>(SlabType.class)); + register(SOFA_SHAPE, new EnumProperty.Factory<>(SofaShape.class)); + } public static void register(Key key, PropertyFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java new file mode 100644 index 000000000..a9d717dcb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.block.state.properties; + +public enum SofaShape { + STRAIGHT, + INNER_LEFT, + INNER_RIGHT, +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c4dab03ce..81fcf2e9a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -463,6 +463,10 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_1.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_2.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_3.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png"); // ores plugin.saveResource("resources/default/configuration/ores.yml"); From 3fb7efee6edbcba24cca9fddfe3a6bccda801d89 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 8 Sep 2025 20:09:09 +0800 Subject: [PATCH 053/226] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B2=99=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/additional-real-blocks.yml | 3 +- .../default/configuration/blocks.yml | 29 +++------------ .../models/{block => item}/custom/sofa.json | 35 ++++++++++--------- .../core/pack/AbstractPackManager.java | 2 +- 4 files changed, 26 insertions(+), 43 deletions(-) rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/{block => item}/custom/sofa.json (78%) diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index afdf2f7ea..c425a70f3 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -81,4 +81,5 @@ minecraft:cherry_fence_gate: 16 minecraft:bamboo_fence_gate: 16 minecraft:crimson_fence_gate: 16 minecraft:warped_fence_gate: 16 -minecraft:barrier: 24 \ No newline at end of file +minecraft:barrier: 24 +minecraft:white_bed: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index d1fa2dbd4..1aca0f06c 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -563,8 +563,6 @@ items#misc: model: type: minecraft:model path: minecraft:item/custom/sofa - generation: - parent: minecraft:block/custom/sofa behavior: type: block_item block: @@ -587,28 +585,11 @@ items#misc: hit: minecraft:block.wood.hit place: minecraft:block.wood.place step: minecraft:block.wood.step - states: - properties: - waterlogged: - type: boolean - default: false - appearances: - waterlogged=false: - state: sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=false] - model: - path: minecraft:block/custom/sofa - waterlogged=true: - state: sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=true] - model: - path: minecraft:block/custom/sofa - variants: - waterlogged=false: - appearance: waterlogged=false - id: 0 - waterlogged=true: - appearance: waterlogged=true - id: 1 - + state: + id: 0 + state: white_bed[facing=west,occupied=false,part=foot] + entity-renderer: + item: default:sofa default:connectable_sofa_straight: material: nether_brick custom-model-data: 3009 diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json similarity index 78% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json index 07bbf9eaa..165259dc3 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json @@ -1,4 +1,5 @@ { + "format_version": "1.21.6", "credit": "Made with Blockbench", "textures": { "0": "item/custom/sofa", @@ -6,9 +7,9 @@ }, "elements": [ { - "from": [1, 0, 1], - "to": [3, 3, 3], - "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]}, + "from": [-0.07, -0.035, -0.07], + "to": [2.95, 2.495, 2.95], + "rotation": {"angle": 0, "axis": "y", "origin": [-0.07, -0.035, -0.07]}, "faces": { "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, @@ -19,9 +20,9 @@ } }, { - "from": [13, 0, 1], - "to": [15, 3, 3], - "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 1]}, + "from": [13.05, -0.035, -0.07], + "to": [16.07, 2.495, 2.95], + "rotation": {"angle": 0, "axis": "y", "origin": [16.07, -0.035, -0.07]}, "faces": { "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, @@ -32,9 +33,9 @@ } }, { - "from": [13, 0, 13], - "to": [15, 3, 15], - "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 15]}, + "from": [13.05, -0.035, 13.05], + "to": [16.07, 2.495, 16.07], + "rotation": {"angle": 0, "axis": "y", "origin": [16.07, -0.035, 16.07]}, "faces": { "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, @@ -45,9 +46,9 @@ } }, { - "from": [1, 0, 13], - "to": [3, 3, 15], - "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, + "from": [-0.07, -0.035, 13.05], + "to": [2.95, 2.495, 16.07], + "rotation": {"angle": 0, "axis": "y", "origin": [-0.07, -0.035, 16.07]}, "faces": { "north": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, "east": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, @@ -58,9 +59,9 @@ } }, { - "from": [0.01, 2.51, 0.01], - "to": [15.99, 8.49, 15.99], - "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, + "from": [-0.0699, 2.5001, -0.0699], + "to": [16.0699, 9.0399, 16.0699], + "rotation": {"angle": 0, "axis": "y", "origin": [-0.08, 1.985, -0.08]}, "faces": { "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, "east": {"uv": [8, 11.5, 4, 13], "texture": "#0"}, @@ -83,12 +84,12 @@ "scale": [0.25, 0.25, 0.25] }, "firstperson_righthand": { - "rotation": [0, 180, 0], + "rotation": [0, -180, 0], "translation": [0.5, 0.5, 0], "scale": [0.5, 0.5, 0.5] }, "firstperson_lefthand": { - "rotation": [0, 180, 0], + "rotation": [0, -180, 0], "translation": [0.5, 0.5, 0], "scale": [0.5, 0.5, 0.5] }, diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 81fcf2e9a..bc7e28b0d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -465,7 +465,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_3.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/sofa.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png"); // ores From c9d7723881ebcf15925899e0b9067f175d3e235a Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 8 Sep 2025 21:29:25 +0800 Subject: [PATCH 054/226] =?UTF-8?q?feat(bukkit):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=BC=B9=E8=B7=B3=E6=96=B9=E5=9D=97=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 68 +++++++++++++++++++ .../block/behavior/BukkitBlockBehaviors.java | 2 + .../UnsafeCompositeBlockBehavior.java | 14 ++++ .../plugin/injector/BlockGenerator.java | 37 +++++++++- .../reflection/minecraft/CoreReflections.java | 8 +++ .../default/configuration/blocks.yml | 3 + .../craftengine/core/block/BlockBehavior.java | 9 +++ gradle.properties | 2 +- 8 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java new file mode 100644 index 000000000..9e167957d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -0,0 +1,68 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class BouncingBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final double bounceHeight; + + public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight) { + super(customBlock); + this.bounceHeight = bounceHeight; + } + + @Override + public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object entity = args[3]; + Object finalFallDistance; + if (VersionHelper.isOrAbove1_21_5()) { + double fallDistance = (double) args[4]; + finalFallDistance = fallDistance * 0.5; + } else { + finalFallDistance = (float) args[4] * 0.5F; + } + FastNMS.INSTANCE.method$Entity$causeFallDamage( + entity, finalFallDistance, 1.0F, + FastNMS.INSTANCE.method$DamageSources$fall(FastNMS.INSTANCE.method$Entity$damageSources(entity)) + ); + } + + @Override + public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object entity = args[1]; + if (FastNMS.INSTANCE.method$Entity$getSharedFlag(entity, 1)) { + bounceUp(entity); + } + } + + private void bounceUp(Object entity) { + Object deltaMovement = FastNMS.INSTANCE.method$Entity$getDeltaMovement(entity); + if (FastNMS.INSTANCE.field$Vec3$y(deltaMovement) < 0.0) { + double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; + FastNMS.INSTANCE.method$Entity$setDeltaMovement( + entity, + FastNMS.INSTANCE.field$Vec3$x(deltaMovement), + -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d, + FastNMS.INSTANCE.field$Vec3$z(deltaMovement) + ); + } + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + double bounceHeight = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bounce-height", 0.66), "bounce-height"); + return new BouncingBlockBehavior(block, bounceHeight); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 71f3de6ed..34435ad14 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -31,6 +31,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SIMPLE_STORAGE_BLOCK = Key.from("craftengine:simple_storage_block"); public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); + public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -60,5 +61,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SIMPLE_STORAGE_BLOCK, SimpleStorageBlockBehavior.FACTORY); register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); + register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 81f40cbd8..7d3449e77 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -336,4 +336,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { behavior.spawnAfterBreak(thisBlock, args, superMethod); } } + + @Override + public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.fallOn(thisBlock, args, superMethod); + } + } + + @Override + public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); + } + } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index b4d14745b..8ad88d92e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -159,7 +159,14 @@ public final class BlockGenerator { .intercept(MethodDelegation.to(PlayerWillDestroyInterceptor.INSTANCE)) // spawnAfterBreak .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$spawnAfterBreak)) - .intercept(MethodDelegation.to(SpawnAfterBreakInterceptor.INSTANCE)); + .intercept(MethodDelegation.to(SpawnAfterBreakInterceptor.INSTANCE)) + // fallOn + .method(ElementMatchers.is(CoreReflections.method$Block$fallOn)) + .intercept(MethodDelegation.to(FallOnInterceptor.INSTANCE)) + // updateEntityMovementAfterFallOn + .method(ElementMatchers.is(CoreReflections.method$Block$updateEntityMovementAfterFallOn)) + .intercept(MethodDelegation.to(UpdateEntityMovementAfterFallOnInterceptor.INSTANCE)) + ; // 1.21.5+ if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) { builder = builder.method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval)) @@ -699,4 +706,32 @@ public final class BlockGenerator { } } } + + public static class FallOnInterceptor { + public static final FallOnInterceptor INSTANCE = new FallOnInterceptor(); + + @RuntimeType + public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + holder.value().fallOn(thisObj, args, superMethod); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run fallOn", e); + } + } + } + + public static class UpdateEntityMovementAfterFallOnInterceptor { + public static final UpdateEntityMovementAfterFallOnInterceptor INSTANCE = new UpdateEntityMovementAfterFallOnInterceptor(); + + @RuntimeType + public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + holder.value().updateEntityMovementAfterFallOn(thisObj, args, superMethod); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run updateEntityMovementAfterFallOn", e); + } + } + } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 8cce0ed83..124263ed2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4209,4 +4209,12 @@ public final class CoreReflections { public static final Method method$BlockAndTintGetter$getLightEngine = requireNonNull( ReflectionUtils.getDeclaredMethod(clazz$BlockAndTintGetter, clazz$LevelLightEngine) ); + + public static final Method method$Block$fallOn = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$Block, void.class, clazz$Level, clazz$BlockState, clazz$BlockPos, clazz$Entity, VersionHelper.isOrAbove1_21_5() ? double.class : float.class) + ); + + public static final Method method$Block$updateEntityMovementAfterFallOn = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$Block, void.class, clazz$BlockGetter, clazz$Entity) + ); } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 1aca0f06c..4a197f8df 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -585,6 +585,9 @@ items#misc: hit: minecraft:block.wood.hit place: minecraft:block.wood.place step: minecraft:block.wood.step + behavior: + type: bouncing_block + bounce-height: 0.66 state: id: 0 state: white_bed[facing=west,occupied=false,part=foot] diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 5536e4322..32b164635 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -178,6 +178,15 @@ public abstract class BlockBehavior { public void spawnAfterBreak(Object thisBlock, Object[] args, Callable superMethod) throws Exception { } + // 1.20.1~1.21.4 Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance + // 1.21.5+ Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance + public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + } + + // BlockGetter level, Entity entity + public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + } + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { return state; } diff --git a/gradle.properties b/gradle.properties index eb5d80f1d..f39e0afa6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.80 +nms_helper_version=1.0.81 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From bafee0ad3c0cd1807821d5fc3184d898ff6b61be Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 9 Sep 2025 22:32:29 +0800 Subject: [PATCH 055/226] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/build.gradle.kts | 2 +- bukkit/paper-loader/build.gradle.kts | 2 +- .../plugin/reflection/minecraft/CoreReflections.java | 2 +- .../craftengine/bukkit/world/BukkitCEWorld.java | 12 ++++++------ .../craftengine/core/pack/AbstractPackManager.java | 4 ++-- .../momirealms/craftengine/core/world/CEWorld.java | 10 ++++++---- gradle.properties | 2 +- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index c46bb9ff0..11ecaf4e1 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -50,7 +50,7 @@ bukkit { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") - contributors = listOf("jhqwqmc", "iqtesterr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8") + contributors = listOf("https://github.com/Xiao-MoMi/craft-engine/graphs/contributors") softDepend = listOf("PlaceholderAPI", "WorldEdit", "FastAsyncWorldEdit", "Skript") foliaSupported = true } diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index 94abf7189..b5542dae5 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -53,7 +53,7 @@ paper { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") - contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8") + contributors = listOf("https://github.com/Xiao-MoMi/craft-engine/graphs/contributors") foliaSupported = true serverDependencies { register("PlaceholderAPI") { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 8cce0ed83..955324052 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -917,7 +917,7 @@ public final class CoreReflections { ); public static final Method method$IdMapper$add = requireNonNull( - ReflectionUtils.getMethod(clazz$IdMapper, void.class, Object.class) + ReflectionUtils.getMethod(clazz$IdMapper, void.class, new String[] {"add", "b"}, Object.class) ); public static final Object instance$Block$BLOCK_STATE_REGISTRY; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 3ad6e4caa..a8022284b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -7,14 +7,13 @@ import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.CEWorld; -import net.momirealms.craftengine.core.world.ChunkPos; -import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; public class BukkitCEWorld extends CEWorld { @@ -39,8 +38,9 @@ public class BukkitCEWorld extends CEWorld { ); super.lightSections.clear(); super.isUpdatingLights = false; - super.lightSections.addAll(super.pendingLightSections); - super.pendingLightSections.clear(); + List pendingLightSections = super.pendingLightSections; + super.pendingLightSections = new ArrayList<>(Math.max(pendingLightSections.size() / 2, 8)); + super.lightSections.addAll(pendingLightSections); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 60433897e..ac37372ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -125,7 +125,7 @@ public abstract class AbstractPackManager implements PackManager { loadInternalList("models", "block/", VANILLA_MODELS::add); loadInternalList("models", "item/", VANILLA_MODELS::add); - + loadInternalList("models", "item/legacy/", key -> VANILLA_MODELS.add(Key.of(key.namespace(), "item/" + key.value().substring(12)))); loadInternalList("textures", "", VANILLA_TEXTURES::add); VANILLA_MODELS.add(Key.of("minecraft", "builtin/entity")); VANILLA_MODELS.add(Key.of("minecraft", "item/player_head")); @@ -171,7 +171,7 @@ public abstract class AbstractPackManager implements PackManager { JsonArray fileList = listJson.getAsJsonArray("files"); for (JsonElement element : fileList) { if (element instanceof JsonPrimitive primitive) { - callback.accept(Key.of(prefix + FileUtils.pathWithoutExtension(primitive.getAsString()))); + callback.accept(Key.of("minecraft", prefix + FileUtils.pathWithoutExtension(primitive.getAsString()))); } } JsonArray directoryList = listJson.getAsJsonArray("directories"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index fafbb6da3..cc9ccc239 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.world; +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -17,6 +18,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public abstract class CEWorld { public static final String REGION_DIRECTORY = "craftengine"; @@ -24,12 +26,12 @@ public abstract class CEWorld { protected final ConcurrentLong2ReferenceChainedHashTable loadedChunkMap; protected final WorldDataStorage worldDataStorage; protected final WorldHeight worldHeightAccessor; - protected final List pendingLightSections = new ArrayList<>(128); - protected final Set lightSections = new HashSet<>(128); + protected List pendingLightSections = new ArrayList<>(); + protected final Set lightSections = ConcurrentHashMap.newKeySet(128); protected final List tickingBlockEntities = new ArrayList<>(); protected final List pendingTickingBlockEntities = new ArrayList<>(); - protected boolean isTickingBlockEntities = false; - protected boolean isUpdatingLights = false; + protected volatile boolean isTickingBlockEntities = false; + protected volatile boolean isUpdatingLights = false; protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; diff --git a/gradle.properties b/gradle.properties index 90bc010eb..ee863028c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.16 +project_version=0.0.62.17 config_version=45 lang_version=25 project_group=net.momirealms From f105a18a52521c61e93ed4d6e72d00798130dbfb Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 09:04:58 +0800 Subject: [PATCH 056/226] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E6=95=B0=E6=8D=AE=E6=9E=84=E5=BB=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=B9=B6=E6=94=B9=E8=BF=9B=E6=97=A5=E5=BF=97=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/item/modifier/ExternalModifier.java | 36 +++++++++++-------- .../craftengine/core/pack/PackCacheData.java | 8 ++--- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java index 8a33a867b..ce9fe1833 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java @@ -8,12 +8,13 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.NotNull; import java.util.*; public class ExternalModifier implements ItemDataModifier { public static final Factory FACTORY = new Factory<>(); - private static final ThreadLocal> BUILD_STACK = ThreadLocal.withInitial(ArrayDeque::new); + private static final ThreadLocal> BUILD_STACK = ThreadLocal.withInitial(LinkedHashSet::new); private final String id; private final ExternalItemSource provider; @@ -38,21 +39,20 @@ public class ExternalModifier implements ItemDataModifier { @SuppressWarnings("unchecked") @Override public Item apply(Item item, ItemBuildContext context) { - String stackElement = provider.plugin() + "[id=" + id + "]"; - Deque buildStack = BUILD_STACK.get(); + Dependency dependency = new Dependency(provider.plugin(), id); + Set buildStack = BUILD_STACK.get(); - if (buildStack.contains(stackElement)) { + if (buildStack.contains(dependency)) { StringJoiner dependencyChain = new StringJoiner(" -> "); - buildStack.forEach(dependencyChain::add); - dependencyChain.add(stackElement); - CraftEngine.instance().logger().warn("Item '" + item.customId().orElseGet(item::id) + - "' encountered circular dependency while building external item '" + this.id + - "' (from plugin '" + provider.plugin() + "'). Dependency chain: " + dependencyChain + buildStack.forEach(element -> dependencyChain.add(element.toString())); + dependencyChain.add(dependency.toString()); + CraftEngine.instance().logger().warn( + "Failed to build '" + this.id + "' because of a dependency loop: " + dependencyChain ); return item; } - buildStack.push(stackElement); + buildStack.add(dependency); try { I another = this.provider.build(this.id, context); if (another == null) { @@ -62,11 +62,12 @@ public class ExternalModifier implements ItemDataModifier { Item anotherWrapped = (Item) CraftEngine.instance().itemManager().wrap(another); item.merge(anotherWrapped); return item; + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to build '" + this.id + "'", e); + return item; } finally { - buildStack.pop(); - if (buildStack.isEmpty()) { - BUILD_STACK.remove(); - } + buildStack.remove(dependency); + BUILD_STACK.remove(); } } @@ -82,4 +83,11 @@ public class ExternalModifier implements ItemDataModifier { return new ExternalModifier<>(id, ResourceConfigUtils.requireNonNullOrThrow(provider, () -> new LocalizedResourceConfigException("warning.config.item.data.external.invalid_source", plugin))); } } + + private record Dependency(String source, String id) { + @Override + public @NotNull String toString() { + return source + "[id=" + id + "]"; + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java index b3c197034..424eccc32 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java @@ -21,8 +21,8 @@ public class PackCacheData { .map(it -> plugin.dataFolderPath().getParent().resolve(it)) .filter(Files::exists) .collect(Collectors.toSet()), - add -> Debugger.RESOURCE_PACK.debug(() -> "Adding external folder: " + add), - remove -> Debugger.RESOURCE_PACK.debug(() -> "Removing external folder: " + remove), + add -> plugin.logger().info("Adding external folder: " + add), + remove -> plugin.logger().info("Removing external folder: " + remove), true ); this.externalZips = new SetMonitor<>( @@ -32,8 +32,8 @@ public class PackCacheData { .filter(Files::isRegularFile) .filter(file -> file.getFileName().toString().endsWith(".zip")) .collect(Collectors.toSet()), - add -> Debugger.RESOURCE_PACK.debug(() -> "Adding external zip: " + add), - remove -> Debugger.RESOURCE_PACK.debug(() -> "Removing external zip: " + remove), + add -> plugin.logger().info("Adding external zip: " + add), + remove -> plugin.logger().info("Removing external zip: " + remove), true ); } From dc1434c725b1698bfd2d9078597dcc9ec72f3c44 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 09:11:10 +0800 Subject: [PATCH 057/226] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E6=95=B0=E6=8D=AE=E6=9E=84=E5=BB=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=B9=B6=E6=94=B9=E8=BF=9B=E6=97=A5=E5=BF=97=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/item/modifier/ExternalModifier.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java index ce9fe1833..4844f542e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java @@ -44,10 +44,10 @@ public class ExternalModifier implements ItemDataModifier { if (buildStack.contains(dependency)) { StringJoiner dependencyChain = new StringJoiner(" -> "); - buildStack.forEach(element -> dependencyChain.add(element.toString())); - dependencyChain.add(dependency.toString()); + buildStack.forEach(element -> dependencyChain.add(element.asString())); + dependencyChain.add(dependency.asString()); CraftEngine.instance().logger().warn( - "Failed to build '" + this.id + "' because of a dependency loop: " + dependencyChain + "Failed to build '" + this.id + "' from plugin '" + provider.plugin() + "' due to dependency loop: " + dependencyChain ); return item; } @@ -63,7 +63,7 @@ public class ExternalModifier implements ItemDataModifier { item.merge(anotherWrapped); return item; } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to build '" + this.id + "'", e); + CraftEngine.instance().logger().warn("Failed to build item '" + this.id + "' from plugin '" + provider.plugin() + "'", e); return item; } finally { buildStack.remove(dependency); @@ -85,8 +85,8 @@ public class ExternalModifier implements ItemDataModifier { } private record Dependency(String source, String id) { - @Override - public @NotNull String toString() { + + public @NotNull String asString() { return source + "[id=" + id + "]"; } } From 0bf346167cb6ada73ddfaa8500466a67394b0a51 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 09:20:10 +0800 Subject: [PATCH 058/226] =?UTF-8?q?refactor(core):=20=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/pack/PackCacheData.java | 34 ++++++------------- .../craftengine/core/util/ListMonitor.java | 6 ---- .../craftengine/core/util/SetMonitor.java | 5 --- 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java index 424eccc32..702bf2163 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java @@ -1,41 +1,29 @@ package net.momirealms.craftengine.core.pack; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.logger.Debugger; -import net.momirealms.craftengine.core.util.SetMonitor; import org.jetbrains.annotations.NotNull; import java.nio.file.Files; import java.nio.file.Path; import java.util.Set; -import java.util.stream.Collectors; public class PackCacheData { private final Set externalZips; private final Set externalFolders; PackCacheData(@NotNull CraftEngine plugin) { - this.externalFolders = new SetMonitor<>( - Config.foldersToMerge().stream() - .map(it -> plugin.dataFolderPath().getParent().resolve(it)) - .filter(Files::exists) - .collect(Collectors.toSet()), - add -> plugin.logger().info("Adding external folder: " + add), - remove -> plugin.logger().info("Removing external folder: " + remove), - true - ); - this.externalZips = new SetMonitor<>( - Config.zipsToMerge().stream() - .map(it -> plugin.dataFolderPath().getParent().resolve(it)) - .filter(Files::exists) - .filter(Files::isRegularFile) - .filter(file -> file.getFileName().toString().endsWith(".zip")) - .collect(Collectors.toSet()), - add -> plugin.logger().info("Adding external zip: " + add), - remove -> plugin.logger().info("Removing external zip: " + remove), - true - ); + this.externalFolders = Config.foldersToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .collect(ObjectOpenHashSet.toSet()); + this.externalZips = Config.zipsToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".zip")) + .collect(ObjectOpenHashSet.toSet()); } @NotNull diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java b/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java index aeb2c9350..3e37d53b5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ListMonitor.java @@ -13,16 +13,10 @@ public class ListMonitor implements List { private final List list; private final Consumer addConsumer; private final Consumer removeConsumer; - public ListMonitor(List list, Consumer addConsumer, Consumer removeConsumer) { - this(list, addConsumer, removeConsumer, false); - } - - public ListMonitor(List list, Consumer addConsumer, Consumer removeConsumer, boolean skipInitialNotification) { this.list = list; this.addConsumer = addConsumer; this.removeConsumer = removeConsumer; - if (skipInitialNotification) return; for (T key : list) { this.addConsumer.accept(key); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java b/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java index 97b5640b2..39a3fa4ac 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SetMonitor.java @@ -13,14 +13,9 @@ public class SetMonitor implements Set { private final Consumer removeConsumer; public SetMonitor(Set set, Consumer addConsumer, Consumer removeConsumer) { - this(set, addConsumer, removeConsumer, false); - } - - public SetMonitor(Set set, Consumer addConsumer, Consumer removeConsumer, boolean skipInitialNotification) { this.set = set; this.addConsumer = addConsumer; this.removeConsumer = removeConsumer; - if (skipInitialNotification) return; for (E element : set) { this.addConsumer.accept(element); } From 837ac974823fd894225e64bc92bca9b662deb348 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 09:55:03 +0800 Subject: [PATCH 059/226] =?UTF-8?q?refactor(block):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BouncingBlockBehavior.java | 2 ++ .../block/behavior/ToggleableLampBlockBehavior.java | 8 ++++---- .../assets/minecraft/models/item/custom/cap.json | 2 -- .../models/item/custom/flower_basket_ceiling.json | 1 - .../models/item/custom/flower_basket_ground.json | 1 - .../minecraft/models/item/custom/flower_basket_wall.json | 2 -- .../assets/minecraft/models/item/custom/sofa.json | 2 -- .../assets/minecraft/models/item/custom/sofa_inner.json | 1 - .../minecraft/models/item/custom/sofa_straight.json | 1 - .../models/item/custom/topaz_trident_in_hand.json | 1 - .../models/item/custom/topaz_trident_throwing.json | 1 - 11 files changed, 6 insertions(+), 16 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 9e167957d..551c1c0b4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -7,6 +7,8 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import java.util.Map; import java.util.concurrent.Callable; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java index 92894f798..577c167c0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java @@ -49,7 +49,7 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { @Override public void onPlace(Object thisBlock, Object[] args, Callable superMethod) { - if (this.canOpenWithHand) return; + if (this.poweredProperty == null) return; Object state = args[0]; Object level = args[1]; Object pos = args[2]; @@ -63,7 +63,7 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { @Override public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) { - if (this.canOpenWithHand) return; + if (this.poweredProperty == null) return; Object blockState = args[0]; Object world = args[1]; if (!CoreReflections.clazz$ServerLevel.isInstance(world)) return; @@ -91,9 +91,9 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { public static class Factory implements BlockBehaviorFactory { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.toggleable_lamp.missing_lit"); - Property powered = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.toggleable_lamp.missing_powered"); boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); + Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.toggleable_lamp.missing_lit"); + Property powered = (Property) (canOpenWithHand ? block.getProperty("powered") : ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.toggleable_lamp.missing_powered")); return new ToggleableLampBlockBehavior(block, lit, powered, canOpenWithHand); } } diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/cap.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/cap.json index 40cfcc334..2ab8bff7a 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/cap.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/cap.json @@ -1,6 +1,4 @@ { - "format_version": "1.21.6", - "credit": "Made with Blockbench", "texture_size": [32, 32], "textures": { "0": "item/custom/cap", diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ceiling.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ceiling.json index 31e1ef903..0066e3414 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ceiling.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ceiling.json @@ -1,5 +1,4 @@ { - "credit": "Made with Blockbench", "textures": { "1": "item/custom/flower_basket", "particle": "item/custom/flower_basket" diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ground.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ground.json index 38d9eba22..7c8c9c386 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ground.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_ground.json @@ -1,5 +1,4 @@ { - "credit": "Made with Blockbench", "textures": { "0": "item/custom/flower_basket" }, diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_wall.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_wall.json index 1fbe404cf..b7801a8cb 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_wall.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_wall.json @@ -1,6 +1,4 @@ { - "format_version": "1.21.6", - "credit": "Made with Blockbench", "textures": { "0": "item/custom/flower_basket" }, diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json index 165259dc3..6793fcf75 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json @@ -1,6 +1,4 @@ { - "format_version": "1.21.6", - "credit": "Made with Blockbench", "textures": { "0": "item/custom/sofa", "particle": "item/custom/sofa" diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json index 595bdfb4f..e9c3a7bf3 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json @@ -1,5 +1,4 @@ { - "credit": "Made with Blockbench", "textures": { "0": "item/custom/sofa", "particle": "item/custom/sofa" diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json index 4c5174513..822eaf59a 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json @@ -1,5 +1,4 @@ { - "credit": "Made with Blockbench", "texture_size": [64, 64], "textures": { "0": "item/custom/sofa", diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_in_hand.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_in_hand.json index a56ed9681..984b06ef5 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_in_hand.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_in_hand.json @@ -1,5 +1,4 @@ { - "credit": "Made with Blockbench", "textures": { "0": "item/custom/topaz_trident_3d", "particle": "item/custom/topaz_trident_3d" diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_throwing.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_throwing.json index 90b814746..6e8a181f3 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_throwing.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_throwing.json @@ -1,5 +1,4 @@ { - "credit": "Made with Blockbench", "textures": { "0": "item/custom/topaz_trident_3d", "particle": "item/custom/topaz_trident_3d" From b936b4853ab02dced3c105f11c7301cae04e0ba3 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 10:19:43 +0800 Subject: [PATCH 060/226] =?UTF-8?q?refactor(block):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 8 +- .../main/resources/additional-real-blocks.yml | 2 +- .../default/configuration/blocks.yml | 226 ++++-------------- gradle.properties | 2 +- 4 files changed, 51 insertions(+), 187 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 551c1c0b4..6a68455aa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -7,8 +7,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; +import org.bukkit.util.Vector; import java.util.Map; import java.util.concurrent.Callable; @@ -23,7 +22,7 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { } @Override - public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { Object entity = args[3]; Object finalFallDistance; if (VersionHelper.isOrAbove1_21_5()) { @@ -42,6 +41,8 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Object entity = args[1]; if (FastNMS.INSTANCE.method$Entity$getSharedFlag(entity, 1)) { + superMethod.call(); + } else { bounceUp(entity); } } @@ -56,6 +57,7 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d, FastNMS.INSTANCE.field$Vec3$z(deltaMovement) ); + FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index c425a70f3..8de88617e 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -81,5 +81,5 @@ minecraft:cherry_fence_gate: 16 minecraft:bamboo_fence_gate: 16 minecraft:crimson_fence_gate: 16 minecraft:warped_fence_gate: 16 -minecraft:barrier: 24 +minecraft:barrier: 128 minecraft:white_bed: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 4a197f8df..71db60034 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -585,6 +585,8 @@ items#misc: hit: minecraft:block.wood.hit place: minecraft:block.wood.place step: minecraft:block.wood.step + tags: + - minecraft:mineable/axe behavior: type: bouncing_block bounce-height: 0.66 @@ -639,253 +641,113 @@ items#misc: hit: minecraft:block.wood.hit place: minecraft:block.wood.place step: minecraft:block.wood.step - behavior: - type: sofa_block + tags: + - minecraft:mineable/axe + behaviors: + - type: sofa_block + - type: bouncing_block + bounce-height: 0.66 states: properties: facing: type: horizontal_direction shape: type: sofa_shape - waterlogged: - type: boolean - default: false appearances: - facing=east,shape=straight,waterlogged=false: + facing=east,shape=straight: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_straight yaw: 90 - facing=north,shape=straight,waterlogged=false: + facing=north,shape=straight: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_straight - facing=south,shape=straight,waterlogged=false: + facing=south,shape=straight: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_straight yaw: 180 - facing=west,shape=straight,waterlogged=false: + facing=west,shape=straight: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_straight yaw: 270 - facing=east,shape=straight,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_straight - yaw: 90 - facing=north,shape=straight,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_straight - facing=south,shape=straight,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_straight - yaw: 180 - facing=west,shape=straight,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_straight - yaw: 270 - facing=east,shape=inner_left,waterlogged=false: + facing=east,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner - facing=north,shape=inner_left,waterlogged=false: + facing=north,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner yaw: 270 - facing=south,shape=inner_left,waterlogged=false: + facing=south,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner yaw: 90 - facing=west,shape=inner_left,waterlogged=false: + facing=west,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner yaw: 180 - facing=east,shape=inner_left,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - facing=north,shape=inner_left,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - yaw: 270 - facing=south,shape=inner_left,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - yaw: 90 - facing=west,shape=inner_left,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - yaw: 180 - facing=east,shape=inner_right,waterlogged=false: + facing=east,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner yaw: 90 - facing=north,shape=inner_right,waterlogged=false: + facing=north,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner - facing=south,shape=inner_right,waterlogged=false: + facing=south,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner yaw: 180 - facing=west,shape=inner_right,waterlogged=false: + facing=west,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: item: default:connectable_sofa_inner yaw: 270 - facing=east,shape=inner_right,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - yaw: 90 - facing=north,shape=inner_right,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - facing=south,shape=inner_right,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - yaw: 180 - facing=west,shape=inner_right,waterlogged=true: - state: barrier[waterlogged=true] - entity-renderer: - item: default:connectable_sofa_inner - yaw: 270 variants: - facing=east,shape=inner_left,waterlogged=false: - appearance: facing=east,shape=inner_left,waterlogged=false + facing=east,shape=inner_left: + appearance: facing=east,shape=inner_left id: 0 - facing=east,shape=inner_right,waterlogged=false: - appearance: facing=east,shape=inner_right,waterlogged=false + facing=east,shape=inner_right: + appearance: facing=east,shape=inner_right id: 1 - facing=east,shape=straight,waterlogged=false: - appearance: facing=east,shape=straight,waterlogged=false + facing=east,shape=straight: + appearance: facing=east,shape=straight id: 2 - facing=north,shape=inner_left,waterlogged=false: - appearance: facing=north,shape=inner_left,waterlogged=false + facing=north,shape=inner_left: + appearance: facing=north,shape=inner_left id: 3 - facing=north,shape=inner_right,waterlogged=false: - appearance: facing=north,shape=inner_right,waterlogged=false + facing=north,shape=inner_right: + appearance: facing=north,shape=inner_right id: 4 - facing=north,shape=straight,waterlogged=false: - appearance: facing=north,shape=straight,waterlogged=false + facing=north,shape=straight: + appearance: facing=north,shape=straight id: 5 - facing=south,shape=inner_left,waterlogged=false: - appearance: facing=south,shape=inner_left,waterlogged=false + facing=south,shape=inner_left: + appearance: facing=south,shape=inner_left id: 6 - facing=south,shape=inner_right,waterlogged=false: - appearance: facing=south,shape=inner_right,waterlogged=false + facing=south,shape=inner_right: + appearance: facing=south,shape=inner_right id: 7 - facing=south,shape=straight,waterlogged=false: - appearance: facing=south,shape=straight,waterlogged=false + facing=south,shape=straight: + appearance: facing=south,shape=straight id: 8 - facing=west,shape=inner_left,waterlogged=false: - appearance: facing=west,shape=inner_left,waterlogged=false + facing=west,shape=inner_left: + appearance: facing=west,shape=inner_left id: 9 - facing=west,shape=inner_right,waterlogged=false: - appearance: facing=west,shape=inner_right,waterlogged=false + facing=west,shape=inner_right: + appearance: facing=west,shape=inner_right id: 10 - facing=west,shape=straight,waterlogged=false: - appearance: facing=west,shape=straight,waterlogged=false + facing=west,shape=straight: + appearance: facing=west,shape=straight id: 11 - facing=east,shape=inner_left,waterlogged=true: - appearance: facing=east,shape=inner_left,waterlogged=true - id: 12 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=east,shape=inner_right,waterlogged=true: - appearance: facing=east,shape=inner_right,waterlogged=true - id: 13 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=east,shape=straight,waterlogged=true: - appearance: facing=east,shape=straight,waterlogged=true - id: 14 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,shape=inner_left,waterlogged=true: - appearance: facing=north,shape=inner_left,waterlogged=true - id: 15 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,shape=inner_right,waterlogged=true: - appearance: facing=north,shape=inner_right,waterlogged=true - id: 16 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,shape=straight,waterlogged=true: - appearance: facing=north,shape=straight,waterlogged=true - id: 17 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,shape=inner_left,waterlogged=true: - appearance: facing=south,shape=inner_left,waterlogged=true - id: 18 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,shape=inner_right,waterlogged=true: - appearance: facing=south,shape=inner_right,waterlogged=true - id: 19 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,shape=straight,waterlogged=true: - appearance: facing=south,shape=straight,waterlogged=true - id: 20 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,shape=inner_left,waterlogged=true: - appearance: facing=west,shape=inner_left,waterlogged=true - id: 21 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,shape=inner_right,waterlogged=true: - appearance: facing=west,shape=inner_right,waterlogged=true - id: 22 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,shape=straight,waterlogged=true: - appearance: facing=west,shape=straight,waterlogged=true - id: 23 - settings: - resistance: 1200.0 - burnable: false - fluid-state: water recipes#misc: default:chinese_lantern: type: shaped diff --git a/gradle.properties b/gradle.properties index 4b1e6f2a5..63049c23f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.81 +nms_helper_version=1.0.82 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 10a35e379836ba9fdaf98e7b4b3a3019cbc6f8f6 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 10:31:48 +0800 Subject: [PATCH 061/226] =?UTF-8?q?refactor(block):=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E7=91=95=E7=96=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BouncingBlockBehavior.java | 12 ++++++++---- .../resources/default/configuration/blocks.yml | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 6a68455aa..d94e9f95a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -7,7 +7,6 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; -import org.bukkit.util.Vector; import java.util.Map; import java.util.concurrent.Callable; @@ -15,10 +14,12 @@ import java.util.concurrent.Callable; public class BouncingBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final double bounceHeight; + private final boolean syncPlayerSelf; - public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight) { + public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean syncPlayerSelf) { super(customBlock); this.bounceHeight = bounceHeight; + this.syncPlayerSelf = syncPlayerSelf; } @Override @@ -57,7 +58,9 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d, FastNMS.INSTANCE.field$Vec3$z(deltaMovement) ); - FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); + if (this.syncPlayerSelf) { + FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); + } } } @@ -66,7 +69,8 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { double bounceHeight = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bounce-height", 0.66), "bounce-height"); - return new BouncingBlockBehavior(block, bounceHeight); + boolean syncPlayerSelf = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("sync-player-self", true), "sync-player-self"); + return new BouncingBlockBehavior(block, bounceHeight, syncPlayerSelf); } } } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 71db60034..32d9d92d4 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -590,6 +590,7 @@ items#misc: behavior: type: bouncing_block bounce-height: 0.66 + sync-player-self: false state: id: 0 state: white_bed[facing=west,occupied=false,part=foot] From 0274bc062f6e04d9d894cf2aa9b82568c581f301 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 10:52:11 +0800 Subject: [PATCH 062/226] =?UTF-8?q?fix:=20=E8=A1=A5=E5=85=85=E6=BC=8F?= =?UTF-8?q?=E6=8E=89=E7=9A=84=E6=96=B9=E5=9D=97=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/default/configuration/block_name.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common-files/src/main/resources/resources/default/configuration/block_name.yml b/common-files/src/main/resources/resources/default/configuration/block_name.yml index f097abe8a..4c750da5a 100644 --- a/common-files/src/main/resources/resources/default/configuration/block_name.yml +++ b/common-files/src/main/resources/resources/default/configuration/block_name.yml @@ -28,6 +28,8 @@ lang: block_name:default:copper_coil: Copper Coil block_name:default:chessboard_block: Chessboard Block block_name:default:safe_block: Safe Block + block_name:default:sofa: Sofa + block_name:default:connectable_sofa: Sofa zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -53,4 +55,6 @@ lang: block_name:default:solid_gunpowder_block: 凝固火药块 block_name:default:copper_coil: 铜线圈 block_name:default:chessboard_block: 棋盘方块 - block_name:default:safe_block: 保险柜 \ No newline at end of file + block_name:default:safe_block: 保险柜 + block_name:default:sofa: 沙发 + block_name:default:connectable_sofa: 沙发 \ No newline at end of file From dd5749758da20564e86a23d186df5e534ecf562b Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 11:23:33 +0800 Subject: [PATCH 063/226] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=BC=B9=E8=B7=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BouncingBlockBehavior.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index d94e9f95a..08ba200ba 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -52,13 +52,15 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { Object deltaMovement = FastNMS.INSTANCE.method$Entity$getDeltaMovement(entity); if (FastNMS.INSTANCE.field$Vec3$y(deltaMovement) < 0.0) { double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; + double y = -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d; + System.out.println("deltaMovement=" + deltaMovement + ", d=" + d + ", y=" + y); FastNMS.INSTANCE.method$Entity$setDeltaMovement( entity, FastNMS.INSTANCE.field$Vec3$x(deltaMovement), - -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d, + y, FastNMS.INSTANCE.field$Vec3$z(deltaMovement) ); - if (this.syncPlayerSelf) { + if (this.syncPlayerSelf && y > 0.06271) { // 不知道为什么会抖 FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } From fb332584c04b8769b0035fc1c1d4a63f91644918 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 11:23:54 +0800 Subject: [PATCH 064/226] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=BC=B9=E8=B7=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/behavior/BouncingBlockBehavior.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 08ba200ba..e602f15fb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -53,7 +53,6 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { if (FastNMS.INSTANCE.field$Vec3$y(deltaMovement) < 0.0) { double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; double y = -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d; - System.out.println("deltaMovement=" + deltaMovement + ", d=" + d + ", y=" + y); FastNMS.INSTANCE.method$Entity$setDeltaMovement( entity, FastNMS.INSTANCE.field$Vec3$x(deltaMovement), From bcb5c0f554d50c867b2c819f948fd323521b60cc Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 11:32:44 +0800 Subject: [PATCH 065/226] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BouncingBlockBehavior.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index e602f15fb..82e6d5634 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -59,7 +59,7 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { y, FastNMS.INSTANCE.field$Vec3$z(deltaMovement) ); - if (this.syncPlayerSelf && y > 0.06271) { // 不知道为什么会抖 + if (this.syncPlayerSelf && y > 0.0315) { // 不知道为什么会抖 FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } From 647e585c86bb4d68ce347a3a9e56b9bce24c9c8c Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 14:40:27 +0800 Subject: [PATCH 066/226] =?UTF-8?q?fix:=20=E4=B8=8D=E6=98=AF=E5=93=A5?= =?UTF-8?q?=E4=BB=AC=E8=BF=99=E5=8E=9F=E7=89=88=E5=BA=8A=E9=83=BD=E6=9C=89?= =?UTF-8?q?=E8=BF=99=E4=B8=AA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BouncingBlockBehavior.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 82e6d5634..d94e9f95a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -52,14 +52,13 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { Object deltaMovement = FastNMS.INSTANCE.method$Entity$getDeltaMovement(entity); if (FastNMS.INSTANCE.field$Vec3$y(deltaMovement) < 0.0) { double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; - double y = -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d; FastNMS.INSTANCE.method$Entity$setDeltaMovement( entity, FastNMS.INSTANCE.field$Vec3$x(deltaMovement), - y, + -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d, FastNMS.INSTANCE.field$Vec3$z(deltaMovement) ); - if (this.syncPlayerSelf && y > 0.0315) { // 不知道为什么会抖 + if (this.syncPlayerSelf) { FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } From c99f693f91f697198ab9450bc9e0087320053cf7 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 16:09:16 +0800 Subject: [PATCH 067/226] =?UTF-8?q?refactor(block):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BC=B9=E8=B7=B3=E6=96=B9=E5=9D=97=E8=A1=8C=E4=B8=BA=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 24 +++++++------------ .../UnsafeCompositeBlockBehavior.java | 6 ++++- .../bukkit/util/LocationUtils.java | 2 +- .../craftengine/core/block/BlockBehavior.java | 3 +++ 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index d94e9f95a..60974d53c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -2,11 +2,13 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.Vec3d; import java.util.Map; import java.util.concurrent.Callable; @@ -25,13 +27,7 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { @Override public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { Object entity = args[3]; - Object finalFallDistance; - if (VersionHelper.isOrAbove1_21_5()) { - double fallDistance = (double) args[4]; - finalFallDistance = fallDistance * 0.5; - } else { - finalFallDistance = (float) args[4] * 0.5F; - } + Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * 0.5 : (float) args[4] * 0.5F; FastNMS.INSTANCE.method$Entity$causeFallDamage( entity, finalFallDistance, 1.0F, FastNMS.INSTANCE.method$DamageSources$fall(FastNMS.INSTANCE.method$Entity$damageSources(entity)) @@ -49,16 +45,12 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { } private void bounceUp(Object entity) { - Object deltaMovement = FastNMS.INSTANCE.method$Entity$getDeltaMovement(entity); - if (FastNMS.INSTANCE.field$Vec3$y(deltaMovement) < 0.0) { + Vec3d deltaMovement = LocationUtils.fromVec(FastNMS.INSTANCE.method$Entity$getDeltaMovement(entity)); + if (deltaMovement.y < 0.0) { double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; - FastNMS.INSTANCE.method$Entity$setDeltaMovement( - entity, - FastNMS.INSTANCE.field$Vec3$x(deltaMovement), - -FastNMS.INSTANCE.field$Vec3$y(deltaMovement) * this.bounceHeight * d, - FastNMS.INSTANCE.field$Vec3$z(deltaMovement) - ); - if (this.syncPlayerSelf) { + double y = -deltaMovement.y * this.bounceHeight * d; + FastNMS.INSTANCE.method$Entity$setDeltaMovement(entity, deltaMovement.x, y, deltaMovement.z); + if (CoreReflections.clazz$Player.isInstance(entity) && this.syncPlayerSelf && y > 0.032) { // 防抖 FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 7d3449e77..413bbd092 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -347,7 +347,11 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { @Override public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { for (AbstractBlockBehavior behavior : this.behaviors) { - behavior.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); + if (behavior instanceof BouncingBlockBehavior bouncingBlockBehavior) { + bouncingBlockBehavior.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); + return; + } } + super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java index 0d0688e1c..9b572b357 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java @@ -29,7 +29,7 @@ public final class LocationUtils { return new Vec3d( FastNMS.INSTANCE.field$Vec3$x(vec), FastNMS.INSTANCE.field$Vec3$y(vec), - FastNMS.INSTANCE.field$Vec3$y(vec) + FastNMS.INSTANCE.field$Vec3$z(vec) ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 32b164635..e620be27a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -121,6 +121,7 @@ public abstract class BlockBehavior { // 1.21+ BlockState state, ServerLevel level, BlockPos pos, Explosion explosion, BiConsumer dropConsumer public void onExplosionHit(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); } // LevelAccessor level, BlockPos pos, BlockState state, FluidState fluidState @@ -181,10 +182,12 @@ public abstract class BlockBehavior { // 1.20.1~1.21.4 Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance // 1.21.5+ Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); } // BlockGetter level, Entity entity public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); } public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { From f85c9bb8dc33cb9c822977f70ad145b85e727ee0 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Wed, 10 Sep 2025 16:43:15 +0800 Subject: [PATCH 068/226] =?UTF-8?q?refactor(core):=20=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/momirealms/craftengine/core/block/BlockBehavior.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index e620be27a..a70227fa5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -121,7 +121,6 @@ public abstract class BlockBehavior { // 1.21+ BlockState state, ServerLevel level, BlockPos pos, Explosion explosion, BiConsumer dropConsumer public void onExplosionHit(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - superMethod.call(); } // LevelAccessor level, BlockPos pos, BlockState state, FluidState fluidState From dcc45c73276e53daa26368c22290d509ffbf9cc2 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 01:37:07 +0800 Subject: [PATCH 069/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dapi=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E7=9A=84=E7=A9=BA=E6=8C=87=E9=92=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/bukkit/api/CraftEngineBlocks.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java index 678f1b710..1cad23f69 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java @@ -188,9 +188,10 @@ public final class CraftEngineBlocks { if (dropLoot) { ContextHolder.Builder builder = new ContextHolder.Builder() .withParameter(DirectContextParameters.POSITION, position); - BukkitServerPlayer serverPlayer = BukkitCraftEngine.instance().adapt(player); + BukkitServerPlayer serverPlayer = null; if (player != null) { - builder.withParameter(DirectContextParameters.PLAYER, serverPlayer); + serverPlayer = BukkitCraftEngine.instance().adapt(player); + builder.withOptionalParameter(DirectContextParameters.PLAYER, serverPlayer); } for (Item item : state.getDrops(builder, world, serverPlayer)) { world.dropItemNaturally(position, item); From e1fdc17fd29ea5eecbb2008e870bed56a333e802 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 01:54:41 +0800 Subject: [PATCH 070/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0api=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/api/CraftEngineBlocks.java | 18 ++++++++++++++++++ .../bukkit/api/CraftEngineFurniture.java | 16 ++++++++++++++++ .../bukkit/api/CraftEngineItems.java | 18 ++++++++++++++++++ .../core/block/AbstractBlockManager.java | 2 +- .../craftengine/core/block/BlockManager.java | 7 ++++++- .../furniture/AbstractFurnitureManager.java | 5 +++++ .../entity/furniture/FurnitureManager.java | 3 +++ .../core/item/AbstractItemManager.java | 4 ++-- .../craftengine/core/item/CustomItem.java | 4 ++++ .../craftengine/core/item/ItemManager.java | 7 ++++++- .../craftengine/core/world/CEWorld.java | 1 - 11 files changed, 79 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java index 1cad23f69..93ec4c025 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java @@ -27,10 +27,28 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; + public final class CraftEngineBlocks { private CraftEngineBlocks() {} + /** + * + * Returns an unmodifiable map of all currently loaded custom blocks. + * The map keys represent unique identifiers, and the values are the corresponding CustomBlock instances. + * + *

Important: Do not attempt to access this method during the onEnable phase + * as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method + * after the event is fired to obtain the complete block list. + * + * @return a non-null map containing all loaded custom blocks + */ + @NotNull + public static Map loadedBlocks() { + return BukkitBlockManager.instance().loadedBlocks(); + } + /** * Gets a custom block by ID * diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java index 113fc0d93..d7367e5cd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java @@ -27,11 +27,27 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Map; public final class CraftEngineFurniture { private CraftEngineFurniture() {} + /** + * Returns an unmodifiable map of all currently loaded custom furniture. + * The map keys represent unique identifiers, and the values are the corresponding CustomFurniture instances. + * + *

Important: Do not attempt to access this method during the onEnable phase + * as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method + * after the event is fired to obtain the complete furniture list. + * + * @return a non-null map containing all loaded custom furniture + */ + @NotNull + public static Map loadedFurniture() { + return BukkitFurnitureManager.instance().loadedFurniture(); + } + /** * Gets custom furniture by ID * diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineItems.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineItems.java index 64c628693..2fe0c11c5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineItems.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineItems.java @@ -8,10 +8,28 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; + public final class CraftEngineItems { private CraftEngineItems() {} + /** + * Returns an unmodifiable map of all currently loaded custom items. + * The map keys represent unique identifiers, and the values are the corresponding CustomItem instances. + * + *

Important: Do not attempt to access this method during the onEnable phase + * as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method + * after the event is fired to obtain the complete item list. + * + * @return a non-null map containing all loaded custom items + * @throws IllegalStateException if the BukkitItemManager instance is not available + */ + @NotNull + public static Map> loadedItems() { + return BukkitItemManager.instance().loadedItems(); + } + /** * Gets a custom item by ID * diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 96916ea58..0fde49874 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -86,7 +86,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } @Override - public Map blocks() { + public Map loadedBlocks() { return Collections.unmodifiableMap(this.byId); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java index 1f6568636..785d13b17 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java @@ -24,7 +24,12 @@ public interface BlockManager extends Manageable, ModelGenerator { Map modBlockStates(); - Map blocks(); + Map loadedBlocks(); + + @Deprecated(forRemoval = true) + default Map blocks() { + return loadedBlocks(); + } Optional blockById(Key key); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index f343eb7ea..2e5b56471 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -58,6 +58,11 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { return Optional.ofNullable(this.byId.get(id)); } + @Override + public Map loadedFurniture() { + return Collections.unmodifiableMap(this.byId); + } + @Override public void unload() { this.byId.clear(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java index 2a31d46de..68cf5fa05 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java @@ -9,6 +9,7 @@ import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.Map; import java.util.Optional; public interface FurnitureManager extends Manageable { @@ -30,6 +31,8 @@ public interface FurnitureManager extends Manageable { Optional furnitureById(Key id); + Map loadedFurniture(); + boolean isFurnitureRealEntity(int entityId); @Nullable diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index f2fb1bba1..61a06df52 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -229,8 +229,8 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } @Override - public Collection items() { - return Collections.unmodifiableCollection(this.customItemsById.keySet()); + public Map> loadedItems() { + return Collections.unmodifiableMap(this.customItemsById); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java index b02cbea0b..bb7a71ed9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java @@ -16,6 +16,10 @@ import java.util.Optional; public interface CustomItem extends BuildableItem { + /** + * Since CraftEngine allows users to add certain functionalities to vanilla items, this custom item might actually be a vanilla item. + * This will be refactored before the 1.0 release, but no changes will be made for now to ensure compatibility. + */ boolean isVanillaItem(); Key id(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java index 4f34eff44..a255c23de 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java @@ -54,7 +54,12 @@ public interface ItemManager extends Manageable, ModelGenerator { Item fromByteArray(byte[] bytes); - Collection items(); + Map> loadedItems(); + + @Deprecated(forRemoval = true) + default Collection items() { + return loadedItems().keySet(); + } ExternalItemSource getExternalItemSource(String name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index cc9ccc239..c5c9f5249 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.world; -import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; From b817426575c3169b026cc2542d74847c83f51cc4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 02:15:09 +0800 Subject: [PATCH 071/226] =?UTF-8?q?=E5=85=81=E8=AE=B8=E9=87=8D=E5=8F=A0?= =?UTF-8?q?=E7=9A=84=E7=89=A9=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/bukkit/world/BukkitWorld.java | 3 ++- .../resources/default/configuration/categories.yml | 7 +++++-- .../craftengine/core/plugin/gui/category/Category.java | 4 +--- .../core/plugin/gui/category/ItemBrowserManagerImpl.java | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index db9d707bf..fc162525c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -44,7 +44,8 @@ public class BukkitWorld implements World { @Override public WorldHeight worldHeight() { if (this.worldHeight == null) { - this.worldHeight = WorldHeight.create(platformWorld().getMinHeight(), platformWorld().getMaxHeight() - platformWorld().getMinHeight()); + org.bukkit.World bWorld = platformWorld(); + this.worldHeight = WorldHeight.create(bWorld.getMinHeight(), bWorld.getMaxHeight() - bWorld.getMinHeight()); } return this.worldHeight; } diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 8614b02f7..aa33df50a 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -72,6 +72,8 @@ categories: - default:solid_gunpowder_block - default:ender_pearl_flower_seeds - default:gui_head_size_1 + - default:gui_head_size_4 + - minecraft:air - default:copper_coil - default:flame_elytra - default:cap @@ -79,5 +81,6 @@ categories: - default:chessboard_block - default:safe_block - default:sofa - - default:connectable_sofa - - default:gui_head_size_4 \ No newline at end of file + - minecraft:air + - minecraft:air + - default:connectable_sofa \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java index b5b139770..c4c18bb80 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java @@ -26,9 +26,7 @@ public class Category implements Comparable { } public void addMember(String member) { - if (!this.members.contains(member)) { - this.members.add(member); - } + this.members.add(member); } public Key id() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index fd5b95839..5556a9d6c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -114,7 +114,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { List members = MiscUtils.getAsStringList(section.getOrDefault("list", List.of())); Key icon = Key.of(section.getOrDefault("icon", ItemKeys.STONE).toString()); int priority = ResourceConfigUtils.getAsInt(section.getOrDefault("priority", 0), "priority"); - Category category = new Category(id, name, MiscUtils.getAsStringList(section.getOrDefault("lore", List.of())), icon, members.stream().distinct().toList(), priority, + Category category = new Category(id, name, MiscUtils.getAsStringList(section.getOrDefault("lore", List.of())), icon, new ArrayList<>(members), priority, ResourceConfigUtils.getAsBoolean(section.getOrDefault("hidden", false), "hidden")); if (ItemBrowserManagerImpl.this.byId.containsKey(id)) { ItemBrowserManagerImpl.this.byId.get(id).merge(category); From 584d8e1f7a2baa0b5237ea02581816e8bd292d68 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 03:20:47 +0800 Subject: [PATCH 072/226] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=91=94=E8=90=BD?= =?UTF-8?q?=E4=BC=A4=E5=AE=B3=E6=9C=BA=E5=88=B6=E5=A4=84=E7=90=86=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 19 +-- .../UnsafeCompositeBlockBehavior.java | 15 ++- .../plugin/injector/BlockGenerator.java | 15 ++- .../default/configuration/blocks.yml | 60 ++++------ .../default/configuration/categories.yml | 4 +- .../{sofa_straight.json => sleeper_sofa.json} | 111 +++++++----------- .../minecraft/models/item/custom/sofa.json | 56 +++++---- .../models/item/custom/sofa_inner.json | 8 +- .../craftengine/core/block/BlockBehavior.java | 11 -- .../special/TriggerOnceBlockBehavior.java | 17 +++ 10 files changed, 159 insertions(+), 157 deletions(-) rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/{sofa_straight.json => sleeper_sofa.json} (62%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 60974d53c..edb1d4cc1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.Vec3d; @@ -13,19 +14,22 @@ import net.momirealms.craftengine.core.world.Vec3d; import java.util.Map; import java.util.concurrent.Callable; -public class BouncingBlockBehavior extends BukkitBlockBehavior { +public class BouncingBlockBehavior extends BukkitBlockBehavior implements TriggerOnceBlockBehavior { public static final Factory FACTORY = new Factory(); private final double bounceHeight; - private final boolean syncPlayerSelf; + private final boolean fallDamage; - public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean syncPlayerSelf) { + public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean fallDamage) { super(customBlock); this.bounceHeight = bounceHeight; - this.syncPlayerSelf = syncPlayerSelf; + this.fallDamage = fallDamage; } @Override public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { + if (!this.fallDamage) { + return; + } Object entity = args[3]; Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * 0.5 : (float) args[4] * 0.5F; FastNMS.INSTANCE.method$Entity$causeFallDamage( @@ -50,7 +54,8 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; double y = -deltaMovement.y * this.bounceHeight * d; FastNMS.INSTANCE.method$Entity$setDeltaMovement(entity, deltaMovement.x, y, deltaMovement.z); - if (CoreReflections.clazz$Player.isInstance(entity) && this.syncPlayerSelf && y > 0.032) { // 防抖 + if (CoreReflections.clazz$Player.isInstance(entity) && y > 0.032) { + // 防抖 FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } @@ -61,8 +66,8 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { double bounceHeight = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bounce-height", 0.66), "bounce-height"); - boolean syncPlayerSelf = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("sync-player-self", true), "sync-player-self"); - return new BouncingBlockBehavior(block, bounceHeight, syncPlayerSelf); + boolean fallDamage = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("fall-damage", false), "fall-damage"); + return new BouncingBlockBehavior(block, bounceHeight, fallDamage); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 413bbd092..d70cf4e47 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.UseOnContext; @@ -14,7 +15,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; -public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { +public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior implements TriggerOnceBlockBehavior { private final AbstractBlockBehavior[] behaviors; public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List behaviors) { @@ -340,18 +341,22 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior { @Override public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { for (AbstractBlockBehavior behavior : this.behaviors) { - behavior.fallOn(thisBlock, args, superMethod); + if (behavior instanceof TriggerOnceBlockBehavior f) { + f.fallOn(thisBlock, args, superMethod); + return; + } } + TriggerOnceBlockBehavior.super.fallOn(thisBlock, args, superMethod); } @Override public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { for (AbstractBlockBehavior behavior : this.behaviors) { - if (behavior instanceof BouncingBlockBehavior bouncingBlockBehavior) { - bouncingBlockBehavior.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); + if (behavior instanceof TriggerOnceBlockBehavior f) { + f.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); return; } } - super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); + TriggerOnceBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index 8ad88d92e..fb1cd5739 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.block.BlockKeys; import net.momirealms.craftengine.core.block.BlockShape; import net.momirealms.craftengine.core.block.DelegatingBlock; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.Key; @@ -538,7 +539,7 @@ public final class BlockGenerator { public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); try { - holder.value().onExplosionHit(thisObj, args, superMethod); + holder.value().onExplosionHit(thisObj, args, () -> null); superMethod.call(); } catch (Exception e) { CraftEngine.instance().logger().severe("Failed to run onExplosionHit", e); @@ -714,7 +715,11 @@ public final class BlockGenerator { public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); try { - holder.value().fallOn(thisObj, args, superMethod); + if (holder.value() instanceof TriggerOnceBlockBehavior behavior) { + behavior.fallOn(thisObj, args, superMethod); + } else { + superMethod.call(); + } } catch (Exception e) { CraftEngine.instance().logger().severe("Failed to run fallOn", e); } @@ -728,7 +733,11 @@ public final class BlockGenerator { public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); try { - holder.value().updateEntityMovementAfterFallOn(thisObj, args, superMethod); + if (holder.value() instanceof TriggerOnceBlockBehavior behavior) { + behavior.updateEntityMovementAfterFallOn(thisObj, args, superMethod); + } else { + superMethod.call(); + } } catch (Exception e) { CraftEngine.instance().logger().severe("Failed to run updateEntityMovementAfterFallOn", e); } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 32d9d92d4..83dbba2a3 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -555,23 +555,23 @@ items#misc: facing=west,open=true: appearance: west_open id: 29 - default:sofa: + default:sleeper_sofa: material: nether_brick custom-model-data: 3008 data: item-name: model: type: minecraft:model - path: minecraft:item/custom/sofa + path: minecraft:item/custom/sleeper_sofa behavior: type: block_item block: loot: template: default:loot_table/self settings: - hardness: 2 - resistance: 3 - map-color: 56 + hardness: 0.5 + resistance: 0.5 + map-color: 27 burn-chance: 5 fire-spread-chance: 20 burnable: true @@ -595,40 +595,30 @@ items#misc: id: 0 state: white_bed[facing=west,occupied=false,part=foot] entity-renderer: - item: default:sofa - default:connectable_sofa_straight: + item: default:sleeper_sofa + default:sofa_inner: material: nether_brick custom-model-data: 3009 - data: - item-name: model: type: minecraft:model - path: minecraft:item/custom/sofa_straight - default:connectable_sofa_inner: + path: minecraft:item/custom/sofa_inner + default:sofa: material: nether_brick custom-model-data: 3010 data: item-name: model: type: minecraft:model - path: minecraft:item/custom/sofa_inner - default:connectable_sofa: - material: nether_brick - custom-model-data: 3011 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/sofa_straight + path: minecraft:item/custom/sofa behavior: type: block_item block: loot: template: default:loot_table/self settings: - hardness: 2 - resistance: 3 - map-color: 56 + hardness: 0.5 + resistance: 0.5 + map-color: 27 burn-chance: 5 fire-spread-chance: 20 burnable: true @@ -658,59 +648,59 @@ items#misc: facing=east,shape=straight: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_straight + item: default:sofa yaw: 90 facing=north,shape=straight: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_straight + item: default:sofa facing=south,shape=straight: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_straight + item: default:sofa yaw: 180 facing=west,shape=straight: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_straight + item: default:sofa yaw: 270 facing=east,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner facing=north,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner yaw: 270 facing=south,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner yaw: 90 facing=west,shape=inner_left: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner yaw: 180 facing=east,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner yaw: 90 facing=north,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner facing=south,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner yaw: 180 facing=west,shape=inner_right: state: barrier[waterlogged=false] entity-renderer: - item: default:connectable_sofa_inner + item: default:sofa_inner yaw: 270 variants: facing=east,shape=inner_left: diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index aa33df50a..989c66a4b 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -80,7 +80,7 @@ categories: - default:pebble - default:chessboard_block - default:safe_block - - default:sofa + - default:sleeper_sofa - minecraft:air - minecraft:air - - default:connectable_sofa \ No newline at end of file + - default:sofa \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json similarity index 62% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json index 822eaf59a..52088b05b 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json @@ -1,53 +1,24 @@ { - "texture_size": [64, 64], "textures": { "0": "item/custom/sofa", "particle": "item/custom/sofa" }, "elements": [ { - "from": [1, 0, 1], - "to": [3, 3, 3], - "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]}, + "from": [-0.005, 2.995, -0.005], + "to": [16.005, 9.005, 16.005], "faces": { - "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, - "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, - "south": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, - "west": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, - "up": {"uv": [16, 15.5, 15.5, 15], "texture": "#0"}, - "down": {"uv": [16, 15.5, 15.5, 16], "texture": "#0"} + "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "east": {"uv": [8, 11.5, 4, 13], "texture": "#0"}, + "south": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "west": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, + "up": {"uv": [4, 4, 0, 0], "texture": "#0"}, + "down": {"uv": [4, 12, 0, 16], "texture": "#0"} } }, { - "from": [13, 0, 1], - "to": [15, 3, 3], - "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 1]}, - "faces": { - "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, - "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, - "south": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, - "west": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, - "up": {"uv": [15.5, 15.5, 16, 15], "texture": "#0"}, - "down": {"uv": [15.5, 15.5, 16, 16], "texture": "#0"} - } - }, - { - "from": [13, 0, 13], - "to": [15, 3, 15], - "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 15]}, - "faces": { - "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, - "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, - "south": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, - "west": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, - "up": {"uv": [15.5, 15, 16, 15.5], "texture": "#0"}, - "down": {"uv": [15.5, 16, 16, 15.5], "texture": "#0"} - } - }, - { - "from": [1, 0, 13], - "to": [3, 3, 15], - "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, + "from": [-0.005, -0.005, 12.995], + "to": [3.005, 3.005, 16.005], "faces": { "north": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, "east": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, @@ -58,29 +29,39 @@ } }, { - "from": [0.01, 2.51, 0.01], - "to": [15.99, 8.49, 15.99], - "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, + "from": [12.995, -0.005, 12.995], + "to": [16.005, 3.005, 16.005], "faces": { - "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, - "east": {"uv": [8, 14.5, 4, 16], "texture": "#0"}, - "south": {"uv": [4, 13, 8, 14.5], "texture": "#0"}, - "west": {"uv": [4, 14.5, 8, 16], "texture": "#0"}, - "up": {"uv": [4, 12, 0, 8], "texture": "#0"}, - "down": {"uv": [4, 12, 0, 16], "texture": "#0"} + "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "south": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15, 16, 15.5], "texture": "#0"}, + "down": {"uv": [15.5, 16, 16, 15.5], "texture": "#0"} } }, { - "from": [0.02, 6.02, 12.02], - "to": [15.98, 19.98, 15.98], - "rotation": {"angle": 22.5, "axis": "x", "origin": [7, 8, 14]}, + "from": [12.995, -0.005, -0.005], + "to": [16.005, 3.005, 3.005], "faces": { - "north": {"uv": [9, 8.5, 13, 12], "texture": "#0"}, - "east": {"uv": [8, 12.5, 9, 16], "texture": "#0"}, - "south": {"uv": [9, 12.5, 13, 16], "texture": "#0"}, - "west": {"uv": [9, 12.5, 8, 16], "texture": "#0"}, - "up": {"uv": [9, 12, 8, 8.5], "rotation": 90, "texture": "#0"}, - "down": {"uv": [7.75, 10.5, 4.25, 11.5], "texture": "#0"} + "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "south": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "west": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, + "up": {"uv": [15.5, 15.5, 16, 15], "texture": "#0"}, + "down": {"uv": [15.5, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "from": [-0.005, -0.005, -0.005], + "to": [3.005, 3.005, 3.005], + "faces": { + "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, + "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "south": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, + "west": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, + "up": {"uv": [16, 15.5, 15.5, 15], "texture": "#0"}, + "down": {"uv": [16, 15.5, 15.5, 16], "texture": "#0"} } } ], @@ -96,12 +77,12 @@ "scale": [0.25, 0.25, 0.25] }, "firstperson_righthand": { - "rotation": [0, 180, 0], + "rotation": [0, -180, 0], "translation": [0.5, 0.5, 0], "scale": [0.5, 0.5, 0.5] }, "firstperson_lefthand": { - "rotation": [0, 180, 0], + "rotation": [0, -180, 0], "translation": [0.5, 0.5, 0], "scale": [0.5, 0.5, 0.5] }, @@ -111,7 +92,7 @@ }, "gui": { "rotation": [25, -135, 0], - "translation": [0.5, -1, 0], + "translation": [0.25, -1, 0], "scale": [0.5, 0.5, 0.5] }, "fixed": { @@ -119,13 +100,5 @@ "translation": [0, 0, -16], "scale": [2, 2, 2] } - }, - "groups": [ - { - "name": "group", - "origin": [0, 0, 0], - "color": 0, - "children": [0, 1, 2, 3, 4, 5] - } - ] + } } \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json index 6793fcf75..52f13c82e 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json @@ -1,13 +1,14 @@ { + "texture_size": [64, 64], "textures": { "0": "item/custom/sofa", "particle": "item/custom/sofa" }, "elements": [ { - "from": [-0.07, -0.035, -0.07], - "to": [2.95, 2.495, 2.95], - "rotation": {"angle": 0, "axis": "y", "origin": [-0.07, -0.035, -0.07]}, + "from": [1, 0, 1], + "to": [3, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]}, "faces": { "north": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, "east": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, @@ -18,9 +19,9 @@ } }, { - "from": [13.05, -0.035, -0.07], - "to": [16.07, 2.495, 2.95], - "rotation": {"angle": 0, "axis": "y", "origin": [16.07, -0.035, -0.07]}, + "from": [13, 0, 1], + "to": [15, 3, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 1]}, "faces": { "north": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, "east": {"uv": [14.5, 15.25, 15, 16], "texture": "#0"}, @@ -31,9 +32,9 @@ } }, { - "from": [13.05, -0.035, 13.05], - "to": [16.07, 2.495, 16.07], - "rotation": {"angle": 0, "axis": "y", "origin": [16.07, -0.035, 16.07]}, + "from": [13, 0, 13], + "to": [15, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 15]}, "faces": { "north": {"uv": [15, 15.25, 15.5, 16], "texture": "#0"}, "east": {"uv": [15, 15.25, 14.5, 16], "texture": "#0"}, @@ -44,9 +45,9 @@ } }, { - "from": [-0.07, -0.035, 13.05], - "to": [2.95, 2.495, 16.07], - "rotation": {"angle": 0, "axis": "y", "origin": [-0.07, -0.035, 16.07]}, + "from": [1, 0, 13], + "to": [3, 3, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, "faces": { "north": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, "east": {"uv": [15.5, 15.25, 15, 16], "texture": "#0"}, @@ -57,17 +58,30 @@ } }, { - "from": [-0.0699, 2.5001, -0.0699], - "to": [16.0699, 9.0399, 16.0699], - "rotation": {"angle": 0, "axis": "y", "origin": [-0.08, 1.985, -0.08]}, + "from": [0, 3, 0], + "to": [16, 9, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, "faces": { "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, - "east": {"uv": [8, 11.5, 4, 13], "texture": "#0"}, - "south": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, - "west": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, - "up": {"uv": [4, 4, 0, 0], "texture": "#0"}, + "east": {"uv": [8, 14.5, 4, 16], "texture": "#0"}, + "south": {"uv": [4, 13, 8, 14.5], "texture": "#0"}, + "west": {"uv": [4, 14.5, 8, 16], "texture": "#0"}, + "up": {"uv": [4, 12, 0, 8], "texture": "#0"}, "down": {"uv": [4, 12, 0, 16], "texture": "#0"} } + }, + { + "from": [0.02, 6.02, 12.02], + "to": [15.98, 19.98, 15.98], + "rotation": {"angle": 22.5, "axis": "x", "origin": [7, 8, 14]}, + "faces": { + "north": {"uv": [9, 8.5, 13, 12], "texture": "#0"}, + "east": {"uv": [8, 12.5, 9, 16], "texture": "#0"}, + "south": {"uv": [9, 12.5, 13, 16], "texture": "#0"}, + "west": {"uv": [9, 12.5, 8, 16], "texture": "#0"}, + "up": {"uv": [9, 12, 8, 8.5], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7.75, 10.5, 4.25, 11.5], "texture": "#0"} + } } ], "display": { @@ -97,7 +111,7 @@ }, "gui": { "rotation": [25, -135, 0], - "translation": [0.25, -1, 0], + "translation": [0.5, -1, 0], "scale": [0.5, 0.5, 0.5] }, "fixed": { @@ -111,7 +125,7 @@ "name": "group", "origin": [0, 0, 0], "color": 0, - "children": [0, 1, 2, 3, 4] + "children": [0, 1, 2, 3, 4, 5] } ] } \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json index e9c3a7bf3..af89f6c41 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json @@ -57,8 +57,8 @@ } }, { - "from": [0.01, 2.51, 0.01], - "to": [15.99, 8.49, 15.99], + "from": [0, 3, 0], + "to": [16, 9, 16], "rotation": {"angle": 0, "axis": "y", "origin": [0, 2, 0]}, "faces": { "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, @@ -98,7 +98,7 @@ ], "display": { "thirdperson_righthand": { - "rotation": [60, 180, 0], + "rotation": [60, -180, 0], "translation": [-0.5, 3, 0.25], "scale": [0.25, 0.25, 0.25] }, @@ -108,7 +108,7 @@ "scale": [0.25, 0.25, 0.25] }, "firstperson_righthand": { - "rotation": [0, 180, 0], + "rotation": [0, -180, 0], "translation": [0.5, 0.5, 0], "scale": [0.5, 0.5, 0.5] }, diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index a70227fa5..5536e4322 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -178,17 +178,6 @@ public abstract class BlockBehavior { public void spawnAfterBreak(Object thisBlock, Object[] args, Callable superMethod) throws Exception { } - // 1.20.1~1.21.4 Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance - // 1.21.5+ Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance - public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - superMethod.call(); - } - - // BlockGetter level, Entity entity - public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - superMethod.call(); - } - public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { return state; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java new file mode 100644 index 000000000..4cad47085 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java @@ -0,0 +1,17 @@ +package net.momirealms.craftengine.core.block.behavior.special; + +import java.util.concurrent.Callable; + +public interface TriggerOnceBlockBehavior { + + // 1.20.1~1.21.4 Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance + // 1.21.5+ Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance + default void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); + } + + // BlockGetter level, Entity entity + default void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); + } +} From c13c86a745950b50edccb76616f1c06f65cc11d9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 03:30:59 +0800 Subject: [PATCH 073/226] Update sleeper_sofa.json --- .../assets/minecraft/models/item/custom/sleeper_sofa.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json index 52088b05b..dd572f72c 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json @@ -5,8 +5,8 @@ }, "elements": [ { - "from": [-0.005, 2.995, -0.005], - "to": [16.005, 9.005, 16.005], + "from": [-0.025, 2.975, -0.025], + "to": [16.025, 9.025, 16.025], "faces": { "north": {"uv": [4, 11.5, 8, 13], "texture": "#0"}, "east": {"uv": [8, 11.5, 4, 13], "texture": "#0"}, From ae85ccdf23534d6d827cec2568a65b1115052f73 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 04:33:18 +0800 Subject: [PATCH 074/226] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E7=9A=84=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 21 --- .../renderer/BukkitBlockEntityElement.java | 106 -------------- .../renderer/BukkitBlockEntityRenderer.java | 81 ++++++----- .../BukkitBlockEntityElementConfigs.java | 13 ++ .../ItemDisplayBlockEntityElement.java | 47 +++++++ .../ItemDisplayBlockEntityElementConfig.java | 131 ++++++++++++++++++ .../bukkit/plugin/BukkitCraftEngine.java | 2 + .../bukkit/world/BukkitCEWorld.java | 6 +- .../core/block/AbstractBlockManager.java | 18 +-- .../core/block/AbstractCustomBlock.java | 2 +- .../core/block/BlockStateAppearance.java | 5 +- .../core/block/ImmutableBlockState.java | 16 +-- .../entity/render/BlockEntityElement.java | 32 ----- .../entity/render/BlockEntityRenderer.java | 4 + .../render/BlockEntityRendererConfig.java | 4 - .../render/element/BlockEntityElement.java | 12 ++ .../element/BlockEntityElementConfig.java | 8 ++ .../BlockEntityElementConfigFactory.java | 9 ++ .../element/BlockEntityElementConfigs.java | 31 +++++ .../network/legacy/LegacyRecipeTypes.java | 5 +- .../modern/display/RecipeDisplayTypes.java | 5 +- .../modern/display/slot/SlotDisplayTypes.java | 5 +- .../craftengine/core/plugin/CraftEngine.java | 6 +- .../number/ExpressionNumberProvider.java | 4 +- .../context/number/FixedNumberProvider.java | 4 +- .../number/GaussianNumberProvider.java | 5 +- .../context/number/UniformNumberProvider.java | 4 +- .../core/registry/BuiltInRegistries.java | 2 + .../craftengine/core/registry/Registries.java | 2 + .../craftengine/core/world/CEWorld.java | 4 +- .../craftengine/core/world/chunk/CEChunk.java | 13 +- 31 files changed, 358 insertions(+), 249 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java 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 c72d5abe3..f32cb3a5b 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,8 +7,6 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitBlockEntityElement; -import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator; @@ -25,10 +23,7 @@ import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.bukkit.util.TagUtils; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; import net.momirealms.craftengine.core.block.parser.BlockStateParser; -import net.momirealms.craftengine.core.entity.Billboard; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; @@ -689,20 +684,4 @@ public final class BukkitBlockManager extends AbstractBlockManager { } return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)) != MBlocks.AIR; } - - @Override - protected BlockEntityElement createBlockEntityElement(Map arguments) { - Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "")); - return new BukkitBlockEntityElement( - LazyReference.lazyReference(() -> BukkitItemManager.instance().createWrappedItem(itemId, null)), - ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), - ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), - ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"), - ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"), - ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), - ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"), - ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)), - Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)) - ); - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java deleted file mode 100644 index 93c1ff950..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityElement.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.entity.renderer; - -import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; -import net.momirealms.craftengine.core.entity.Billboard; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.util.LazyReference; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -import java.util.ArrayList; -import java.util.List; - -public class BukkitBlockEntityElement implements BlockEntityElement { - private final LazyReference> lazyMetadataPacket; - private final LazyReference> item; - private final Vector3f scale; - private final Vector3f position; - private final Vector3f translation; - private final float xRot; - private final float yRot; - private final Quaternionf rotation; - private final ItemDisplayContext displayContext; - private final Billboard billboard; - - public BukkitBlockEntityElement(LazyReference> item, - Vector3f scale, - Vector3f position, - Vector3f translation, - float xRot, - float yRot, - Quaternionf rotation, - ItemDisplayContext displayContext, - Billboard billboard) { - this.item = item; - this.scale = scale; - this.position = position; - this.translation = translation; - this.xRot = xRot; - this.yRot = yRot; - this.rotation = rotation; - this.displayContext = displayContext; - this.billboard = billboard; - this.lazyMetadataPacket = LazyReference.lazyReference(() -> { - List dataValues = new ArrayList<>(); - ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.get().getLiteralObject(), dataValues); - ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); - ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); - ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); - ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); - ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues); - return dataValues; - }); - } - - @Override - public Item item() { - return this.item.get(); - } - - @Override - public Vector3f scale() { - return this.scale; - } - - @Override - public Vector3f translation() { - return this.translation; - } - - @Override - public Vector3f position() { - return this.position; - } - - @Override - public float yRot() { - return this.yRot; - } - - @Override - public float xRot() { - return this.xRot; - } - - @Override - public Billboard billboard() { - return billboard; - } - - @Override - public ItemDisplayContext displayContext() { - return displayContext; - } - - @Override - public Quaternionf rotation() { - return rotation; - } - - @Override - public LazyReference> metadataValues() { - return this.lazyMetadataPacket; - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java index d7f354018..6c7e14267 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java @@ -1,48 +1,22 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.world.BlockPos; -import org.joml.Vector3f; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.List; -import java.util.UUID; public class BukkitBlockEntityRenderer extends BlockEntityRenderer { - private final Object cachedSpawnPacket; - private final Object cachedDespawnPacket; + private final BlockEntityElement[] elements; private final WeakReference chunkHolder; - public BukkitBlockEntityRenderer(WeakReference chunkHolder, - BlockEntityRendererConfig config, - BlockPos pos) { + public BukkitBlockEntityRenderer(WeakReference chunkHolder, BlockEntityElement[] elements) { this.chunkHolder = chunkHolder; - BlockEntityElement[] elements = config.elements(); - IntList ids = new IntArrayList(elements.length); - List spawnPackets = new ArrayList<>(elements.length); - for (BlockEntityElement element : elements) { - int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); - Vector3f position = element.position(); - spawnPackets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( - entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, - element.xRot(), element.yRot(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 - )); - spawnPackets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket( - entityId, element.metadataValues().get() - )); - ids.add(entityId); - } - this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(spawnPackets); - this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(ids); + this.elements = elements; } @Override @@ -50,10 +24,10 @@ public class BukkitBlockEntityRenderer extends BlockEntityRenderer { List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); if (players.isEmpty()) return; for (Object player : players) { - FastNMS.INSTANCE.method$ServerPlayerConnection$send( - FastNMS.INSTANCE.field$Player$connection(player), - this.cachedDespawnPacket - ); + org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(bkPlayer); + if (serverPlayer == null) continue; + despawn(serverPlayer); } } @@ -62,20 +36,43 @@ public class BukkitBlockEntityRenderer extends BlockEntityRenderer { List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); if (players.isEmpty()) return; for (Object player : players) { - FastNMS.INSTANCE.method$ServerPlayerConnection$send( - FastNMS.INSTANCE.field$Player$connection(player), - this.cachedSpawnPacket - ); + org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(bkPlayer); + if (serverPlayer == null) continue; + spawn(serverPlayer); + } + } + + @Override + public void update() { + List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); + if (players.isEmpty()) return; + for (Object player : players) { + org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(bkPlayer); + if (serverPlayer == null) continue; + update(serverPlayer); + } + } + + @Override + public void update(Player player) { + for (BlockEntityElement element : this.elements) { + element.update(player); } } @Override public void spawn(Player player) { - player.sendPacket(this.cachedSpawnPacket, false); + for (BlockEntityElement element : this.elements) { + element.spawn(player); + } } @Override public void despawn(Player player) { - player.sendPacket(this.cachedDespawnPacket, false); + for (BlockEntityElement element : this.elements) { + element.despawn(player); + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java new file mode 100644 index 000000000..a8e8effd4 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigs; + +public class BukkitBlockEntityElementConfigs extends BlockEntityElementConfigs { + + static { + register(ITEM_DISPLAY, ItemDisplayBlockEntityElementConfig.FACTORY); + } + + public static void init() { + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java new file mode 100644 index 000000000..b195e328d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java @@ -0,0 +1,47 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.BlockPos; +import org.joml.Vector3f; + +import java.util.List; +import java.util.UUID; + +public class ItemDisplayBlockEntityElement implements BlockEntityElement { + private final Object cachedSpawnPacket; + private final Object cachedDespawnPacket; + + public ItemDisplayBlockEntityElement(ItemDisplayBlockEntityElementConfig config, BlockPos pos) { + int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); + Vector3f position = config.position(); + this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of( + FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, + config.xRot(), config.yRot(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 + ), + FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket( + entityId, config.metadataValues().get() + ) + )); + this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId)); + } + + @Override + public void despawn(Player player) { + player.sendPacket(this.cachedDespawnPacket, false); + } + + @Override + public void spawn(Player player) { + player.sendPacket(this.cachedSpawnPacket, true); + } + + @Override + public void update(Player player) { + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java new file mode 100644 index 000000000..35d53f551 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java @@ -0,0 +1,131 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; +import net.momirealms.craftengine.core.entity.Billboard; +import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.LazyReference; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementConfig { + public static final Factory FACTORY = new Factory(); + private final LazyReference> lazyMetadataPacket; + private final LazyReference> item; + private final Vector3f scale; + private final Vector3f position; + private final Vector3f translation; + private final float xRot; + private final float yRot; + private final Quaternionf rotation; + private final ItemDisplayContext displayContext; + private final Billboard billboard; + + public ItemDisplayBlockEntityElementConfig(LazyReference> item, + Vector3f scale, + Vector3f position, + Vector3f translation, + float xRot, + float yRot, + Quaternionf rotation, + ItemDisplayContext displayContext, + Billboard billboard) { + this.item = item; + this.scale = scale; + this.position = position; + this.translation = translation; + this.xRot = xRot; + this.yRot = yRot; + this.rotation = rotation; + this.displayContext = displayContext; + this.billboard = billboard; + this.lazyMetadataPacket = LazyReference.lazyReference(() -> { + List dataValues = new ArrayList<>(); + ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.get().getLiteralObject(), dataValues); + ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); + ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); + ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); + ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); + ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues); + return dataValues; + }); + } + + @Override + public ItemDisplayBlockEntityElement create(BlockPos pos) { + return new ItemDisplayBlockEntityElement(this, pos); + } + + public Item item() { + return this.item.get(); + } + + public Vector3f scale() { + return this.scale; + } + + public Vector3f translation() { + return this.translation; + } + + public Vector3f position() { + return this.position; + } + + public float yRot() { + return this.yRot; + } + + public float xRot() { + return this.xRot; + } + + public Billboard billboard() { + return billboard; + } + + public ItemDisplayContext displayContext() { + return displayContext; + } + + public Quaternionf rotation() { + return rotation; + } + + public LazyReference> metadataValues() { + return this.lazyMetadataPacket; + } + + public static class Factory implements BlockEntityElementConfigFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockEntityElementConfig create(Map arguments) { + // todo item should not be null + Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "")); + return (BlockEntityElementConfig) new ItemDisplayBlockEntityElementConfig( + LazyReference.lazyReference(() -> BukkitItemManager.instance().createWrappedItem(itemId, null)), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), + ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), + ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"), + ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)), + Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)) + ); + } + } +} 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 e69ac269a..6633b1fa3 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 @@ -5,6 +5,7 @@ import net.momirealms.craftengine.bukkit.advancement.BukkitAdvancementManager; 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.entity.renderer.element.BukkitBlockEntityElementConfigs; import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitHitBoxTypes; import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager; @@ -189,6 +190,7 @@ public class BukkitCraftEngine extends CraftEngine { BukkitBlockBehaviors.init(); BukkitItemBehaviors.init(); BukkitHitBoxTypes.init(); + BukkitBlockEntityElementConfigs.init(); PacketConsumers.initEntities(RegistryUtils.currentEntityTypeRegistrySize()); super.packManager = new BukkitPackManager(this); super.senderFactory = new BukkitSenderFactory(this); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index a8022284b..e9a079384 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -4,7 +4,7 @@ import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitBlockEntity import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.LightUtils; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; import net.momirealms.craftengine.core.world.*; @@ -45,11 +45,11 @@ public class BukkitCEWorld extends CEWorld { } @Override - public BlockEntityRenderer createBlockEntityRenderer(BlockEntityRendererConfig config, BlockPos pos) { + public BlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, BlockPos pos) { Object serverLevel = this.world.serverWorld(); Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); long chunkKey = ChunkPos.asLong(pos.x() >> 4, pos.z() >> 4); Object chunkHolder = FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, chunkKey); - return new BukkitBlockEntityRenderer(new WeakReference<>(chunkHolder), config, pos); + return new BukkitBlockEntityRenderer(new WeakReference<>(chunkHolder), elements); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 0fde49874..458587234 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -4,8 +4,9 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityElement; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigs; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.loot.LootTable; @@ -168,8 +169,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected abstract CustomBlock.Builder platformBuilder(Key id); - protected abstract BlockEntityElement createBlockEntityElement(Map arguments); - public class BlockParser implements ConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; @@ -225,7 +224,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 获取原版外观的注册表id int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow( stateSection.get("state"), "warning.config.block.state.missing_state")); - Optional blockEntityRenderer = parseBlockEntityRender(stateSection.get("entity-renderer")); + Optional[]> blockEntityRenderer = parseBlockEntityRender(stateSection.get("entity-renderer")); // 为原版外观赋予外观模型并检查模型冲突 this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(stateSection, "model", "models")); @@ -299,11 +298,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return appearances; } - private Optional parseBlockEntityRender(Object arguments) { + @SuppressWarnings("unchecked") + private Optional[]> parseBlockEntityRender(Object arguments) { if (arguments == null) return Optional.empty(); - List elements = ResourceConfigUtils.parseConfigAsList(arguments, AbstractBlockManager.this::createBlockEntityElement); - if (elements.isEmpty()) return Optional.empty(); - return Optional.of(new BlockEntityRendererConfig(elements.toArray(new BlockEntityElement[0]))); + List> blockEntityElementConfigs = ResourceConfigUtils.parseConfigAsList(arguments, BlockEntityElementConfigs::fromMap); + if (blockEntityElementConfigs.isEmpty()) return Optional.empty(); + return Optional.of(blockEntityElementConfigs.toArray(new BlockEntityElementConfig[0])); } @NotNull diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index f69e2061d..52a246e06 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -86,7 +86,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { state.setSettings(blockStateVariant.settings()); state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(stateId)); state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); - blockStateAppearance.blockEntityRenderer().ifPresent(state::setEntityRenderer); + blockStateAppearance.blockEntityRenderer().ifPresent(state::setRenderers); } // double check if there's any invalid state diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java index 9bd5565f8..0e2c7acc6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -1,10 +1,11 @@ package net.momirealms.craftengine.core.block; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import java.util.Optional; -public record BlockStateAppearance(int stateRegistryId, Optional blockEntityRenderer) { +public record BlockStateAppearance(int stateRegistryId, Optional[]> blockEntityRenderer) { public static final BlockStateAppearance INVALID = new BlockStateAppearance(-1, Optional.empty()); public boolean isInvalid() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index b733743a4..95ea103ce 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -4,7 +4,8 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.BlockEntityType; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.Player; @@ -32,7 +33,7 @@ public final class ImmutableBlockState extends BlockStateHolder { private BlockSettings settings; private BlockEntityType blockEntityType; @Nullable - private BlockEntityRendererConfig renderer; + private BlockEntityElementConfig[] renderers; ImmutableBlockState( Holder owner, @@ -69,13 +70,12 @@ public final class ImmutableBlockState extends BlockStateHolder { return this == EmptyBlock.STATE; } - @Nullable - public BlockEntityRendererConfig entityRenderer() { - return this.renderer; + public BlockEntityElementConfig[] renderers() { + return renderers; } - public void setEntityRenderer(@Nullable BlockEntityRendererConfig rendererConfig) { - this.renderer = rendererConfig; + public void setRenderers(BlockEntityElementConfig[] renderers) { + this.renderers = renderers; } @Override @@ -98,7 +98,7 @@ public final class ImmutableBlockState extends BlockStateHolder { } public boolean hasBlockEntityRenderer() { - return this.renderer != null; + return this.renderers != null; } public BlockStateWrapper customBlockState() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java deleted file mode 100644 index 07939e997..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityElement.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.momirealms.craftengine.core.block.entity.render; - -import net.momirealms.craftengine.core.entity.Billboard; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.util.LazyReference; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -import java.util.List; - -public interface BlockEntityElement { - Item item(); - - Vector3f scale(); - - Vector3f translation(); - - Vector3f position(); - - float yRot(); - - float xRot(); - - Billboard billboard(); - - ItemDisplayContext displayContext(); - - Quaternionf rotation(); - - LazyReference> metadataValues(); -} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java index 2b1e5e7a0..4ed0d9ab8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java @@ -8,7 +8,11 @@ public abstract class BlockEntityRenderer { public abstract void despawn(); + public abstract void update(); + public abstract void spawn(Player player); public abstract void despawn(Player player); + + public abstract void update(Player player); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java deleted file mode 100644 index e49a3f164..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRendererConfig.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.momirealms.craftengine.core.block.entity.render; - -public record BlockEntityRendererConfig(BlockEntityElement[] elements) { -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java new file mode 100644 index 000000000..7ce7e22de --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java @@ -0,0 +1,12 @@ +package net.momirealms.craftengine.core.block.entity.render.element; + +import net.momirealms.craftengine.core.entity.player.Player; + +public interface BlockEntityElement { + + void spawn(Player player); + + void despawn(Player player); + + void update(Player player); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java new file mode 100644 index 000000000..32d7bf4ae --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.block.entity.render.element; + +import net.momirealms.craftengine.core.world.BlockPos; + +public interface BlockEntityElementConfig { + + E create(BlockPos pos); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java new file mode 100644 index 000000000..2aba16476 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java @@ -0,0 +1,9 @@ +package net.momirealms.craftengine.core.block.entity.render.element; + +import java.util.Map; + +@FunctionalInterface +public interface BlockEntityElementConfigFactory { + + BlockEntityElementConfig create(Map args); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java new file mode 100644 index 000000000..6ea5d16ad --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java @@ -0,0 +1,31 @@ +package net.momirealms.craftengine.core.block.entity.render.element; + +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.registry.Registries; +import net.momirealms.craftengine.core.registry.WritableRegistry; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; + +import java.util.Map; +import java.util.Optional; + +public class BlockEntityElementConfigs { + public static final Key ITEM_DISPLAY = Key.of("craftengine:item_display"); + public static final Key TEXT_DISPLAY = Key.of("craftengine:text_display"); + + public static void register(Key key, BlockEntityElementConfigFactory type) { + ((WritableRegistry) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE) + .register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type); + } + + public static BlockEntityElementConfig fromMap(Map arguments) { + Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(ITEM_DISPLAY); + BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type); + if (factory == null) { + // todo 发送消息 + throw new LocalizedResourceConfigException("", type.toString()); + } + return factory.create(arguments); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/legacy/LegacyRecipeTypes.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/legacy/LegacyRecipeTypes.java index f162b1be0..89ef272af 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/legacy/LegacyRecipeTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/legacy/LegacyRecipeTypes.java @@ -35,7 +35,10 @@ public final class LegacyRecipeTypes { public static final Key SMITHING_TRIM = Key.of("smithing_trim"); public static final Key DECORATED_POT_RECIPE = Key.of("crafting_decorated_pot"); - public static void register() { + public static void init() { + } + + static { register(SHAPED_RECIPE, new LegacyRecipe.Type(LegacyShapedRecipe::read)); register(SHAPELESS_RECIPE, new LegacyRecipe.Type(LegacyShapelessRecipe::read)); register(ARMOR_DYE, new LegacyRecipe.Type(LegacyCustomRecipe::read)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/RecipeDisplayTypes.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/RecipeDisplayTypes.java index da82d2a0d..81d781991 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/RecipeDisplayTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/RecipeDisplayTypes.java @@ -15,7 +15,10 @@ public final class RecipeDisplayTypes { public static final Key STONECUTTER = Key.of("stonecutter"); public static final Key SMITHING = Key.of("smithing"); - public static void register() { + public static void init() { + } + + static { register(CRAFTING_SHAPELESS, new RecipeDisplay.Type(ShapelessCraftingRecipeDisplay::read)); register(CRAFTING_SHAPED, new RecipeDisplay.Type(ShapedCraftingRecipeDisplay::read)); register(FURNACE, new RecipeDisplay.Type(FurnaceRecipeDisplay::read)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/slot/SlotDisplayTypes.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/slot/SlotDisplayTypes.java index 36e63baf4..123f8dc0c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/slot/SlotDisplayTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/network/modern/display/slot/SlotDisplayTypes.java @@ -18,7 +18,10 @@ public final class SlotDisplayTypes { public static final Key WITH_REMAINDER = Key.of("with_remainder"); public static final Key COMPOSITE = Key.of("composite"); - public static void register() { + public static void init() { + } + + static { register(EMPTY, new SlotDisplay.Type(EmptySlotDisplay::read)); register(ANY_FUEL, new SlotDisplay.Type(AnyFuelDisplay::read)); register(ITEM, new SlotDisplay.Type(ItemSlotDisplay::read)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index aa5eea243..276dc3ee8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -102,9 +102,9 @@ public abstract class CraftEngine implements Plugin { } protected void onPluginLoad() { - RecipeDisplayTypes.register(); - SlotDisplayTypes.register(); - LegacyRecipeTypes.register(); + RecipeDisplayTypes.init(); + SlotDisplayTypes.init(); + LegacyRecipeTypes.init(); ((Logger) LogManager.getRootLogger()).addFilter(new LogFilter()); ((Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java index 43bf33fcf..e00e459d6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java @@ -12,7 +12,7 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; public class ExpressionNumberProvider implements NumberProvider { - public static final FactoryImpl FACTORY = new FactoryImpl(); + public static final Factory FACTORY = new Factory(); private final String expr; public ExpressionNumberProvider(String expr) { @@ -52,7 +52,7 @@ public class ExpressionNumberProvider implements NumberProvider { return this.expr; } - public static class FactoryImpl implements NumberProviderFactory { + public static class Factory implements NumberProviderFactory { @Override public NumberProvider create(Map arguments) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java index d636f5268..5deeeb5a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java @@ -9,7 +9,7 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; public class FixedNumberProvider implements NumberProvider { - public static final FactoryImpl FACTORY = new FactoryImpl(); + public static final Factory FACTORY = new Factory(); private final double value; public FixedNumberProvider(double value) { @@ -35,7 +35,7 @@ public class FixedNumberProvider implements NumberProvider { return new FixedNumberProvider(value); } - public static class FactoryImpl implements NumberProviderFactory { + public static class Factory implements NumberProviderFactory { @Override public NumberProvider create(Map arguments) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java index 6ca708f2b..77de046d5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java @@ -10,8 +10,7 @@ import java.util.Random; import java.util.concurrent.ThreadLocalRandom; public class GaussianNumberProvider implements NumberProvider { - public static final FactoryImpl FACTORY = new FactoryImpl(); - + public static final Factory FACTORY = new Factory(); private final double min; private final double max; private final double mean; @@ -83,7 +82,7 @@ public class GaussianNumberProvider implements NumberProvider { return stdDev; } - public static class FactoryImpl implements NumberProviderFactory { + public static class Factory implements NumberProviderFactory { @Override public NumberProvider create(Map arguments) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java index dca387d0e..eb56593d0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java @@ -8,7 +8,7 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; public class UniformNumberProvider implements NumberProvider { - public static final FactoryImpl FACTORY = new FactoryImpl(); + public static final Factory FACTORY = new Factory(); private final NumberProvider min; private final NumberProvider max; @@ -45,7 +45,7 @@ public class UniformNumberProvider implements NumberProvider { return NumberProviders.UNIFORM; } - public static class FactoryImpl implements NumberProviderFactory { + public static class Factory implements NumberProviderFactory { @Override public NumberProvider create(Map arguments) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index e5db08ae8..d2dba3025 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.registry; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.properties.PropertyFactory; import net.momirealms.craftengine.core.entity.furniture.HitBoxFactory; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; @@ -87,6 +88,7 @@ public class BuiltInRegistries { public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE, 16); public static final Registry> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET, 16); public static final Registry> BLOCK_ENTITY_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_TYPE, 128); + public static final Registry BLOCK_ENTITY_ELEMENT_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_ELEMENT_TYPE, 16); private static Registry createConstantBoundRegistry(ResourceKey> key, int expectedSize) { return new ConstantBoundRegistry<>(key, expectedSize); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index 374ff31f8..dcf5fe8bc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.registry; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.properties.PropertyFactory; import net.momirealms.craftengine.core.entity.furniture.HitBoxFactory; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; @@ -89,4 +90,5 @@ public class Registries { public static final ResourceKey>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type")); public static final ResourceKey>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet_type")); public static final ResourceKey>> BLOCK_ENTITY_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_type")); + public static final ResourceKey> BLOCK_ENTITY_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_element_type")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index c5c9f5249..998af6ac1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -5,7 +5,7 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -220,5 +220,5 @@ public abstract class CEWorld { this.isTickingBlockEntities = false; } - public abstract BlockEntityRenderer createBlockEntityRenderer(BlockEntityRendererConfig config, BlockPos pos); + public abstract BlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, BlockPos pos); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 4dbea6f15..beab7ec93 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRendererConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.logger.Debugger; @@ -103,9 +104,13 @@ public class CEChunk { } public void addBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) { - BlockEntityRendererConfig config = state.entityRenderer(); - if (config != null) { - BlockEntityRenderer renderer = this.world.createBlockEntityRenderer(config, pos); + BlockEntityElementConfig[] renderers = state.renderers(); + if (renderers != null && renderers.length > 0) { + BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; + for (int i = 0; i < elements.length; i++) { + elements[i] = renderers[i].create(pos); + } + BlockEntityRenderer renderer = this.world.createBlockEntityRenderer(elements, pos); renderer.spawn(); try { this.renderLock.writeLock().lock(); From d8347fb7ec001b5ce0b56c084f2a25950e25d84b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 04:56:47 +0800 Subject: [PATCH 075/226] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E7=9A=84hashcode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/block/ImmutableBlockState.java | 18 +----------------- .../craftengine/core/util/Int2ObjectBiMap.java | 4 ++-- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 95ea103ce..8eb70038f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -29,7 +29,6 @@ public final class ImmutableBlockState extends BlockStateHolder { private BlockStateWrapper customBlockState; private BlockStateWrapper vanillaBlockState; private BlockBehavior behavior; - private Integer hashCode; private BlockSettings settings; private BlockEntityType blockEntityType; @Nullable @@ -78,27 +77,12 @@ public final class ImmutableBlockState extends BlockStateHolder { this.renderers = renderers; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ImmutableBlockState state)) return false; - return state.owner == this.owner && state.tag.equals(this.tag); - } - - @Override - public int hashCode() { - if (this.hashCode == null) { - this.hashCode = getNbtToSave().hashCode(); - } - return this.hashCode; - } - public boolean hasBlockEntity() { return this.blockEntityType != null; } public boolean hasBlockEntityRenderer() { - return this.renderers != null; + return this.renderers != null ; } public BlockStateWrapper customBlockState() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java b/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java index 297d49600..76cbad82e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java @@ -126,7 +126,7 @@ public class Int2ObjectBiMap implements IndexedIterable { private int findIndex(@Nullable K value, int id) { int i; - for(i = id; i < this.values.length; ++i) { + for (i = id; i < this.values.length; ++i) { if (this.values[i] == value) { return i; } @@ -136,7 +136,7 @@ public class Int2ObjectBiMap implements IndexedIterable { } } - for(i = 0; i < id; ++i) { + for (i = 0; i < id; ++i) { if (this.values[i] == value) { return i; } From d00efda2d7286a581f3288e8c36bf6cf3218c3b2 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Thu, 11 Sep 2025 15:27:01 +0800 Subject: [PATCH 076/226] =?UTF-8?q?refactor(block):=20=E6=9B=B4=E6=B8=85?= =?UTF-8?q?=E6=99=B0=E7=9A=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 16 +++++++++------- .../resources/default/configuration/blocks.yml | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index edb1d4cc1..aeaa1934f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -17,19 +17,19 @@ import java.util.concurrent.Callable; public class BouncingBlockBehavior extends BukkitBlockBehavior implements TriggerOnceBlockBehavior { public static final Factory FACTORY = new Factory(); private final double bounceHeight; + private final boolean syncPlayerPosition; private final boolean fallDamage; - public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean fallDamage) { + public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean syncPlayerPosition, boolean fallDamage) { super(customBlock); this.bounceHeight = bounceHeight; + this.syncPlayerPosition = syncPlayerPosition; this.fallDamage = fallDamage; } @Override public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { - if (!this.fallDamage) { - return; - } + if (!this.fallDamage) return; Object entity = args[3]; Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * 0.5 : (float) args[4] * 0.5F; FastNMS.INSTANCE.method$Entity$causeFallDamage( @@ -54,8 +54,9 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge double d = CoreReflections.clazz$LivingEntity.isInstance(entity) ? 1.0 : 0.8; double y = -deltaMovement.y * this.bounceHeight * d; FastNMS.INSTANCE.method$Entity$setDeltaMovement(entity, deltaMovement.x, y, deltaMovement.z); - if (CoreReflections.clazz$Player.isInstance(entity) && y > 0.032) { - // 防抖 + if (CoreReflections.clazz$Player.isInstance(entity) && this.syncPlayerPosition + && /* 防抖 -> */ y > 0.032 /* <- 防抖 */ + ) { FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); } } @@ -66,8 +67,9 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge @Override public BlockBehavior create(CustomBlock block, Map arguments) { double bounceHeight = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bounce-height", 0.66), "bounce-height"); + boolean syncPlayerPosition = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("sync-player-position", true), "sync-player-position"); boolean fallDamage = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("fall-damage", false), "fall-damage"); - return new BouncingBlockBehavior(block, bounceHeight, fallDamage); + return new BouncingBlockBehavior(block, bounceHeight, syncPlayerPosition, fallDamage); } } } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 83dbba2a3..757d0d468 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -590,7 +590,7 @@ items#misc: behavior: type: bouncing_block bounce-height: 0.66 - sync-player-self: false + sync-player-position: false state: id: 0 state: white_bed[facing=west,occupied=false,part=foot] From 324cba94cc7ccd5e2596d8bdcfa160069296a9e5 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Thu, 11 Sep 2025 17:59:43 +0800 Subject: [PATCH 077/226] =?UTF-8?q?feat(BouncingBlockBehavior):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8E=89=E8=90=BD?= =?UTF-8?q?=E4=BC=A4=E5=AE=B3=E5=80=8D=E6=95=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index aeaa1934f..2d40df2d3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -18,20 +18,20 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge public static final Factory FACTORY = new Factory(); private final double bounceHeight; private final boolean syncPlayerPosition; - private final boolean fallDamage; + private final double fallDamageMultiplier; - public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean syncPlayerPosition, boolean fallDamage) { + public BouncingBlockBehavior(CustomBlock customBlock, double bounceHeight, boolean syncPlayerPosition, double fallDamageMultiplier) { super(customBlock); this.bounceHeight = bounceHeight; this.syncPlayerPosition = syncPlayerPosition; - this.fallDamage = fallDamage; + this.fallDamageMultiplier = fallDamageMultiplier; } @Override public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { - if (!this.fallDamage) return; + if (this.fallDamageMultiplier <= 0.0) return; Object entity = args[3]; - Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * 0.5 : (float) args[4] * 0.5F; + Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * this.fallDamageMultiplier : (float) args[4] * this.fallDamageMultiplier; FastNMS.INSTANCE.method$Entity$causeFallDamage( entity, finalFallDistance, 1.0F, FastNMS.INSTANCE.method$DamageSources$fall(FastNMS.INSTANCE.method$Entity$damageSources(entity)) @@ -68,8 +68,8 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge public BlockBehavior create(CustomBlock block, Map arguments) { double bounceHeight = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bounce-height", 0.66), "bounce-height"); boolean syncPlayerPosition = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("sync-player-position", true), "sync-player-position"); - boolean fallDamage = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("fall-damage", false), "fall-damage"); - return new BouncingBlockBehavior(block, bounceHeight, syncPlayerPosition, fallDamage); + double fallDamageMultiplier = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("fall-damage-multiplier", 0.5), "fall-damage-multiplier"); + return new BouncingBlockBehavior(block, bounceHeight, syncPlayerPosition, fallDamageMultiplier); } } } From c5c518ce148254c378cf3e3b207949800e21e7c8 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 11 Sep 2025 18:29:42 +0800 Subject: [PATCH 078/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0tp=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/BukkitPlatform.java | 11 ++ .../plugin/user/BukkitServerPlayer.java | 11 +- .../core/entity/player/Player.java | 4 + .../craftengine/core/plugin/CraftEngine.java | 1 + .../craftengine/core/plugin/Platform.java | 3 + .../plugin/context/event/EventFunctions.java | 1 + .../context/function/CommonFunctions.java | 2 + .../context/function/TeleportFunction.java | 104 ++++++++++++++++++ 8 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java index e1055644a..6e1545272 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java @@ -2,10 +2,12 @@ package net.momirealms.craftengine.bukkit.plugin; import com.google.gson.JsonElement; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps; import net.momirealms.craftengine.core.plugin.Platform; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.world.World; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; import org.bukkit.Bukkit; @@ -51,4 +53,13 @@ public class BukkitPlatform implements Platform { public Tag javaToSparrowNBT(Object object) { return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object); } + + @Override + public World getWorld(String name) { + org.bukkit.World world = Bukkit.getWorld(name); + if (world == null) { + return null; + } + return BukkitAdaptors.adapt(world); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 4ec9a9a20..5b9512f15 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -37,10 +37,8 @@ import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.World; -import net.momirealms.craftengine.core.world.WorldEvents; import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.*; @@ -48,6 +46,7 @@ import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -1094,4 +1093,10 @@ public class BukkitServerPlayer extends Player { public void clearTrackedChunks() { this.trackedChunks.clear(); } + + @Override + public void teleport(WorldPosition worldPosition) { + Location location = new Location((org.bukkit.World) worldPosition.world().platformWorld(), worldPosition.x(), worldPosition.y(), worldPosition.z(), worldPosition.yRot(), worldPosition.xRot()); + this.platformPlayer().teleportAsync(location, PlayerTeleportEvent.TeleportCause.PLUGIN); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 6675659ba..3ed4302fc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -8,6 +8,8 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Position; +import net.momirealms.craftengine.core.world.WorldPosition; import org.jetbrains.annotations.NotNull; public abstract class Player extends AbstractEntity implements NetWorkUser { @@ -148,4 +150,6 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void clearPotionEffects(); public abstract CooldownData cooldown(); + + public abstract void teleport(WorldPosition worldPosition); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 276dc3ee8..b2759fd4b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -35,6 +35,7 @@ import net.momirealms.craftengine.core.plugin.logger.filter.LogFilter; import net.momirealms.craftengine.core.plugin.network.NetworkManager; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.sound.SoundManager; +import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Logger; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java index 03e0c52b2..3ab8e0267 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.plugin; import com.google.gson.JsonElement; +import net.momirealms.craftengine.core.world.World; import net.momirealms.sparrow.nbt.Tag; public interface Platform { @@ -14,4 +15,6 @@ public interface Platform { Tag snbtToSparrowNBT(String nbt); Tag javaToSparrowNBT(Object object); + + World getWorld(String name); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java index fbb129296..bfde55253 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java @@ -42,6 +42,7 @@ public class EventFunctions { register(CommonFunctions.REMOVE_FURNITURE, new RemoveFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.REPLACE_FURNITURE, new ReplaceFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.MYTHIC_MOBS_SKILL, new MythicMobsSkillFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.TELEPORT, new TeleportFunction.FactoryImpl<>(EventConditions::fromMap)); } public static void register(Key key, FunctionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java index bede6e946..5f1d6d787 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java @@ -31,4 +31,6 @@ public final class CommonFunctions { public static final Key REMOVE_FURNITURE = Key.of("craftengine:remove_furniture"); public static final Key REPLACE_FURNITURE = Key.of("craftengine:replace_furniture"); public static final Key MYTHIC_MOBS_SKILL = Key.of("craftengine:mythic_mobs_skill"); + public static final Key TELEPORT = Key.of("craftengine:teleport"); + public static final Key TOAST = Key.of("craftengine:toast"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java new file mode 100644 index 000000000..1455c93ab --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java @@ -0,0 +1,104 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.Platform; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.WorldPosition; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Text; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +public class TeleportFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + @Nullable + private final TextProvider world; + private final NumberProvider x; + private final NumberProvider y; + private final NumberProvider z; + private final NumberProvider pitch; + private final NumberProvider yaw; + + public TeleportFunction(List> predicates, @Nullable PlayerSelector selector, @Nullable TextProvider world, + NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider pitch, NumberProvider yaw) { + super(predicates); + this.selector = selector; + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.pitch = pitch; + this.yaw = yaw; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { + it.teleport(new WorldPosition( + Optional.ofNullable(this.world).map(w -> w.get(ctx)).map(w -> CraftEngine.instance().platform().getWorld(w)).orElse(it.world()), + this.x.getDouble(ctx), + this.y.getDouble(ctx), + this.z.getDouble(ctx), + this.pitch.getFloat(ctx), + this.yaw.getFloat(ctx)) + ); + }); + } else { + for (Player viewer : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + viewer.teleport(new WorldPosition( + Optional.ofNullable(this.world).map(w -> w.get(relationalContext)).map(w -> CraftEngine.instance().platform().getWorld(w)).orElse(viewer.world()), + this.x.getDouble(relationalContext), + this.y.getDouble(relationalContext), + this.z.getDouble(relationalContext), + this.pitch.getFloat(relationalContext), + this.yaw.getFloat(relationalContext)) + ); + } + } + } + + @Override + public Key type() { + return CommonFunctions.TELEPORT; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + TextProvider world = Optional.ofNullable(arguments.get("world")).map(String::valueOf).map(TextProviders::fromString).orElse(null); + NumberProvider x = NumberProviders.fromObject(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("x"), "warning.config.function.teleport.missing_x")); + NumberProvider y = NumberProviders.fromObject(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("y"), "warning.config.function.teleport.missing_y")); + NumberProvider z = NumberProviders.fromObject(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("z"), "warning.config.function.teleport.missing_z")); + NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", 0)); + NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", 0)); + return new TeleportFunction<>( + getPredicates(arguments), + PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), + world, x, y, z, pitch, yaw + ); + } + } +} From e00734d9abe443c25b4d4ab75a999dd7b3d3f88d Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 04:15:45 +0800 Subject: [PATCH 079/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mythicmobs/MythicItemDrop.java | 2 +- .../skript/expression/ExprCustomItem.java | 3 +- .../advancement/BukkitAdvancementManager.java | 76 ++++++- .../behavior/ConcretePowderBlockBehavior.java | 2 +- .../block/behavior/LeavesBlockBehavior.java | 2 +- .../ItemDisplayBlockEntityElement.java | 19 +- .../ItemDisplayBlockEntityElementConfig.java | 28 +-- .../bukkit/item/BukkitItemManager.java | 3 +- .../item/recipe/BukkitRecipeManager.java | 4 +- .../item/recipe/RecipeEventListener.java | 11 +- .../bukkit/pack/BukkitPackManager.java | 2 +- .../feature/TotemAnimationCommand.java | 2 +- .../handler/ProjectilePacketHandler.java | 2 +- .../reflection/minecraft/CoreReflections.java | 188 +++++++++++++++++- .../minecraft/NetworkReflections.java | 6 + .../plugin/user/BukkitServerPlayer.java | 6 + .../src/main/resources/translations/en.yml | 12 +- .../AbstractAdvancementManager.java | 2 + .../core/advancement/AdvancementManager.java | 5 + .../element/BlockEntityElementConfigs.java | 3 +- .../core/entity/player/Player.java | 4 +- .../craftengine/core/item/BuildableItem.java | 9 +- .../core/item/ItemBuildContext.java | 16 +- .../core/loot/AbstractVanillaLootManager.java | 2 +- .../core/pack/AbstractPackManager.java | 2 +- .../core/pack/conflict/PathContext.java | 2 +- .../craftengine/core/plugin/CraftEngine.java | 1 - .../core/plugin/context/ContextHolder.java | 28 ++- .../plugin/context/PlayerOptionalContext.java | 14 +- .../plugin/context/event/EventFunctions.java | 2 + .../context/function/ActionBarFunction.java | 2 +- .../context/function/CommandFunction.java | 2 +- .../context/function/CommonFunctions.java | 1 + .../context/function/LevelerExpFunction.java | 2 +- .../context/function/MessageFunction.java | 2 +- .../context/function/OpenWindowFunction.java | 2 +- .../function/PotionEffectFunction.java | 2 +- .../context/function/SetCooldownFunction.java | 2 +- .../context/function/SetFoodFunction.java | 2 +- .../function/SetSaturationFunction.java | 2 +- .../context/function/SetVariableFunction.java | 72 +++++++ .../context/function/TeleportFunction.java | 27 +-- .../context/function/TitleFunction.java | 2 +- .../context/function/ToastFunction.java | 87 ++++++++ .../gui/category/ItemBrowserManagerImpl.java | 12 +- .../core/util/ReflectionUtils.java | 16 ++ gradle.properties | 4 +- 47 files changed, 590 insertions(+), 107 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetVariableFunction.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java index 634f341c2..dbb2f00db 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java @@ -33,7 +33,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop { @Override public AbstractItemStack getDrop(DropMetadata dropMetadata, double amount) { - ItemBuildContext context = ItemBuildContext.EMPTY; + ItemBuildContext context = ItemBuildContext.empty(); SkillCaster caster = dropMetadata.getCaster(); if (caster != null && caster.getEntity() instanceof AbstractPlayer abstractPlayer) { Entity bukkitEntity = abstractPlayer.getBukkitEntity(); diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprCustomItem.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprCustomItem.java index 6ad45ad4c..ff22863b6 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprCustomItem.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprCustomItem.java @@ -33,7 +33,6 @@ public class ExprCustomItem extends SimpleExpression { private Expression itemIds; @Override - @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { itemIds = exprs[0]; return true; @@ -49,7 +48,7 @@ public class ExprCustomItem extends SimpleExpression { if (object instanceof String string) { CustomItem customItem = CraftEngineItems.byId(Key.of(string)); if (customItem != null) { - ItemType itemType = new ItemType(customItem.buildItemStack(ItemBuildContext.EMPTY)); + ItemType itemType = new ItemType(customItem.buildItemStack(ItemBuildContext.empty())); items.add(itemType); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java index 38ead6b3f..c037f46c3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java @@ -1,17 +1,25 @@ package net.momirealms.craftengine.bukkit.advancement; import com.google.gson.JsonElement; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.advancement.AbstractAdvancementManager; +import net.momirealms.craftengine.core.advancement.AdvancementType; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.VersionHelper; import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class BukkitAdvancementManager extends AbstractAdvancementManager { private final BukkitCraftEngine plugin; @@ -33,6 +41,70 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager { return this.advancementParser; } + @Override + public void sendToast(Player player, Item icon, Component message, AdvancementType type) { + try { + Object displayInfo = CoreReflections.constructor$DisplayInfo.newInstance( + icon.getLiteralObject(), + ComponentUtils.adventureToMinecraft(message), // title + CoreReflections.instance$Component$empty, // description + VersionHelper.isOrAbove1_20_3() ? Optional.empty() : null, // background + CoreReflections.instance$AdvancementType$values[type.ordinal()], + true, // show toast + false, // announce to chat + true // hidden + ); + if (VersionHelper.isOrAbove1_20_2()) { + displayInfo = Optional.of(displayInfo); + } + Object resourceLocation = KeyUtils.toResourceLocation(Key.of("craftengine", "toast")); + Object criterion = VersionHelper.isOrAbove1_20_2() ? + CoreReflections.constructor$Criterion.newInstance(CoreReflections.constructor$ImpossibleTrigger.newInstance(), CoreReflections.constructor$ImpossibleTrigger$TriggerInstance.newInstance()) : + CoreReflections.constructor$Criterion.newInstance(CoreReflections.constructor$ImpossibleTrigger$TriggerInstance.newInstance()); + Map criteria = Map.of("impossible", criterion); + Object advancementProgress = CoreReflections.constructor$AdvancementProgress.newInstance(); + Object advancement; + if (VersionHelper.isOrAbove1_20_2()) { + Object advancementRequirements = VersionHelper.isOrAbove1_20_3() ? + CoreReflections.constructor$AdvancementRequirements.newInstance(List.of(List.of("impossible"))) : + CoreReflections.constructor$AdvancementRequirements.newInstance((Object) new String[][] {{"impossible"}}); + advancement = CoreReflections.constructor$Advancement.newInstance( + Optional.empty(), + displayInfo, + CoreReflections.instance$AdvancementRewards$EMPTY, + criteria, + advancementRequirements, + false + ); + CoreReflections.method$AdvancementProgress$update.invoke(advancementProgress, advancementRequirements); + advancement = CoreReflections.constructor$AdvancementHolder.newInstance(resourceLocation, advancement); + } else { + advancement = CoreReflections.constructor$Advancement.newInstance( + resourceLocation, + null, // parent + displayInfo, + CoreReflections.instance$AdvancementRewards$EMPTY, + criteria, + new String[][] {{"impossible"}}, + false + ); + CoreReflections.method$AdvancementProgress$update.invoke(advancementProgress, criteria, new String[][] {{"impossible"}}); + } + CoreReflections.method$AdvancementProgress$grantProgress.invoke(advancementProgress, "impossible"); + Map advancementsToGrant = new HashMap<>(); + advancementsToGrant.put(resourceLocation, advancementProgress); + Object grantPacket = VersionHelper.isOrAbove1_21_5() ? + NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, Arrays.asList(advancement), new HashSet<>(), advancementsToGrant, true) : + NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, Arrays.asList(advancement), new HashSet<>(), advancementsToGrant); + Object removePacket = VersionHelper.isOrAbove1_21_5() ? + NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), new HashSet<>() {{add(resourceLocation);}}, new HashMap<>(), true) : + NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), new HashSet<>() {{add(resourceLocation);}}, new HashMap<>()); + player.sendPackets(List.of(grantPacket, removePacket), false); + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Failed to send toast for player " + player.name(), e); + } + } + public class AdvancementParser implements ConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"advancements", "advancement"}; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java index aa872018e..d932532cf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java @@ -129,7 +129,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { Object mutablePos = CoreReflections.method$BlockPos$mutable.invoke(pos); int j = Direction.values().length; for (int k = 0; k < j; k++) { - Object direction = CoreReflections.instance$Directions[k]; + Object direction = CoreReflections.instance$Direction$values[k]; Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, mutablePos); if (direction != CoreReflections.instance$Direction$DOWN || canSolidify(blockState)) { CoreReflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, pos, direction); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java index c863c19a6..85924d053 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java @@ -132,7 +132,7 @@ public class LeavesBlockBehavior extends BukkitBlockBehavior { Object mutablePos = CoreReflections.constructor$MutableBlockPos.newInstance(); int j = Direction.values().length; for (int k = 0; k < j; ++k) { - Object direction = CoreReflections.instance$Directions[k]; + Object direction = CoreReflections.instance$Direction$values[k]; CoreReflections.method$MutableBlockPos$setWithOffset.invoke(mutablePos, blockPos, direction); Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, mutablePos); i = Math.min(i, getDistanceAt(blockState) + 1); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java index b195e328d..f60666842 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java @@ -13,22 +13,21 @@ import java.util.List; import java.util.UUID; public class ItemDisplayBlockEntityElement implements BlockEntityElement { + private final ItemDisplayBlockEntityElementConfig config; private final Object cachedSpawnPacket; private final Object cachedDespawnPacket; + private final int entityId; public ItemDisplayBlockEntityElement(ItemDisplayBlockEntityElementConfig config, BlockPos pos) { int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); Vector3f position = config.position(); - this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of( - FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( - entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, - config.xRot(), config.yRot(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 - ), - FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket( - entityId, config.metadataValues().get() - ) - )); + this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, + config.xRot(), config.yRot(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 + ); + this.config = config; this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId)); + this.entityId = entityId; } @Override @@ -38,7 +37,7 @@ public class ItemDisplayBlockEntityElement implements BlockEntityElement { @Override public void spawn(Player player) { - player.sendPacket(this.cachedSpawnPacket, true); + player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), true); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java index 35d53f551..b70d1136a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java @@ -7,9 +7,9 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.LazyReference; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import org.joml.Quaternionf; @@ -19,11 +19,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementConfig { public static final Factory FACTORY = new Factory(); - private final LazyReference> lazyMetadataPacket; - private final LazyReference> item; + private final Function> lazyMetadataPacket; + private final Function> item; private final Vector3f scale; private final Vector3f position; private final Vector3f translation; @@ -33,7 +34,7 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo private final ItemDisplayContext displayContext; private final Billboard billboard; - public ItemDisplayBlockEntityElementConfig(LazyReference> item, + public ItemDisplayBlockEntityElementConfig(Function> item, Vector3f scale, Vector3f position, Vector3f translation, @@ -51,16 +52,16 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo this.rotation = rotation; this.displayContext = displayContext; this.billboard = billboard; - this.lazyMetadataPacket = LazyReference.lazyReference(() -> { + this.lazyMetadataPacket = player -> { List dataValues = new ArrayList<>(); - ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.get().getLiteralObject(), dataValues); + ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues); ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues); return dataValues; - }); + }; } @Override @@ -68,8 +69,8 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo return new ItemDisplayBlockEntityElement(this, pos); } - public Item item() { - return this.item.get(); + public Item item(Player player) { + return this.item.apply(player); } public Vector3f scale() { @@ -104,8 +105,8 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo return rotation; } - public LazyReference> metadataValues() { - return this.lazyMetadataPacket; + public List metadataValues(Player player) { + return this.lazyMetadataPacket.apply(player); } public static class Factory implements BlockEntityElementConfigFactory { @@ -113,10 +114,9 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo @SuppressWarnings("unchecked") @Override public BlockEntityElementConfig create(Map arguments) { - // todo item should not be null - Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "")); + Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item")); return (BlockEntityElementConfig) new ItemDisplayBlockEntityElementConfig( - LazyReference.lazyReference(() -> BukkitItemManager.instance().createWrappedItem(itemId, null)), + player -> BukkitItemManager.instance().createWrappedItem(itemId, player), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index c21500bb8..2318e20a9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -21,7 +21,6 @@ import net.momirealms.craftengine.core.item.recipe.DatapackRecipeResult; import net.momirealms.craftengine.core.item.recipe.UniqueIdItem; import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.util.*; @@ -335,7 +334,7 @@ public class BukkitItemManager extends AbstractItemManager { @Override public ItemStack buildCustomItemStack(Key id, Player player) { - return Optional.ofNullable(this.customItemsById.get(id)).map(it -> it.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1)).orElse(null); + return Optional.ofNullable(this.customItemsById.get(id)).map(it -> it.buildItemStack(ItemBuildContext.of(player), 1)).orElse(null); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index 11cd5b5a1..b738734a3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -200,7 +200,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { for (UniqueKey holder : holders) { Optional> buildableItem = BukkitItemManager.instance().getBuildableItem(holder.key()); if (buildableItem.isPresent()) { - ItemStack itemStack = buildableItem.get().buildItemStack(ItemBuildContext.EMPTY, 1); + ItemStack itemStack = buildableItem.get().buildItemStack(ItemBuildContext.empty(), 1); Object nmsStack = FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack); itemStacks.add(nmsStack); } else { @@ -336,7 +336,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { if (recipe instanceof CustomBrewingRecipe brewingRecipe) { if (!VersionHelper.isOrAbove1_20_2()) return; PotionMix potionMix = new PotionMix(new NamespacedKey(id.namespace(), id.value()), - brewingRecipe.result(ItemBuildContext.EMPTY), + brewingRecipe.result(ItemBuildContext.empty()), PotionMix.createPredicateChoice(container -> { Item wrapped = this.plugin.itemManager().wrap(container); return brewingRecipe.container().test(UniqueIdItem.of(wrapped)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index ef458eacb..59894f3e4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -23,7 +23,6 @@ import net.momirealms.craftengine.core.item.recipe.input.SmithingInput; import net.momirealms.craftengine.core.item.setting.AnvilRepairItem; import net.momirealms.craftengine.core.item.setting.ItemEquipment; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.util.*; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -614,9 +613,9 @@ public class RecipeEventListener implements Listener { Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); if (craftingTableRecipe.hasVisualResult()) { - inventory.setResult(craftingTableRecipe.assembleVisual(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY))); + inventory.setResult(craftingTableRecipe.assembleVisual(input, ItemBuildContext.of(serverPlayer))); } else { - inventory.setResult(craftingTableRecipe.assemble(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY))); + inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); } } @@ -642,7 +641,7 @@ public class RecipeEventListener implements Listener { if (input == null) return; Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); - inventory.setResult(craftingTableRecipe.assemble(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY))); + inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); } private CraftingInput getCraftingInput(CraftingInventory inventory) { @@ -694,7 +693,7 @@ public class RecipeEventListener implements Listener { SmithingInput input = getSmithingInput(inventory); if (smithingTrimRecipe.matches(input)) { Player player = InventoryUtils.getPlayerFromInventoryEvent(event); - ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), new ItemBuildContext(BukkitAdaptors.adapt(player), ContextHolder.EMPTY)); + ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), ItemBuildContext.of(BukkitAdaptors.adapt(player))); event.setResult(result); } else { event.setResult(null); @@ -718,7 +717,7 @@ public class RecipeEventListener implements Listener { SmithingInput input = getSmithingInput(inventory); if (smithingTransformRecipe.matches(input)) { Player player = InventoryUtils.getPlayerFromInventoryEvent(event); - ItemStack processed = smithingTransformRecipe.assemble(input, new ItemBuildContext(BukkitAdaptors.adapt(player), ContextHolder.EMPTY)); + ItemStack processed = smithingTransformRecipe.assemble(input, ItemBuildContext.of(BukkitAdaptors.adapt(player))); event.setResult(processed); } else { event.setResult(null); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 6acebe428..a8513327a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -90,7 +90,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { return; } if (!Config.sendPackOnUpload()) return; - CraftEngine.instance().logger().info("Complete uploading resource pack"); + CraftEngine.instance().logger().info("Completed uploading resource pack"); for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) { sendResourcePack(player); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java index 9584a7fd1..7001785f0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java @@ -61,7 +61,7 @@ public class TotemAnimationCommand extends BukkitCommandFeature { handleFeedback(context, MessageConstants.COMMAND_TOTEM_NOT_TOTEM, Component.text(key.toString())); return; } - Item item = customItem.buildItem(ItemBuildContext.EMPTY); + Item item = customItem.buildItem(ItemBuildContext.empty()); if (VersionHelper.isOrAbove1_21_2()) { if (context.flags().contains("sound_location")) { String soundResourceLocation = context.flags().getValue("sound_location").get().toString(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java index 5c7d29b86..6e697af25 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java @@ -112,7 +112,7 @@ public class ProjectilePacketHandler implements EntityPacketHandler { Optional> customItem = BukkitItemManager.instance().getCustomItem(this.projectile.metadata().item()); if (customItem.isEmpty()) return itemDisplayValues; ProjectileMeta meta = this.projectile.metadata(); - Item displayedItem = customItem.get().buildItem(ItemBuildContext.EMPTY); + Item displayedItem = customItem.get().buildItem(ItemBuildContext.empty()); // 我们应当使用新的展示物品的组件覆盖原物品的组件,以完成附魔,附魔光效等组件的继承 displayedItem = this.projectile.item().mergeCopy(displayedItem); ItemDisplayEntityData.InterpolationDelay.addEntityDataIfNotDefaultValue(-1, itemDisplayValues); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 79cd15c2d..48eb0072e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -469,17 +469,17 @@ public final class CoreReflections { public static final Object instance$Direction$SOUTH; public static final Object instance$Direction$WEST; public static final Object instance$Direction$EAST; - public static final Object[] instance$Directions; + public static final Object[] instance$Direction$values; static { try { - instance$Directions = (Object[]) method$Direction$values.invoke(null); - instance$Direction$DOWN = instance$Directions[0]; - instance$Direction$UP = instance$Directions[1]; - instance$Direction$NORTH = instance$Directions[2]; - instance$Direction$SOUTH = instance$Directions[3]; - instance$Direction$WEST = instance$Directions[4]; - instance$Direction$EAST = instance$Directions[5]; + instance$Direction$values = (Object[]) method$Direction$values.invoke(null); + instance$Direction$DOWN = instance$Direction$values[0]; + instance$Direction$UP = instance$Direction$values[1]; + instance$Direction$NORTH = instance$Direction$values[2]; + instance$Direction$SOUTH = instance$Direction$values[3]; + instance$Direction$WEST = instance$Direction$values[4]; + instance$Direction$EAST = instance$Direction$values[5]; } catch (ReflectiveOperationException e) { throw new ReflectionInitException("Failed to init Direction", e); } @@ -4217,4 +4217,176 @@ public final class CoreReflections { public static final Method method$Block$updateEntityMovementAfterFallOn = requireNonNull( ReflectionUtils.getDeclaredMethod(clazz$Block, void.class, clazz$BlockGetter, clazz$Entity) ); + + public static final Class clazz$AdvancementRewards = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.AdvancementRewards", + "advancements.AdvancementRewards" + ) + ); + + public static final Field field$AdvancementRewards$EMPTY = requireNonNull( + ReflectionUtils.getStaticDeclaredField(clazz$AdvancementRewards, clazz$AdvancementRewards, 0) + ); + + public static final Object instance$AdvancementRewards$EMPTY; + + static { + try { + instance$AdvancementRewards$EMPTY = field$AdvancementRewards$EMPTY.get(null); + } catch (ReflectiveOperationException e) { + throw new ReflectionInitException("Failed to initialize AdvancementRewards$EMPTY", e); + } + } + + public static final Class clazz$AdvancementRequirements = MiscUtils.requireNonNullIf( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.AdvancementRequirements", + "advancements.AdvancementRequirements" + ), VersionHelper.isOrAbove1_20_2() + ); + + public static final Constructor constructor$AdvancementRequirements = Optional.ofNullable(clazz$AdvancementRequirements) + .map(it -> { + if (VersionHelper.isOrAbove1_20_3()) { + return ReflectionUtils.getConstructor(it, List.class); + } else { + return ReflectionUtils.getConstructor(it, String[][].class); + } + }).orElse(null); + + public static final Class clazz$AdvancementProgress = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.AdvancementProgress", + "advancements.AdvancementProgress" + ) + ); + + public static final Constructor constructor$AdvancementProgress = requireNonNull( + ReflectionUtils.getConstructor(clazz$AdvancementProgress) + ); + + public static final Method method$AdvancementProgress$update = requireNonNull( + VersionHelper.isOrAbove1_20_2() ? + ReflectionUtils.getMethod(clazz$AdvancementProgress, void.class, clazz$AdvancementRequirements) : + ReflectionUtils.getMethod(clazz$AdvancementProgress, void.class, Map.class, String[][].class) + ); + + public static final Class clazz$AdvancementType = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.AdvancementFrameType", + VersionHelper.isOrAbove1_20_3() ? "advancements.AdvancementType" : "advancements.FrameType" + ) + ); + + public static final Method method$AdvancementType$values = requireNonNull( + ReflectionUtils.getStaticMethod(clazz$AdvancementType, clazz$AdvancementType.arrayType()) + ); + + public static final Object[] instance$AdvancementType$values; + + static { + try { + instance$AdvancementType$values = (Object[]) method$AdvancementType$values.invoke(null); + } catch (ReflectiveOperationException e) { + throw new ReflectionInitException("Failed to initialize AdvancementTypes", e); + } + } + + public static final Class clazz$DisplayInfo = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.AdvancementDisplay", + "advancements.DisplayInfo" + ) + ); + + public static final Constructor constructor$DisplayInfo = requireNonNull( + VersionHelper.isOrAbove1_20_3() ? + ReflectionUtils.getConstructor(clazz$DisplayInfo, clazz$ItemStack, clazz$Component, clazz$Component, Optional.class, clazz$AdvancementType, boolean.class, boolean.class, boolean.class) : + ReflectionUtils.getConstructor(clazz$DisplayInfo, clazz$ItemStack, clazz$Component, clazz$Component, clazz$ResourceLocation, clazz$AdvancementType, boolean.class, boolean.class, boolean.class) + ); + + public static final Class clazz$Criterion = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.Criterion", + "advancements.Criterion" + ) + ); + + public static final Class clazz$CriterionTrigger = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.CriterionTrigger", + "advancements.CriterionTrigger" + ) + ); + + public static final Class clazz$CriterionTriggerInstance = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.CriterionInstance", + "advancements.CriterionTriggerInstance" + ) + ); + + public static final Class clazz$ImpossibleTrigger = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.critereon.CriterionTriggerImpossible", + "advancements.critereon.ImpossibleTrigger" + ) + ); + + public static final Constructor constructor$ImpossibleTrigger = requireNonNull( + ReflectionUtils.getConstructor(clazz$ImpossibleTrigger) + ); + + public static final Class clazz$ImpossibleTrigger$TriggerInstance = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.critereon.CriterionTriggerImpossible$a", + "advancements.critereon.ImpossibleTrigger$TriggerInstance" + ) + ); + + public static final Constructor constructor$Criterion = requireNonNull( + VersionHelper.isOrAbove1_20_2() ? + ReflectionUtils.getConstructor(clazz$Criterion, clazz$CriterionTrigger, clazz$CriterionTriggerInstance) : + ReflectionUtils.getConstructor(clazz$Criterion, clazz$CriterionTriggerInstance) + ); + + public static final Constructor constructor$ImpossibleTrigger$TriggerInstance = requireNonNull( + ReflectionUtils.getConstructor(clazz$ImpossibleTrigger$TriggerInstance) + ); + + public static final Class clazz$Advancement = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.Advancement", + "advancements.Advancement" + ) + ); + + public static final Constructor constructor$Advancement = requireNonNull( + VersionHelper.isOrAbove1_20_2() ? + ReflectionUtils.getConstructor(clazz$Advancement, Optional.class, Optional.class, clazz$AdvancementRewards, Map.class, clazz$AdvancementRequirements, boolean.class) : + ReflectionUtils.getConstructor(clazz$Advancement, clazz$ResourceLocation, clazz$Advancement, clazz$DisplayInfo, clazz$AdvancementRewards, Map.class, String[][].class, boolean.class) + ); + + public static final Class clazz$CriterionProgress = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.CriterionProgress", + "advancements.CriterionProgress" + ) + ); + + public static final Method method$AdvancementProgress$grantProgress = requireNonNull( + ReflectionUtils.getMethod(clazz$AdvancementProgress, boolean.class, new String[]{"grantProgress", "a"}, String.class) + ); + + public static final Class clazz$AdvancementHolder = MiscUtils.requireNonNullIf( + BukkitReflectionUtils.findReobfOrMojmapClass( + "advancements.AdvancementHolder", + "advancements.AdvancementHolder" + ), VersionHelper.isOrAbove1_20_2() + ); + + public static final Constructor constructor$AdvancementHolder = Optional.ofNullable(clazz$AdvancementHolder) + .map(it -> ReflectionUtils.getConstructor(it, clazz$ResourceLocation, clazz$Advancement)) + .orElse(null); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java index f97ae5164..efbcc589d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java @@ -1671,4 +1671,10 @@ public final class NetworkReflections { "network.protocol.game.ClientboundForgetLevelChunkPacket" ) ); + + public static final Constructor constructor$ClientboundUpdateAdvancementsPacket = requireNonNull( + VersionHelper.isOrAbove1_21_5() ? + ReflectionUtils.getConstructor(clazz$ClientboundUpdateAdvancementsPacket, boolean.class, Collection.class, Set.class, Map.class, boolean.class) : + ReflectionUtils.getConstructor(clazz$ClientboundUpdateAdvancementsPacket, boolean.class, Collection.class, Set.class, Map.class) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 5b9512f15..dbdbebfeb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -20,6 +20,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MMobEffects import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.bukkit.world.BukkitWorld; +import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; @@ -235,6 +236,11 @@ public class BukkitServerPlayer extends Player { return AdventureModeUtils.canPlace(platformPlayer().getInventory().getItemInMainHand(), new Location(platformPlayer().getWorld(), pos.x(), pos.y(), pos.z()), state); } + @Override + public void sendToast(Component text, Item icon, AdvancementType type) { + this.plugin.advancementManager().sendToast(this, icon, text, type); + } + @Override public void sendActionBar(Component text) { Object packet = FastNMS.INSTANCE.constructor$ClientboundActionBarPacket(ComponentUtils.adventureToMinecraft(text)); diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 2b6d0705d..42e20d0f1 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -261,6 +261,8 @@ warning.config.block.state.missing_state: "Issue found in file - warning.config.block.state.missing_properties: "Issue found in file - The block '' is missing the required 'properties' section for 'states'." warning.config.block.state.missing_appearances: "Issue found in file - The block '' is missing the required 'appearances' section for 'states'." warning.config.block.state.missing_variants: "Issue found in file - The block '' is missing the required 'variants' section for 'states'." +warning.config.block.state.entity_renderer.invalid_type: "Issue found in file - The block '' is using an invalid entity renderer type ''." +warning.config.block.state.entity_renderer.item_display.missing_item: "Issue found in file - The block '' is missing the required 'item' argument for 'item_display' entity renderer." warning.config.block.state.variant.missing_appearance: "Issue found in file - The block '' is missing the required 'appearance' argument for variant ''." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." @@ -392,7 +394,7 @@ warning.config.function.command.missing_command: "Issue found in file Issue found in file - The config '' is missing the required 'actionbar' argument for 'actionbar' function." warning.config.function.message.missing_message: "Issue found in file - The config '' is missing the required 'message' argument for 'message' function." warning.config.function.open_window.missing_gui_type: "Issue found in file - The config '' is missing the required 'gui-type' argument for 'open_window' function." -warning.config.function.open_window.invalid_gui_type: "Issue found in file - The config '' is using an invalid gui type for 'open_window' function. Allowed types: []." +warning.config.function.open_window.invalid_gui_type: "Issue found in file - The config '' is using an invalid gui type '' for 'open_window' function. Allowed types: []." warning.config.function.run.missing_functions: "Issue found in file - The config '' is missing the required 'functions' argument for 'run' function." warning.config.function.place_block.missing_block_state: "Issue found in file - The config '' is missing the required 'block-state' argument for 'place_block' function." warning.config.function.set_food.missing_food: "Issue found in file - The config '' is missing the required 'food' argument for 'set_food' function." @@ -415,6 +417,14 @@ warning.config.function.remove_cooldown.missing_id: "Issue found in file warning.config.function.mythic_mobs_skill.missing_skill: "Issue found in file - The config '' is missing the required 'skill' argument for 'mythic_mobs_skill' function." warning.config.function.spawn_furniture.missing_furniture_id: "Issue found in file - The config '' is missing the required 'furniture-id' argument for 'spawn_furniture' function." warning.config.function.replace_furniture.missing_furniture_id: "Issue found in file - The config '' is missing the required 'furniture-id' argument for 'replace_furniture' function." +warning.config.function.teleport.missing_x: "Issue found in file - The config '' is missing the required 'x' argument for 'teleport' function." +warning.config.function.teleport.missing_y: "Issue found in file - The config '' is missing the required 'y' argument for 'teleport' function." +warning.config.function.teleport.missing_z: "Issue found in file - The config '' is missing the required 'z' argument for 'teleport' function." +warning.config.function.set_variable.missing_name: "Issue found in file - The config '' is missing the required 'name' argument for 'set_variable' function." +warning.config.function.set_variable.missing_value: "Issue found in file - The config '' is missing the required 'number' or 'text' argument for 'set_variable' function." +warning.config.function.toast.missing_toast: "Issue found in file - The config '' is missing the required 'toast' argument for 'toast' function." +warning.config.function.toast.missing_icon: "Issue found in file - The config '' is missing the required 'icon' argument for 'toast' function." +warning.config.function.toast.invalid_advancement_type: "Issue found in file - The config '' is using an invalid advancement type '' for 'toast' function. Allowed types: []." warning.config.selector.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for selector." warning.config.selector.invalid_type: "Issue found in file - The config '' is using an invalid selector type ''." warning.config.selector.invalid_target: "Issue found in file - The config '' is using an invalid selector target ''." diff --git a/core/src/main/java/net/momirealms/craftengine/core/advancement/AbstractAdvancementManager.java b/core/src/main/java/net/momirealms/craftengine/core/advancement/AbstractAdvancementManager.java index 8af17d774..43711d2f9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/advancement/AbstractAdvancementManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/advancement/AbstractAdvancementManager.java @@ -8,4 +8,6 @@ public abstract class AbstractAdvancementManager implements AdvancementManager { public AbstractAdvancementManager(CraftEngine plugin) { this.plugin = plugin; } + + } diff --git a/core/src/main/java/net/momirealms/craftengine/core/advancement/AdvancementManager.java b/core/src/main/java/net/momirealms/craftengine/core/advancement/AdvancementManager.java index b4a13703b..f7d664bdf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/advancement/AdvancementManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/advancement/AdvancementManager.java @@ -1,9 +1,14 @@ package net.momirealms.craftengine.core.advancement; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; public interface AdvancementManager extends Manageable { ConfigParser parser(); + + void sendToast(Player player, Item icon, Component message, AdvancementType type); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java index 6ea5d16ad..70338ebee 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java @@ -23,8 +23,7 @@ public class BlockEntityElementConfigs { Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(ITEM_DISPLAY); BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type); if (factory == null) { - // todo 发送消息 - throw new LocalizedResourceConfigException("", type.toString()); + throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString()); } return factory.create(arguments); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 3ed4302fc..9a33c2dbc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.entity.player; import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.CooldownData; @@ -8,7 +9,6 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Position; import net.momirealms.craftengine.core.world.WorldPosition; import org.jetbrains.annotations.NotNull; @@ -64,6 +64,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract boolean canPlace(BlockPos pos, Object state); + public abstract void sendToast(Component text, Item icon, AdvancementType type); + public abstract void sendActionBar(Component text); public abstract void sendMessage(Component text, boolean overlay); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/BuildableItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/BuildableItem.java index 4e996c007..3b2798539 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/BuildableItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/BuildableItem.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.util.Key; public interface BuildableItem { @@ -27,18 +26,18 @@ public interface BuildableItem { } default I buildItemStack() { - return buildItemStack(ItemBuildContext.EMPTY, 1); + return buildItemStack(ItemBuildContext.empty(), 1); } default I buildItemStack(int count) { - return buildItemStack(ItemBuildContext.EMPTY, count); + return buildItemStack(ItemBuildContext.empty(), count); } default I buildItemStack(Player player) { - return this.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1); + return this.buildItemStack(ItemBuildContext.of(player), 1); } default I buildItemStack(Player player, int count) { - return this.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), count); + return this.buildItemStack(ItemBuildContext.of(player), count); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java index a8b7dd219..1a9009dd6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.item; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; @@ -10,12 +11,23 @@ import org.jetbrains.annotations.Nullable; import java.util.Map; public class ItemBuildContext extends PlayerOptionalContext { - public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.EMPTY); + + /** + * Use {@link #empty()} instead + */ + @Deprecated(since = "0.0.63", forRemoval = true) + public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.empty()); + public static final TagResolver[] EMPTY_RESOLVERS = empty().tagResolvers(); public ItemBuildContext(@Nullable Player player, @NotNull ContextHolder contexts) { super(player, contexts); } + @NotNull + public static ItemBuildContext empty() { + return new ItemBuildContext(null, ContextHolder.empty()); + } + @NotNull public static ItemBuildContext of(@Nullable Player player, @NotNull ContextHolder contexts) { return new ItemBuildContext(player, contexts); @@ -29,7 +41,7 @@ public class ItemBuildContext extends PlayerOptionalContext { @NotNull public static ItemBuildContext of(@Nullable Player player) { - if (player == null) return new ItemBuildContext(null, ContextHolder.EMPTY); + if (player == null) return new ItemBuildContext(null, ContextHolder.empty()); return new ItemBuildContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player))); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/AbstractVanillaLootManager.java b/core/src/main/java/net/momirealms/craftengine/core/loot/AbstractVanillaLootManager.java index fbacf55cb..80b937630 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/AbstractVanillaLootManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/AbstractVanillaLootManager.java @@ -8,7 +8,7 @@ import java.util.Optional; public abstract class AbstractVanillaLootManager implements VanillaLootManager { protected final Map blockLoots = new HashMap<>(); - // TODO More entity NBT + // TODO 实现一个基于entity data的生物战利品系统 protected final Map entityLoots = new HashMap<>(); public AbstractVanillaLootManager() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 2ce619894..ece216966 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -467,7 +467,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_1.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_2.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/pebble_3.json"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_straight.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sleeper_sofa.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/PathContext.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/PathContext.java index 55bdce4db..d8aaefc24 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/PathContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/PathContext.java @@ -18,6 +18,6 @@ public class PathContext extends AbstractCommonContext { } public static PathContext of(Path path) { - return new PathContext(ContextHolder.EMPTY, path); + return new PathContext(ContextHolder.empty(), path); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index b2759fd4b..276dc3ee8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -35,7 +35,6 @@ import net.momirealms.craftengine.core.plugin.logger.filter.LogFilter; import net.momirealms.craftengine.core.plugin.network.NetworkManager; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.sound.SoundManager; -import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Logger; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java index 5e54f2ea8..59d678797 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.plugin.context; import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; @@ -10,11 +11,32 @@ import java.util.Optional; import java.util.function.Supplier; public class ContextHolder { + /** + * Use {@link #empty()} instead + */ + @Deprecated(forRemoval = true, since = "0.0.63") public static final ContextHolder EMPTY = ContextHolder.builder().immutable(true).build(); + protected final Map, Supplier> params; + private final boolean immutable; + + public ContextHolder(Map, Supplier> params, boolean immutable) { + this.params = immutable ? ImmutableMap.copyOf(params) : new HashMap<>(params); + this.immutable = immutable; + } public ContextHolder(Map, Supplier> params) { - this.params = params; + this.params = new HashMap<>(params); + this.immutable = true; + } + + @NotNull + public static ContextHolder empty() { + return ContextHolder.builder().build(); + } + + public boolean immutable() { + return this.immutable; } public boolean has(ContextKey key) { @@ -67,7 +89,7 @@ public class ContextHolder { } public static class Builder { - private final Map, Supplier> params = new HashMap<>(); + private final Map, Supplier> params = new HashMap<>(8); private boolean immutable = false; public Builder() {} @@ -113,7 +135,7 @@ public class ContextHolder { } public ContextHolder build() { - return new ContextHolder(this.immutable ? ImmutableMap.copyOf(this.params) : this.params); + return new ContextHolder(this.params, this.immutable); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java index f2a39186c..e38a1668d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/PlayerOptionalContext.java @@ -11,7 +11,12 @@ import java.util.List; import java.util.Map; public class PlayerOptionalContext extends AbstractChainParameterContext implements PlayerContext { - public static final PlayerOptionalContext EMPTY = new PlayerOptionalContext(null, ContextHolder.EMPTY); + /** + * Use {@link #empty()} instead + */ + @Deprecated(forRemoval = true) + public static final PlayerOptionalContext EMPTY = new PlayerOptionalContext(null, ContextHolder.empty()); + protected final Player player; public PlayerOptionalContext(@Nullable Player player, @@ -40,10 +45,15 @@ public class PlayerOptionalContext extends AbstractChainParameterContext impleme @NotNull public static PlayerOptionalContext of(@Nullable Player player) { - if (player == null) return EMPTY; + if (player == null) return empty(); return new PlayerOptionalContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player))); } + @NotNull + public static PlayerOptionalContext empty() { + return new PlayerOptionalContext(null, ContextHolder.empty()); + } + @Override @Nullable public Player player() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java index bfde55253..8c36f5af6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java @@ -43,6 +43,8 @@ public class EventFunctions { register(CommonFunctions.REPLACE_FURNITURE, new ReplaceFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.MYTHIC_MOBS_SKILL, new MythicMobsSkillFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.TELEPORT, new TeleportFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_VARIABLE, new SetVariableFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.TOAST, new ToastFunction.FactoryImpl<>(EventConditions::fromMap)); } public static void register(Key key, FunctionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java index 9f3781528..86aeebfa5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java @@ -33,7 +33,7 @@ public class ActionBarFunction extends AbstractConditionalF }); } else { for (Player viewer : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); viewer.sendActionBar(AdventureHelper.miniMessage().deserialize(this.message.get(relationalContext), relationalContext.tagResolvers())); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java index 2447d64ff..fc6c360fa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java @@ -45,7 +45,7 @@ public class CommandFunction extends AbstractConditionalFun )); } else { for (Player viewer : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); executeCommands(relationalContext, this.asEvent ? viewer::performCommandAsEvent : command1 -> viewer.performCommand(command1, this.asOp)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java index 5f1d6d787..143b02f1d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java @@ -33,4 +33,5 @@ public final class CommonFunctions { public static final Key MYTHIC_MOBS_SKILL = Key.of("craftengine:mythic_mobs_skill"); public static final Key TELEPORT = Key.of("craftengine:teleport"); public static final Key TOAST = Key.of("craftengine:toast"); + public static final Key SET_VARIABLE = Key.of("craftengine:set_variable"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java index dd369f792..51316f239 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java @@ -36,7 +36,7 @@ public class LevelerExpFunction extends AbstractConditional }); } else { for (Player target : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target)); CraftEngine.instance().compatibilityManager().addLevelerExp(target, this.plugin, this.leveler, this.count.getDouble(relationalContext)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java index 640d26b41..ffedc3333 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java @@ -38,7 +38,7 @@ public class MessageFunction extends AbstractConditionalFun }); } else { for (Player viewer : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); for (TextProvider c : this.messages) { viewer.sendMessage(AdventureHelper.miniMessage().deserialize(c.get(relationalContext), relationalContext.tagResolvers()), this.overlay); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java index 9799f63e2..3cda10b9b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java @@ -46,7 +46,7 @@ public class OpenWindowFunction extends AbstractConditional for (Player viewer : this.selector.get(ctx)) { CraftEngine.instance().guiManager().openInventory(viewer, this.guiType); if (this.optionalTitle != null) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); CraftEngine.instance().guiManager().updateInventoryTitle(viewer, AdventureHelper.miniMessage().deserialize(this.optionalTitle.get(relationalContext), relationalContext.tagResolvers())); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java index 0b91844ee..fa25af75f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java @@ -39,7 +39,7 @@ public class PotionEffectFunction extends AbstractCondition }); } else { for (Player target : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target)); target.addPotionEffect(this.potionEffectType, this.duration.getInt(relationalContext), this.amplifier.getInt(relationalContext), this.ambient, this.particles); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java index 8019f8717..5634d5f19 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java @@ -41,7 +41,7 @@ public class SetCooldownFunction extends AbstractConditiona }); } else { for (Player target : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target)); long millis = TimeUtils.parseToMillis(this.time.get(relationalContext)); CooldownData data = target.cooldown(); if (this.add) data.addCooldown(this.id, millis); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java index 70a1e3f6e..544a2a147 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java @@ -33,7 +33,7 @@ public class SetFoodFunction extends AbstractConditionalFun optionalPlayer.ifPresent(player -> player.setFoodLevel(this.add ? player.foodLevel() + this.count.getInt(ctx) : this.count.getInt(ctx))); } else { for (Player target : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target)); target.setFoodLevel(this.add ? target.foodLevel() + this.count.getInt(relationalContext) : this.count.getInt(relationalContext)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java index 6b002aedd..9c6d24e98 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java @@ -33,7 +33,7 @@ public class SetSaturationFunction extends AbstractConditio optionalPlayer.ifPresent(player -> player.setSaturation(this.add ? player.saturation() + this.count.getFloat(ctx) : this.count.getFloat(ctx))); } else { for (Player target : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target)); target.setSaturation(this.add ? target.saturation() + this.count.getFloat(relationalContext) : this.count.getFloat(relationalContext)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetVariableFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetVariableFunction.java new file mode 100644 index 000000000..4d8c47419 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetVariableFunction.java @@ -0,0 +1,72 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import com.mojang.datafixers.util.Either; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; +import net.momirealms.craftengine.core.plugin.context.ContextKey; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; + +public class SetVariableFunction extends AbstractConditionalFunction { + private final Either either; + private final String variableName; + private final boolean asInt; + + public SetVariableFunction(List> predicates, String variableName, Either either, boolean asInt) { + super(predicates); + this.either = either; + this.variableName = variableName; + this.asInt = asInt; + } + + @Override + public void runInternal(CTX ctx) { + ContextHolder contexts = ctx.contexts(); + if (contexts.immutable()) return; + this.either.ifLeft(text -> contexts.withParameter(ContextKey.direct("var_" + this.variableName), text.get(ctx))) + .ifRight(number -> contexts.withParameter(ContextKey.direct("var_" + this.variableName), asInt ? number.getInt(ctx) : number.getDouble(ctx))); + } + + @Override + public Key type() { + return CommonFunctions.SET_VARIABLE; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + String variableName = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("name"), "warning.config.function.set_variable.missing_name"); + if (arguments.containsKey("number")) { + return new SetVariableFunction<>( + getPredicates(arguments), + variableName, + Either.right(NumberProviders.fromObject(arguments.get("number"))), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("as-int", false), "as-int") + ); + } else if (arguments.containsKey("text")) { + return new SetVariableFunction<>( + getPredicates(arguments), + variableName, + Either.left(TextProviders.fromString(arguments.get("text").toString())), + false + ); + } else { + throw new LocalizedResourceConfigException("warning.config.function.set_variable.missing_value"); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java index 1455c93ab..f207afed2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TeleportFunction.java @@ -2,7 +2,6 @@ package net.momirealms.craftengine.core.plugin.context.function; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.Platform; import net.momirealms.craftengine.core.plugin.context.*; import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; @@ -11,19 +10,14 @@ import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; import net.momirealms.craftengine.core.plugin.context.text.TextProvider; import net.momirealms.craftengine.core.plugin.context.text.TextProviders; -import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldPosition; import org.jetbrains.annotations.Nullable; -import org.w3c.dom.Text; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; public class TeleportFunction extends AbstractConditionalFunction { private final PlayerSelector selector; @@ -47,22 +41,21 @@ public class TeleportFunction extends AbstractConditionalFu this.yaw = yaw; } + @SuppressWarnings("DuplicatedCode") @Override public void runInternal(CTX ctx) { if (this.selector == null) { - ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { - it.teleport(new WorldPosition( - Optional.ofNullable(this.world).map(w -> w.get(ctx)).map(w -> CraftEngine.instance().platform().getWorld(w)).orElse(it.world()), - this.x.getDouble(ctx), - this.y.getDouble(ctx), - this.z.getDouble(ctx), - this.pitch.getFloat(ctx), - this.yaw.getFloat(ctx)) - ); - }); + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> it.teleport(new WorldPosition( + Optional.ofNullable(this.world).map(w -> w.get(ctx)).map(w -> CraftEngine.instance().platform().getWorld(w)).orElse(it.world()), + this.x.getDouble(ctx), + this.y.getDouble(ctx), + this.z.getDouble(ctx), + this.pitch.getFloat(ctx), + this.yaw.getFloat(ctx)) + )); } else { for (Player viewer : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); viewer.teleport(new WorldPosition( Optional.ofNullable(this.world).map(w -> w.get(relationalContext)).map(w -> CraftEngine.instance().platform().getWorld(w)).orElse(viewer.world()), this.x.getDouble(relationalContext), diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java index 43eb8d195..cbae62005 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java @@ -45,7 +45,7 @@ public class TitleFunction extends AbstractConditionalFunct )); } else { for (Player viewer : this.selector.get(ctx)) { - RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); viewer.sendTitle( AdventureHelper.miniMessage().deserialize(this.main.get(relationalContext), relationalContext.tagResolvers()), AdventureHelper.miniMessage().deserialize(this.sub.get(relationalContext), relationalContext.tagResolvers()), diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java new file mode 100644 index 000000000..eadb1609e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java @@ -0,0 +1,87 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.advancement.AdvancementType; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.EnumUtils; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class ToastFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final TextProvider toast; + private final java.util.function.Function> icon; + private final AdvancementType advancementType; + + public ToastFunction(List> predicates, + @Nullable PlayerSelector selector, + TextProvider toast, + java.util.function.Function> icon, + AdvancementType advancementType) { + super(predicates); + this.selector = selector; + this.toast = toast; + this.icon = icon; + this.advancementType = advancementType; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> it.sendToast(AdventureHelper.miniMessage().deserialize(this.toast.get(ctx), ctx.tagResolvers()), this.icon.apply(it), this.advancementType)); + } else { + for (Player viewer : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); + viewer.sendToast(AdventureHelper.miniMessage().deserialize(this.toast.get(relationalContext), relationalContext.tagResolvers()), this.icon.apply(viewer), this.advancementType); + } + } + } + + @Override + public Key type() { + return CommonFunctions.TOAST; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + AdvancementType advancementType; + String advancementName = arguments.getOrDefault("advancement-type", "goal").toString(); + try { + advancementType = AdvancementType.valueOf(advancementName.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new LocalizedResourceConfigException("warning.config.function.toast.invalid_advancement_type", advancementName, EnumUtils.toString(AdvancementType.values())); + } + String toast = ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "toast", "message"), "warning.config.function.toast.missing_toast"); + Key item = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "item", "icon"), "warning.config.function.toast.missing_icon")); + return new ToastFunction<>( + getPredicates(arguments), + PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), + TextProviders.fromString(toast), + player -> CraftEngine.instance().itemManager().createWrappedItem(item, player), + advancementType + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index 5556a9d6c..b4f530ae8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -161,8 +161,8 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { this.plugin.logger().warn("Can't not find item " + it.icon() + " for category icon"); return null; } - item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(it.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); - item.loreJson(it.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(it.displayName(), ItemBuildContext.EMPTY_RESOLVERS))); + item.loreJson(it.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY_RESOLVERS))).toList()); return new ItemWithAction(item, (element, click) -> { click.cancel(); player.playSound(Constants.SOUND_CLICK_BUTTON); @@ -245,12 +245,12 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { if (ItemUtils.isEmpty(item)) { if (!subCategory.icon().equals(ItemKeys.AIR)) { item = this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player); - item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); - item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY_RESOLVERS))); + item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY_RESOLVERS))).toList()); } } else { - item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); - item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY_RESOLVERS))); + item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY_RESOLVERS))).toList()); } return new ItemWithAction(item, (element, click) -> { click.cancel(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ReflectionUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ReflectionUtils.java index fa7b61ce5..703571af4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ReflectionUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ReflectionUtils.java @@ -111,6 +111,22 @@ public class ReflectionUtils { return null; } + @Nullable + public static Field getStaticDeclaredField(final Class clazz, final Class type, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + if (Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + } + return null; + } + @Nullable public static Field getDeclaredField(final Class clazz, final Class type, int index) { int i = 0; diff --git a/gradle.properties b/gradle.properties index 63049c23f..b366a9595 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.17 +project_version=0.0.62.18 config_version=45 -lang_version=26 +lang_version=27 project_group=net.momirealms latest_supported_version=1.21.8 From c2e5cb1522821ab66610dcc2c080d8abed912b6b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 05:07:13 +0800 Subject: [PATCH 080/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=AE=9E=E4=BD=93=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BukkitBlockEntityElementConfigs.java | 1 + .../TextDisplayBlockEntityElement.java | 46 +++++++ .../TextDisplayBlockEntityElementConfig.java | 128 ++++++++++++++++++ .../element/BlockEntityElementConfigs.java | 2 +- 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java index a8e8effd4..b8c12c41b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java @@ -6,6 +6,7 @@ public class BukkitBlockEntityElementConfigs extends BlockEntityElementConfigs { static { register(ITEM_DISPLAY, ItemDisplayBlockEntityElementConfig.FACTORY); + register(TEXT_DISPLAY, TextDisplayBlockEntityElementConfig.FACTORY); } public static void init() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java new file mode 100644 index 000000000..f3bd9ce31 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java @@ -0,0 +1,46 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.BlockPos; +import org.joml.Vector3f; + +import java.util.List; +import java.util.UUID; + +public class TextDisplayBlockEntityElement implements BlockEntityElement { + private final TextDisplayBlockEntityElementConfig config; + private final Object cachedSpawnPacket; + private final Object cachedDespawnPacket; + private final int entityId; + + public TextDisplayBlockEntityElement(TextDisplayBlockEntityElementConfig config, BlockPos pos) { + int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); + Vector3f position = config.position(); + this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId, UUID.randomUUID(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, + config.xRot(), config.yRot(), MEntityTypes.TEXT_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 + ); + this.config = config; + this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId)); + this.entityId = entityId; + } + + @Override + public void despawn(Player player) { + player.sendPacket(this.cachedDespawnPacket, false); + } + + @Override + public void spawn(Player player) { + player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), true); + } + + @Override + public void update(Player player) { + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java new file mode 100644 index 000000000..1a105d91d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java @@ -0,0 +1,128 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; +import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; +import net.momirealms.craftengine.core.entity.Billboard; +import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; + +public class TextDisplayBlockEntityElementConfig implements BlockEntityElementConfig { + public static final Factory FACTORY = new Factory(); + private final Function> lazyMetadataPacket; + private final String text; + private final Vector3f scale; + private final Vector3f position; + private final Vector3f translation; + private final float xRot; + private final float yRot; + private final Quaternionf rotation; + private final Billboard billboard; + + public TextDisplayBlockEntityElementConfig(String text, + Vector3f scale, + Vector3f position, + Vector3f translation, + float xRot, + float yRot, + Quaternionf rotation, + Billboard billboard) { + this.text = text; + this.scale = scale; + this.position = position; + this.translation = translation; + this.xRot = xRot; + this.yRot = yRot; + this.rotation = rotation; + this.billboard = billboard; + this.lazyMetadataPacket = player -> { + List dataValues = new ArrayList<>(); + TextDisplayEntityData.Text.addEntityDataIfNotDefaultValue(ComponentUtils.adventureToMinecraft(text(player)), dataValues); + TextDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); + TextDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); + TextDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); + TextDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); + return dataValues; + }; + } + + @Override + public TextDisplayBlockEntityElement create(BlockPos pos) { + return new TextDisplayBlockEntityElement(this, pos); + } + + public Component text(Player player) { + return AdventureHelper.miniMessage().deserialize(this.text, PlayerOptionalContext.of(player).tagResolvers()); + } + + public Vector3f scale() { + return this.scale; + } + + public Vector3f translation() { + return this.translation; + } + + public Vector3f position() { + return this.position; + } + + public float yRot() { + return this.yRot; + } + + public float xRot() { + return this.xRot; + } + + public Billboard billboard() { + return billboard; + } + + public Quaternionf rotation() { + return rotation; + } + + public List metadataValues(Player player) { + return this.lazyMetadataPacket.apply(player); + } + + public static class Factory implements BlockEntityElementConfigFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockEntityElementConfig create(Map arguments) { + String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.block.state.entity_renderer.text_display.missing_text"); + return (BlockEntityElementConfig) new TextDisplayBlockEntityElementConfig( + text, + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), + ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), + ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"), + Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)) + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java index 70338ebee..77841caa8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java @@ -20,7 +20,7 @@ public class BlockEntityElementConfigs { } public static BlockEntityElementConfig fromMap(Map arguments) { - Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(ITEM_DISPLAY); + Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY); BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type); if (factory == null) { throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString()); From bed042b81cd3d53629e45e58a7f6ae3950387579 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 05:26:18 +0800 Subject: [PATCH 081/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E5=AE=9A?= =?UTF-8?q?=E7=9A=84=E4=BA=A4=E4=BA=92=E8=B7=9D=E7=A6=BB=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=8E=AA=E6=96=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/network/PacketConsumers.java | 12 ++++++++++++ .../bukkit/plugin/user/BukkitServerPlayer.java | 7 +++++++ .../net/momirealms/craftengine/core/world/Vec3d.java | 7 +++++++ 3 files changed, 26 insertions(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index cd2cb926b..6bd1915bd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -41,6 +41,7 @@ import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.entity.furniture.HitBox; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.font.FontManager; import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; @@ -1608,6 +1609,11 @@ public class PacketConsumers { if (serverPlayer.isAdventureMode() || !furniture.isValid()) return; + // todo 重构家具时候注意,需要准备加载好的hitbox类,以获取hitbox坐标 + if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { + return; + } + FurnitureAttemptBreakEvent preBreakEvent = new FurnitureAttemptBreakEvent(serverPlayer.platformPlayer(), furniture); if (EventUtils.fireAndCheckCancel(preBreakEvent)) return; @@ -1641,6 +1647,7 @@ public class PacketConsumers { float x = buf.readFloat(); float y = buf.readFloat(); float z = buf.readFloat(); + // todo 这个是错误的,这是实体的相对位置而非绝对位置 Location interactionPoint = new Location(platformPlayer.getWorld(), x, y, z); InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; boolean usingSecondaryAction = buf.readBoolean(); @@ -1660,6 +1667,11 @@ public class PacketConsumers { return; } + // todo 重构家具时候注意,需要准备加载好的hitbox类,以获取hitbox坐标 + if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { + return; + } + FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint); if (EventUtils.fireAndCheckCancel(interactEvent)) { return; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index dbdbebfeb..5d872be4d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -516,6 +516,13 @@ public class BukkitServerPlayer extends Player { return (new AABB(pos)).distanceToSqr(this.getEyePosition()) < d * d; } + public boolean canInteractPoint(Vec3d pos, double distance) { + double d = this.getCachedInteractionRange() + distance; + System.out.println(Vec3d.distanceToSqr(this.getEyePosition(), pos)); + System.out.println(d * d); + return Vec3d.distanceToSqr(this.getEyePosition(), pos) < d * d; + } + public final Vec3d getEyePosition() { Location eyeLocation = this.platformPlayer().getEyeLocation(); return new Vec3d(eyeLocation.getX(), eyeLocation.getY(), eyeLocation.getZ()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java index 1a367fc03..aeb8b3df5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java @@ -45,6 +45,13 @@ public class Vec3d implements Position { return atLowerCornerWithOffset(vec, 0.5, deltaY, 0.5); } + public static double distanceToSqr(Vec3d vec1, Vec3d vec2) { + double dx = vec2.x - vec1.x; + double dy = vec2.y - vec1.y; + double dz = vec2.z - vec1.z; + return dx * dx + dy * dy + dz * dz; + } + @Override public double x() { return x; From d35afe98859f1c98f57670b78365922140e61057 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 05:27:22 +0800 Subject: [PATCH 082/226] Update BukkitServerPlayer.java --- .../craftengine/bukkit/plugin/user/BukkitServerPlayer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 5d872be4d..8292deb0d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -518,8 +518,6 @@ public class BukkitServerPlayer extends Player { public boolean canInteractPoint(Vec3d pos, double distance) { double d = this.getCachedInteractionRange() + distance; - System.out.println(Vec3d.distanceToSqr(this.getEyePosition(), pos)); - System.out.println(d * d); return Vec3d.distanceToSqr(this.getEyePosition(), pos) < d * d; } From c285e6be78cb6ba0884578c40f7cce7cb5ecfa47 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 10:37:22 +0800 Subject: [PATCH 083/226] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8F=B0=E7=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../default/configuration/blocks.yml | 110 ++++++++++++++++++ .../default/configuration/categories.yml | 6 +- .../default/configuration/furniture.yml | 51 -------- 3 files changed, 113 insertions(+), 54 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 757d0d468..2384cba31 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -739,6 +739,116 @@ items#misc: facing=west,shape=straight: appearance: facing=west,shape=straight id: 11 + default:table_lamp: + material: nether_brick + custom-model-data: 3011 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/table_lamp + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/metal + overrides: + hardness: 1 + resistance: 1 + replaceable: false + is-redstone-conductor: false + is-suffocating: false + behaviors: + - type: toggleable_lamp_block + can-open-with-hand: true + - type: sturdy_base_block + direction: down + support-types: + - full + - center + states: + properties: + lit: + type: boolean + default: false + facing: + type: 4-direction + default: north + appearances: + east_off: + state: barrier + entity-renderer: + item: default:table_lamp + north_off: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: -90 + south_off: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 90 + west_off: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 180 + east_on: + state: barrier + entity-renderer: + item: default:table_lamp + north_on: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: -90 + south_on: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 90 + west_on: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 180 + variants: + facing=east,lit=false: + appearance: east_off + id: 12 + facing=north,lit=false: + appearance: north_off + id: 13 + facing=south,lit=false: + appearance: south_off + id: 14 + facing=west,lit=false: + appearance: west_off + id: 15 + facing=east,lit=true: + appearance: east_on + id: 16 + settings: + luminance: 15 + facing=north,lit=true: + appearance: north_on + id: 17 + settings: + luminance: 15 + facing=south,lit=true: + appearance: south_on + id: 18 + settings: + luminance: 15 + facing=west,lit=true: + appearance: west_on + id: 19 + settings: + luminance: 15 recipes#misc: default:chinese_lantern: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 989c66a4b..8dc018294 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -52,10 +52,9 @@ categories: default:furniture: name: <#FFD700> hidden: true - icon: default:table_lamp + icon: default:flower_basket list: - default:bench - - default:table_lamp - default:wooden_chair - default:flower_basket default:misc: @@ -83,4 +82,5 @@ categories: - default:sleeper_sofa - minecraft:air - minecraft:air - - default:sofa \ No newline at end of file + - default:sofa + - default:table_lamp \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/furniture.yml b/common-files/src/main/resources/resources/default/configuration/furniture.yml index 23212868d..e54a3ccb0 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture.yml @@ -42,57 +42,6 @@ items: template: default:loot_table/furniture arguments: item: default:bench - default:table_lamp: - material: nether_brick - custom-model-data: 2001 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/table_lamp - behavior: - type: furniture_item - furniture: - settings: - item: default:table_lamp - sounds: - break: minecraft:block.lantern.break - place: minecraft:block.lantern.place - placement: - ground: - loot-spawn-offset: 0,0.2,0 - rules: - rotation: ANY - alignment: QUARTER - elements: - - item: default:table_lamp - display-transform: NONE - billboard: FIXED - translation: 0,0.5,0 - rotation: 90 - hitboxes: - - position: 0,0,0 - type: interaction - blocks-building: true - width: 0.7 - height: 0.1 - interactive: true - - position: 0,0.1,-0.1 - type: interaction - blocks-building: true - width: 0.1 - height: 0.6 - interactive: true - - position: 0,0.6,0.15 - type: interaction - blocks-building: true - width: 0.4 - height: 0.4 - interactive: true - loot: - template: default:loot_table/furniture - arguments: - item: default:table_lamp default:wooden_chair: material: nether_brick custom-model-data: 2002 From f724faaf86be4fea2340e680299f14769eeb95a0 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 13:21:41 +0800 Subject: [PATCH 084/226] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BA=A7=E6=A4=85=E6=96=B9=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../block/behavior/SeatBlockBehavior.java | 77 ++++++++ .../block/entity/BukkitBlockEntityTypes.java | 1 + .../bukkit/block/entity/SeatBlockEntity.java | 179 ++++++++++++++++++ .../furniture/BukkitFurnitureManager.java | 3 + .../default/configuration/blocks.yml | 13 +- .../block/behavior/EntityBlockBehavior.java | 5 + .../block/entity/BlockEntityTypeKeys.java | 1 + 8 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 34435ad14..f13476d45 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -32,6 +32,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); + public static final Key SEAT_BLOCK = Key.from("craftengine:seat_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -62,5 +63,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); + register(SEAT_BLOCK, SeatBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java new file mode 100644 index 000000000..252c8b11d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java @@ -0,0 +1,77 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; +import net.momirealms.craftengine.bukkit.block.entity.SeatBlockEntity; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import org.joml.Vector3f; + +import java.util.Map; + +public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Vector3f offset; + private final float yaw; + private final boolean limitPlayerRotation; + + public SeatBlockBehavior(CustomBlock customBlock, Vector3f offset, float yaw, boolean limitPlayerRotation) { + super(customBlock); + this.offset = offset; + this.yaw = yaw; + this.limitPlayerRotation = limitPlayerRotation; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + BukkitServerPlayer player = (BukkitServerPlayer) context.getPlayer(); + if (player == null || player.isSecondaryUseActive() || player.platformPlayer() == null) { + return InteractionResult.PASS; + } + CEWorld world = context.getLevel().storageWorld(); + BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos()); + if (!(blockEntity instanceof SeatBlockEntity seatBlockEntity) || !seatBlockEntity.seatEntities().isEmpty()) { + return InteractionResult.PASS; + } + seatBlockEntity.spawnSeatEntityForPlayer(player.platformPlayer(), this.offset, this.yaw, this.limitPlayerRotation); + return InteractionResult.SUCCESS_AND_CANCEL; + } + + @SuppressWarnings("unchecked") + @Override + public BlockEntityType blockEntityType() { + return (BlockEntityType) BukkitBlockEntityTypes.SEAT; + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { + return new SeatBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + return EntityBlockBehavior.createTickerHelper(SeatBlockEntity::tick); + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Vector3f offset = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("offset", "0,0,0"), "offset"); + float yaw = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"); + boolean limitPlayerRotation = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("limit-player-rotation", true), "limit-player-rotation"); + return new SeatBlockBehavior(block, offset, yaw, limitPlayerRotation); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java index 3e778bd32..c739d0bc4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -6,4 +6,5 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; public class BukkitBlockEntityTypes extends BlockEntityTypes { public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); + public static final BlockEntityType SEAT = register(BlockEntityTypeKeys.SEAT, SeatBlockEntity::new); } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java new file mode 100644 index 000000000..40f6473cf --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java @@ -0,0 +1,179 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.bukkit.util.EntityUtils; +import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.QuaternionUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.Map; +import java.util.Objects; + +@SuppressWarnings("DuplicatedCode") +public class SeatBlockEntity extends BlockEntity { + private final Map seatEntities = new Reference2ObjectArrayMap<>(1); + + public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.SEAT, pos, blockState); + } + + public Map seatEntities() { + return this.seatEntities; + } + + public static void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, SeatBlockEntity seat) { + if (seat.seatEntities.isEmpty()) return; + for (Map.Entry entry : seat.seatEntities.entrySet()) { + Entity entity = entry.getKey(); + if (!entity.getPassengers().isEmpty()) continue; + Player player = entry.getValue(); + seat.tryLeavingSeat(player, entity); + seat.seatEntities.remove(entity); + } + } + + @Override + public void preRemove() { + if (this.seatEntities.isEmpty()) return; + for (Map.Entry entry : this.seatEntities.entrySet()) { + Entity entity = entry.getKey(); + entity.remove(); + this.seatEntities.remove(entity); + } + this.seatEntities.clear(); + } + + public void spawnSeatEntityForPlayer(@NotNull Player player, @NotNull Vector3f offset, float yaw, boolean limitPlayerRotation) { + if (!this.seatEntities.isEmpty() || !this.isValid()) return; + Location location = calculateSeatLocation(player, this.pos, this.blockState, offset, yaw); + Entity seatEntity = limitPlayerRotation ? + EntityUtils.spawnEntity(player.getWorld(), + VersionHelper.isOrAbove1_20_2() ? location.subtract(0, 0.9875, 0) : location.subtract(0, 0.990625, 0), + EntityType.ARMOR_STAND, + entity -> { + ArmorStand armorStand = (ArmorStand) entity; + if (VersionHelper.isOrAbove1_21_3()) { + Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01); + } else { + LegacyAttributeUtils.setMaxHealth(armorStand); + } + armorStand.setSmall(true); + armorStand.setInvisible(true); + armorStand.setSilent(true); + armorStand.setInvulnerable(true); + armorStand.setArms(false); + armorStand.setCanTick(false); + armorStand.setAI(false); + armorStand.setGravity(false); + armorStand.setPersistent(false); + }) : + EntityUtils.spawnEntity(player.getWorld(), + VersionHelper.isOrAbove1_20_2() ? location : location.subtract(0, 0.25, 0), + EntityType.ITEM_DISPLAY, + entity -> { + ItemDisplay itemDisplay = (ItemDisplay) entity; + itemDisplay.setPersistent(false); + }); + if (!seatEntity.addPassenger(player)) { + seatEntity.remove(); + return; + } + this.seatEntities.put(seatEntity, player); + } + + private Location calculateSeatLocation(Player player, BlockPos pos, ImmutableBlockState state, Vector3f offset, float yaw) { + Location location = new Location(player.getWorld(), pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5); + for (Property property : state.getProperties()) { + if (property.name().equals("facing") && property.valueClass() == HorizontalDirection.class) { + switch ((HorizontalDirection) state.get(property)) { + case NORTH -> location.setYaw(0); + case SOUTH -> location.setYaw(180); + case WEST -> location.setYaw(270); + case EAST -> location.setYaw(90); + } + break; + } + if (property.name().equals("facing_clockwise") && property.valueClass() == HorizontalDirection.class) { + switch ((HorizontalDirection) state.get(property)) { + case NORTH -> location.setYaw(90); + case SOUTH -> location.setYaw(270); + case WEST -> location.setYaw(0); + case EAST -> location.setYaw(180); + } + break; + } + } + Vector3f newOffset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - location.getYaw()), 0) + .conjugate() + .transform(new Vector3f(offset)); + double newYaw = yaw + location.getYaw(); + if (newYaw < -180) newYaw += 360; + Location newLocation = location.clone(); + newLocation.setYaw((float) newYaw); + newLocation.add(newOffset.x, newOffset.y + 0.6, -newOffset.z); + return newLocation; + } + + private void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) { + vehicle.remove(); + if (player.getVehicle() != null) return; + Location vehicleLocation = vehicle.getLocation(); + Location originalLocation = vehicleLocation.clone(); + originalLocation.setY(this.pos.y()); + Location targetLocation = originalLocation.clone().add(vehicleLocation.getDirection().multiply(1.1)); + if (!isSafeLocation(targetLocation)) { + targetLocation = findSafeLocationNearby(originalLocation); + if (targetLocation == null) return; + } + targetLocation.setYaw(player.getLocation().getYaw()); + targetLocation.setPitch(player.getLocation().getPitch()); + if (VersionHelper.isFolia()) { + player.teleportAsync(targetLocation); + } else { + player.teleport(targetLocation); + } + } + + private boolean isSafeLocation(Location location) { + World world = location.getWorld(); + if (world == null) return false; + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + if (!world.getBlockAt(x, y - 1, z).getType().isSolid()) return false; + if (!world.getBlockAt(x, y, z).isPassable()) return false; + return world.getBlockAt(x, y + 1, z).isPassable(); + } + + @Nullable + private Location findSafeLocationNearby(Location center) { + World world = center.getWorld(); + if (world == null) return null; + int centerX = center.getBlockX(); + int centerY = center.getBlockY(); + int centerZ = center.getBlockZ(); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) continue; + int x = centerX + dx; + int z = centerZ + dz; + Location nearbyLocation = new Location(world, x + 0.5, centerY, z + 0.5); + if (isSafeLocation(nearbyLocation)) return nearbyLocation; + } + } + return null; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index 7cc579dce..b7e58f8be 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -337,6 +337,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { plugin.scheduler().sync().runDelayed(() -> tryLeavingSeat(player, entity), player.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4); } + @SuppressWarnings("DuplicatedCode") protected void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) { Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); if (baseFurniture == null) return; @@ -375,6 +376,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return (entity instanceof ArmorStand || entity instanceof ItemDisplay); } + @SuppressWarnings("DuplicatedCode") private boolean isSafeLocation(Location location) { World world = location.getWorld(); if (world == null) return false; @@ -386,6 +388,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return world.getBlockAt(x, y + 1, z).isPassable(); } + @SuppressWarnings("DuplicatedCode") @Nullable private Location findSafeLocationNearby(Location center) { World world = center.getWorld(); diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 2384cba31..f3b182d5b 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -587,10 +587,13 @@ items#misc: step: minecraft:block.wood.step tags: - minecraft:mineable/axe - behavior: - type: bouncing_block - bounce-height: 0.66 - sync-player-position: false + behaviors: + - type: bouncing_block + bounce-height: 0.66 + sync-player-position: false + - type: seat_block + offset: 0,-0.5,0 + limit-player-rotation: false state: id: 0 state: white_bed[facing=west,occupied=false,part=foot] @@ -638,6 +641,8 @@ items#misc: - type: sofa_block - type: bouncing_block bounce-height: 0.66 + - type: seat_block + offset: 0,-0.45,0 states: properties: facing: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java index e994ca03e..5df15876e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java @@ -18,4 +18,9 @@ public interface EntityBlockBehavior { default BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { return null; } + + @SuppressWarnings("unchecked") + static BlockEntityTicker createTickerHelper(BlockEntityTicker ticker) { + return (BlockEntityTicker) ticker; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java index cb452fd4d..b801d9156 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -7,4 +7,5 @@ public final class BlockEntityTypeKeys { public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite"); public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); + public static final Key SEAT = Key.of("craftengine:seat"); } From c70b765a32417ba5291cb8b438e917446aa48798 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 13:41:00 +0800 Subject: [PATCH 085/226] =?UTF-8?q?fix(block):=20=E8=A1=A5=E4=B8=80?= =?UTF-8?q?=E4=B8=8B=E6=8C=A5=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/behavior/SeatBlockBehavior.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java index 252c8b11d..bc8995e80 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java @@ -36,12 +36,13 @@ public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBloc @Override public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { BukkitServerPlayer player = (BukkitServerPlayer) context.getPlayer(); - if (player == null || player.isSecondaryUseActive() || player.platformPlayer() == null) { + if (player == null || player.isSecondaryUseActive()) { return InteractionResult.PASS; } CEWorld world = context.getLevel().storageWorld(); BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos()); if (!(blockEntity instanceof SeatBlockEntity seatBlockEntity) || !seatBlockEntity.seatEntities().isEmpty()) { + player.swingHand(context.getHand()); return InteractionResult.PASS; } seatBlockEntity.spawnSeatEntityForPlayer(player.platformPlayer(), this.offset, this.yaw, this.limitPlayerRotation); From 071b6872e4caf55f0e6dce1bee9041f10594b386 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 14:44:22 +0800 Subject: [PATCH 086/226] =?UTF-8?q?fix(block):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E8=B9=A6=E8=B5=B7=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 21 ++++++++++++++++--- .../block/behavior/SeatBlockBehavior.java | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 2d40df2d3..01d3539db 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -1,15 +1,18 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.Vec3d; +import org.bukkit.entity.Entity; import java.util.Map; import java.util.concurrent.Callable; @@ -31,7 +34,7 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { if (this.fallDamageMultiplier <= 0.0) return; Object entity = args[3]; - Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * this.fallDamageMultiplier : (float) args[4] * this.fallDamageMultiplier; + Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * this.fallDamageMultiplier : (float) args[4] * (float) this.fallDamageMultiplier; FastNMS.INSTANCE.method$Entity$causeFallDamage( entity, finalFallDistance, 1.0F, FastNMS.INSTANCE.method$DamageSources$fall(FastNMS.INSTANCE.method$Entity$damageSources(entity)) @@ -55,9 +58,21 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge double y = -deltaMovement.y * this.bounceHeight * d; FastNMS.INSTANCE.method$Entity$setDeltaMovement(entity, deltaMovement.x, y, deltaMovement.z); if (CoreReflections.clazz$Player.isInstance(entity) && this.syncPlayerPosition - && /* 防抖 -> */ y > 0.032 /* <- 防抖 */ + && /* 防抖 -> */ y > 0.035 /* <- 防抖 */ ) { - FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true); + // 这里一定要延迟 1t 不然就会出问题 + if (VersionHelper.isFolia()) { + Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity); + bukkitEntity.getScheduler().runDelayed(BukkitCraftEngine.instance().javaPlugin(), + r -> FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true), + () -> {}, 1L + ); + } else { + CraftEngine.instance().scheduler().sync().runLater( + () -> FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true), + 1L + ); + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java index bc8995e80..7cd3d28a2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java @@ -39,10 +39,10 @@ public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBloc if (player == null || player.isSecondaryUseActive()) { return InteractionResult.PASS; } + player.swingHand(context.getHand()); CEWorld world = context.getLevel().storageWorld(); BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos()); if (!(blockEntity instanceof SeatBlockEntity seatBlockEntity) || !seatBlockEntity.seatEntities().isEmpty()) { - player.swingHand(context.getHand()); return InteractionResult.PASS; } seatBlockEntity.spawnSeatEntityForPlayer(player.platformPlayer(), this.offset, this.yaw, this.limitPlayerRotation); From 0ae97b8d48e66e86b0f40732dcacdcf363717f72 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 15:16:02 +0800 Subject: [PATCH 087/226] =?UTF-8?q?fix(block):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 2 +- .../bukkit/block/entity/SeatBlockEntity.java | 44 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index 01d3539db..f72f41ed8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -65,7 +65,7 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity); bukkitEntity.getScheduler().runDelayed(BukkitCraftEngine.instance().javaPlugin(), r -> FastNMS.INSTANCE.field$Entity$hurtMarked(entity, true), - () -> {}, 1L + null, 1L ); } else { CraftEngine.instance().scheduler().sync().runLater( diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java index 40f6473cf..63320eb1b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.bukkit.block.entity; +import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.util.EntityUtils; import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -35,13 +37,45 @@ public class SeatBlockEntity extends BlockEntity { } public static void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, SeatBlockEntity seat) { - if (seat.seatEntities.isEmpty()) return; - for (Map.Entry entry : seat.seatEntities.entrySet()) { + int size = seat.seatEntities.size(); + if (size == 0) return; + if (size == 1) { + // 99.9999%的情况下会命中这里 + Map.Entry entry = seat.seatEntities.entrySet().iterator().next(); Entity entity = entry.getKey(); - if (!entity.getPassengers().isEmpty()) continue; Player player = entry.getValue(); - seat.tryLeavingSeat(player, entity); - seat.seatEntities.remove(entity); + if (VersionHelper.isFolia()) { + entity.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), t -> { + if (entity.getPassengers().isEmpty()) { + seat.tryLeavingSeat(player, entity); + seat.seatEntities.remove(entity); + } + }, null); + } else { + if (entity.getPassengers().isEmpty()) { + seat.tryLeavingSeat(player, entity); + seat.seatEntities.remove(entity); + } + } + return; + } + for (Map.Entry entry : ImmutableList.copyOf(seat.seatEntities.entrySet())) { + // 几乎不可能命中这里,除非写出bug了 + Entity entity = entry.getKey(); + Player player = entry.getValue(); + if (VersionHelper.isFolia()) { + entity.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), t -> { + if (entity.getPassengers().isEmpty()) { + seat.tryLeavingSeat(player, entity); + seat.seatEntities.remove(entity); + } + }, null); + } else { + if (entity.getPassengers().isEmpty()) { + seat.tryLeavingSeat(player, entity); + seat.seatEntities.remove(entity); + } + } } } From 760a98319438bb5dcccf1f5ac5a63721a6820830 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 15:24:39 +0800 Subject: [PATCH 088/226] =?UTF-8?q?fix(block):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/entity/SeatBlockEntity.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java index 63320eb1b..e39b6c2ae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java @@ -82,12 +82,11 @@ public class SeatBlockEntity extends BlockEntity { @Override public void preRemove() { if (this.seatEntities.isEmpty()) return; - for (Map.Entry entry : this.seatEntities.entrySet()) { - Entity entity = entry.getKey(); - entity.remove(); - this.seatEntities.remove(entity); + try { + this.seatEntities.keySet().forEach(Entity::remove); + } finally { + this.seatEntities.clear(); } - this.seatEntities.clear(); } public void spawnSeatEntityForPlayer(@NotNull Player player, @NotNull Vector3f offset, float yaw, boolean limitPlayerRotation) { From ee37c8cd6820b70bddeb994e40726cd49f58ccd3 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 17:16:07 +0800 Subject: [PATCH 089/226] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/default/configuration/block_name.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/block_name.yml b/common-files/src/main/resources/resources/default/configuration/block_name.yml index 4c750da5a..97e421dac 100644 --- a/common-files/src/main/resources/resources/default/configuration/block_name.yml +++ b/common-files/src/main/resources/resources/default/configuration/block_name.yml @@ -28,8 +28,8 @@ lang: block_name:default:copper_coil: Copper Coil block_name:default:chessboard_block: Chessboard Block block_name:default:safe_block: Safe Block + block_name:default:sleeper_sofa: Sofa block_name:default:sofa: Sofa - block_name:default:connectable_sofa: Sofa zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -56,5 +56,5 @@ lang: block_name:default:copper_coil: 铜线圈 block_name:default:chessboard_block: 棋盘方块 block_name:default:safe_block: 保险柜 - block_name:default:sofa: 沙发 - block_name:default:connectable_sofa: 沙发 \ No newline at end of file + block_name:default:sleeper_sofa: 沙发 + block_name:default:sofa: 沙发 \ No newline at end of file From 1cb63e21950a9db7fdfa85b176840787606b43cc Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 17:40:35 +0800 Subject: [PATCH 090/226] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BouncingBlockBehavior.java | 4 ++-- gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index f72f41ed8..f7895e804 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -34,9 +34,9 @@ public class BouncingBlockBehavior extends BukkitBlockBehavior implements Trigge public void fallOn(Object thisBlock, Object[] args, Callable superMethod) { if (this.fallDamageMultiplier <= 0.0) return; Object entity = args[3]; - Object finalFallDistance = VersionHelper.isOrAbove1_21_5() ? (double) args[4] * this.fallDamageMultiplier : (float) args[4] * (float) this.fallDamageMultiplier; + Number fallDistance = (Number) args[4]; FastNMS.INSTANCE.method$Entity$causeFallDamage( - entity, finalFallDistance, 1.0F, + entity, fallDistance.doubleValue() * this.fallDamageMultiplier, 1.0F, FastNMS.INSTANCE.method$DamageSources$fall(FastNMS.INSTANCE.method$Entity$damageSources(entity)) ); } diff --git a/gradle.properties b/gradle.properties index b366a9595..7f85f1eb8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.82 +nms_helper_version=1.0.83 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 6a744d9ee8ec32c6c746086a3f63929581a9e9bd Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 19:40:37 +0800 Subject: [PATCH 091/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dtoast=E5=AD=97?= =?UTF-8?q?=E4=BD=93=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/user/BukkitServerPlayer.java | 1 - .../core/plugin/context/function/ToastFunction.java | 11 +++++------ .../core/plugin/text/minimessage/ImageTag.java | 6 ++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 8292deb0d..c7367b2cf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -533,7 +533,6 @@ public class BukkitServerPlayer extends Player { if (optionalCustomState.isPresent()) { ImmutableBlockState customState = optionalCustomState.get(); Item tool = getItemInHand(InteractionHand.MAIN_HAND); - boolean isCorrectTool = FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(tool.getLiteralObject(), blockState); // 如果自定义方块在服务端侧未使用正确的工具,那么需要还原挖掘速度 if (!BlockStateUtils.isCorrectTool(customState, tool)) { progress *= customState.settings().incorrectToolSpeed(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java index eadb1609e..833a57d3c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java @@ -21,17 +21,16 @@ import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Optional; public class ToastFunction extends AbstractConditionalFunction { private final PlayerSelector selector; - private final TextProvider toast; + private final String toast; private final java.util.function.Function> icon; private final AdvancementType advancementType; public ToastFunction(List> predicates, @Nullable PlayerSelector selector, - TextProvider toast, + String toast, java.util.function.Function> icon, AdvancementType advancementType) { super(predicates); @@ -44,11 +43,11 @@ public class ToastFunction extends AbstractConditionalFunct @Override public void runInternal(CTX ctx) { if (this.selector == null) { - ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> it.sendToast(AdventureHelper.miniMessage().deserialize(this.toast.get(ctx), ctx.tagResolvers()), this.icon.apply(it), this.advancementType)); + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> it.sendToast(AdventureHelper.miniMessage().deserialize(this.toast, ctx.tagResolvers()), this.icon.apply(it), this.advancementType)); } else { for (Player viewer : this.selector.get(ctx)) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); - viewer.sendToast(AdventureHelper.miniMessage().deserialize(this.toast.get(relationalContext), relationalContext.tagResolvers()), this.icon.apply(viewer), this.advancementType); + viewer.sendToast(AdventureHelper.miniMessage().deserialize(this.toast, relationalContext.tagResolvers()), this.icon.apply(viewer), this.advancementType); } } } @@ -78,7 +77,7 @@ public class ToastFunction extends AbstractConditionalFunct return new ToastFunction<>( getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), - TextProviders.fromString(toast), + toast, player -> CraftEngine.instance().itemManager().createWrappedItem(item, player), advancementType ); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/ImageTag.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/ImageTag.java index 6ba90c439..e04d70f62 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/ImageTag.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/ImageTag.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.plugin.text.minimessage; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.Context; import net.kyori.adventure.text.minimessage.ParsingException; import net.kyori.adventure.text.minimessage.tag.Tag; @@ -12,7 +11,6 @@ import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; import java.util.Optional; public class ImageTag implements TagResolver { @@ -34,9 +32,9 @@ public class ImageTag implements TagResolver { if (arguments.hasNext()) { int row = arguments.popOr("No argument row provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); int column = arguments.popOr("No argument column provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); - return Tag.selfClosingInserting(Component.empty().children(List.of(optional.get().componentAt(row,column)))); + return Tag.selfClosingInserting(optional.get().componentAt(row,column)); } else { - return Tag.selfClosingInserting(Component.empty().children(List.of(optional.get().componentAt(0,0)))); + return Tag.selfClosingInserting(optional.get().componentAt(0,0)); } } else { throw ctx.newException("Invalid image id", arguments); From 2c1251ce247142e4709d269fa1410a220b45871c Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 19:44:02 +0800 Subject: [PATCH 092/226] =?UTF-8?q?=E8=AE=A9=E6=88=91=E4=BB=AC=E7=9A=84?= =?UTF-8?q?=E4=B8=8B=E7=95=8C=E5=90=88=E9=87=91=E7=A0=A7=E6=91=94=E4=B8=8D?= =?UTF-8?q?=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/resources/default/configuration/blocks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index f3b182d5b..23fbb2cf8 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -69,7 +69,6 @@ items#misc: overrides: tags: - minecraft:mineable/pickaxe - - minecraft:anvil sounds: break: minecraft:block.anvil.break step: minecraft:block.anvil.step From ce55b9c2f69e3292950850d3b4653410bdba7dda Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 20:04:01 +0800 Subject: [PATCH 093/226] =?UTF-8?q?=E5=A6=82=E6=9E=9C=E4=B8=8B=E7=95=8C?= =?UTF-8?q?=E5=90=88=E9=87=91=E7=A0=A7=E6=91=94=E6=AD=BB=E8=A6=81=E6=9C=89?= =?UTF-8?q?=E5=A3=B0=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/FallingBlockRemoveListener.java | 43 +++++++++++++------ .../block/behavior/FallingBlockBehavior.java | 18 ++++---- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java index 5be81793d..d97dd842b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java @@ -9,10 +9,12 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; import org.bukkit.entity.FallingBlock; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityRemoveEvent; import java.util.Optional; @@ -20,32 +22,47 @@ import java.util.Optional; public final class FallingBlockRemoveListener implements Listener { @EventHandler - public void onFallingBlockBreak(org.bukkit.event.entity.EntityRemoveEvent event) { - if (event.getCause() == org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP && event.getEntity() instanceof FallingBlock fallingBlock) { - try { + public void onFallingBlockBreak(EntityRemoveEvent event) { + if (!(event.getEntity() instanceof FallingBlock fallingBlock)) return; + try { + if (event.getCause() == EntityRemoveEvent.Cause.DROP) { Object fallingBlockEntity = CraftBukkitReflections.field$CraftEntity$entity.get(fallingBlock); - boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); - if (cancelDrop) return; Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; ImmutableBlockState customState = optionalCustomState.get(); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(fallingBlock.getWorld()); + World world = new BukkitWorld(fallingBlock.getWorld()); WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.FALLING_BLOCK, true) - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : customState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); + boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); + if (!cancelDrop) { + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.FALLING_BLOCK, true) + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : customState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); + } } Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); if (!isSilent) { world.playBlockSound(position, customState.settings().sounds().destroySound()); } - } catch (ReflectiveOperationException e) { - CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e); + } else if (event.getCause() == EntityRemoveEvent.Cause.DESPAWN) { + Object fallingBlockEntity = CraftBukkitReflections.field$CraftEntity$entity.get(fallingBlock); + Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCustomState.isEmpty()) return; + ImmutableBlockState customState = optionalCustomState.get(); + World world = new BukkitWorld(fallingBlock.getWorld()); + WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); + Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); + boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); + if (!isSilent) { + world.playBlockSound(position, customState.settings().sounds().destroySound()); + } } + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index 308e0168e..52173b8e8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; import java.util.Map; @@ -80,19 +81,20 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { if (VersionHelper.isOrAbove1_20_3()) return; Object level = args[0]; Object fallingBlockEntity = args[2]; - boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); - if (cancelDrop) return; Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; ImmutableBlockState customState = optionalCustomState.get(); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.FALLING_BLOCK, true) - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : customState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); + boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); + if (!cancelDrop) { + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.FALLING_BLOCK, true) + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : customState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); + } } Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); From ca7b2f0134977b0181225c2c04fc0526643e86e4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 20:17:37 +0800 Subject: [PATCH 094/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE=E7=9A=84=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/command/BukkitCommandManager.java | 3 +- .../DebugSaveDefaultResourcesCommand.java | 30 +++++++++++++++++++ common-files/src/main/resources/commands.yml | 9 +++++- .../core/pack/AbstractPackManager.java | 2 +- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSaveDefaultResourcesCommand.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 008bb53b8..9d80faf29 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -55,7 +55,8 @@ public class BukkitCommandManager extends AbstractCommandManager new DisableResourceCommand(this, plugin), new ListResourceCommand(this, plugin), new UploadPackCommand(this, plugin), - new SendResourcePackCommand(this, plugin) + new SendResourcePackCommand(this, plugin), + new DebugSaveDefaultResourcesCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSaveDefaultResourcesCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSaveDefaultResourcesCommand.java new file mode 100644 index 000000000..75525786e --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSaveDefaultResourcesCommand.java @@ -0,0 +1,30 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.pack.AbstractPackManager; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; + +public class DebugSaveDefaultResourcesCommand extends BukkitCommandFeature { + + public DebugSaveDefaultResourcesCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .handler(context -> { + AbstractPackManager packManager = (AbstractPackManager) CraftEngine.instance().packManager(); + packManager.saveDefaultConfigs(); + context.sender().sendMessage("Saved default configs"); + }); + } + + @Override + public String getFeatureID() { + return "debug_save_default_resources"; + } +} diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index 3ac0d8c5c..3a2b6c8de 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -198,11 +198,18 @@ debug_is_chunk_persistent_loaded: debug_entity_id: enable: true - permission: ce.command.debug.debug_entity_id + permission: ce.command.debug.entity_id usage: - /craftengine debug entity-id - /ce debug entity-id +debug_save_default_resources: + enable: true + permission: ce.command.debug.save_default_resources + usage: + - /craftengine debug save-default-resources + - /ce debug save-default-resources + debug_test: enable: true permission: ce.command.debug.test diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index ece216966..3e106923e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -354,7 +354,7 @@ public abstract class AbstractPackManager implements PackManager { } } - private void saveDefaultConfigs() { + public void saveDefaultConfigs() { // internal plugin.saveResource("resources/remove_shulker_head/resourcepack/pack.mcmeta"); plugin.saveResource("resources/remove_shulker_head/resourcepack/assets/minecraft/shaders/core/rendertype_entity_solid.fsh"); From 4d9dcd431a5713b441e0ebb9c28caad2f363ebd3 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 20:25:04 +0800 Subject: [PATCH 095/226] =?UTF-8?q?Revert=20"=E5=A6=82=E6=9E=9C=E4=B8=8B?= =?UTF-8?q?=E7=95=8C=E5=90=88=E9=87=91=E7=A0=A7=E6=91=94=E6=AD=BB=E8=A6=81?= =?UTF-8?q?=E6=9C=89=E5=A3=B0=E9=9F=B3"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ce55b9c2f69e3292950850d3b4653410bdba7dda. --- .../block/FallingBlockRemoveListener.java | 43 ++++++------------- .../block/behavior/FallingBlockBehavior.java | 18 ++++---- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java index d97dd842b..5be81793d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java @@ -9,12 +9,10 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; import org.bukkit.entity.FallingBlock; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityRemoveEvent; import java.util.Optional; @@ -22,47 +20,32 @@ import java.util.Optional; public final class FallingBlockRemoveListener implements Listener { @EventHandler - public void onFallingBlockBreak(EntityRemoveEvent event) { - if (!(event.getEntity() instanceof FallingBlock fallingBlock)) return; - try { - if (event.getCause() == EntityRemoveEvent.Cause.DROP) { + public void onFallingBlockBreak(org.bukkit.event.entity.EntityRemoveEvent event) { + if (event.getCause() == org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP && event.getEntity() instanceof FallingBlock fallingBlock) { + try { Object fallingBlockEntity = CraftBukkitReflections.field$CraftEntity$entity.get(fallingBlock); - Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); - Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); - if (optionalCustomState.isEmpty()) return; - ImmutableBlockState customState = optionalCustomState.get(); - World world = new BukkitWorld(fallingBlock.getWorld()); - WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); - if (!cancelDrop) { - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.FALLING_BLOCK, true) - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : customState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); - } - } - Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); - boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); - if (!isSilent) { - world.playBlockSound(position, customState.settings().sounds().destroySound()); - } - } else if (event.getCause() == EntityRemoveEvent.Cause.DESPAWN) { - Object fallingBlockEntity = CraftBukkitReflections.field$CraftEntity$entity.get(fallingBlock); + if (cancelDrop) return; Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; ImmutableBlockState customState = optionalCustomState.get(); - World world = new BukkitWorld(fallingBlock.getWorld()); + net.momirealms.craftengine.core.world.World world = new BukkitWorld(fallingBlock.getWorld()); WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.FALLING_BLOCK, true) + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : customState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); + } Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); if (!isSilent) { world.playBlockSound(position, customState.settings().sounds().destroySound()); } + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e); } - } catch (ReflectiveOperationException e) { - CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index 52173b8e8..308e0168e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -16,7 +16,6 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; import java.util.Map; @@ -81,20 +80,19 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { if (VersionHelper.isOrAbove1_20_3()) return; Object level = args[0]; Object fallingBlockEntity = args[2]; + boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); + if (cancelDrop) return; Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; ImmutableBlockState customState = optionalCustomState.get(); - World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); - boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); - if (!cancelDrop) { - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.FALLING_BLOCK, true) - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : customState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); - } + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.FALLING_BLOCK, true) + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : customState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); } Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); From d6b2241325a509344576e4695c96126f2e5acc25 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 20:26:55 +0800 Subject: [PATCH 096/226] =?UTF-8?q?=E6=92=A4=E9=94=80=E5=BA=A7=E4=BD=8D?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 - .../block/behavior/SeatBlockBehavior.java | 78 ------- .../block/entity/BukkitBlockEntityTypes.java | 1 - .../bukkit/block/entity/SeatBlockEntity.java | 212 ------------------ .../default/configuration/blocks.yml | 5 - 5 files changed, 298 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index f13476d45..34435ad14 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -32,7 +32,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); - public static final Key SEAT_BLOCK = Key.from("craftengine:seat_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -63,6 +62,5 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); - register(SEAT_BLOCK, SeatBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java deleted file mode 100644 index 7cd3d28a2..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.behavior; - -import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; -import net.momirealms.craftengine.bukkit.block.entity.SeatBlockEntity; -import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.core.block.BlockBehavior; -import net.momirealms.craftengine.core.block.CustomBlock; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; -import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; -import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.entity.BlockEntityType; -import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; -import net.momirealms.craftengine.core.entity.player.InteractionResult; -import net.momirealms.craftengine.core.item.context.UseOnContext; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.CEWorld; -import org.joml.Vector3f; - -import java.util.Map; - -public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { - public static final Factory FACTORY = new Factory(); - private final Vector3f offset; - private final float yaw; - private final boolean limitPlayerRotation; - - public SeatBlockBehavior(CustomBlock customBlock, Vector3f offset, float yaw, boolean limitPlayerRotation) { - super(customBlock); - this.offset = offset; - this.yaw = yaw; - this.limitPlayerRotation = limitPlayerRotation; - } - - @Override - public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { - BukkitServerPlayer player = (BukkitServerPlayer) context.getPlayer(); - if (player == null || player.isSecondaryUseActive()) { - return InteractionResult.PASS; - } - player.swingHand(context.getHand()); - CEWorld world = context.getLevel().storageWorld(); - BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos()); - if (!(blockEntity instanceof SeatBlockEntity seatBlockEntity) || !seatBlockEntity.seatEntities().isEmpty()) { - return InteractionResult.PASS; - } - seatBlockEntity.spawnSeatEntityForPlayer(player.platformPlayer(), this.offset, this.yaw, this.limitPlayerRotation); - return InteractionResult.SUCCESS_AND_CANCEL; - } - - @SuppressWarnings("unchecked") - @Override - public BlockEntityType blockEntityType() { - return (BlockEntityType) BukkitBlockEntityTypes.SEAT; - } - - @Override - public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { - return new SeatBlockEntity(pos, state); - } - - @Override - public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { - return EntityBlockBehavior.createTickerHelper(SeatBlockEntity::tick); - } - - public static class Factory implements BlockBehaviorFactory { - - @Override - public BlockBehavior create(CustomBlock block, Map arguments) { - Vector3f offset = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("offset", "0,0,0"), "offset"); - float yaw = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"); - boolean limitPlayerRotation = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("limit-player-rotation", true), "limit-player-rotation"); - return new SeatBlockBehavior(block, offset, yaw, limitPlayerRotation); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java index c739d0bc4..3e778bd32 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -6,5 +6,4 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; public class BukkitBlockEntityTypes extends BlockEntityTypes { public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); - public static final BlockEntityType SEAT = register(BlockEntityTypeKeys.SEAT, SeatBlockEntity::new); } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java deleted file mode 100644 index e39b6c2ae..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java +++ /dev/null @@ -1,212 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.entity; - -import com.google.common.collect.ImmutableList; -import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; -import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.util.EntityUtils; -import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.util.HorizontalDirection; -import net.momirealms.craftengine.core.util.QuaternionUtils; -import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.CEWorld; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; - -import java.util.Map; -import java.util.Objects; - -@SuppressWarnings("DuplicatedCode") -public class SeatBlockEntity extends BlockEntity { - private final Map seatEntities = new Reference2ObjectArrayMap<>(1); - - public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState) { - super(BukkitBlockEntityTypes.SEAT, pos, blockState); - } - - public Map seatEntities() { - return this.seatEntities; - } - - public static void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, SeatBlockEntity seat) { - int size = seat.seatEntities.size(); - if (size == 0) return; - if (size == 1) { - // 99.9999%的情况下会命中这里 - Map.Entry entry = seat.seatEntities.entrySet().iterator().next(); - Entity entity = entry.getKey(); - Player player = entry.getValue(); - if (VersionHelper.isFolia()) { - entity.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), t -> { - if (entity.getPassengers().isEmpty()) { - seat.tryLeavingSeat(player, entity); - seat.seatEntities.remove(entity); - } - }, null); - } else { - if (entity.getPassengers().isEmpty()) { - seat.tryLeavingSeat(player, entity); - seat.seatEntities.remove(entity); - } - } - return; - } - for (Map.Entry entry : ImmutableList.copyOf(seat.seatEntities.entrySet())) { - // 几乎不可能命中这里,除非写出bug了 - Entity entity = entry.getKey(); - Player player = entry.getValue(); - if (VersionHelper.isFolia()) { - entity.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), t -> { - if (entity.getPassengers().isEmpty()) { - seat.tryLeavingSeat(player, entity); - seat.seatEntities.remove(entity); - } - }, null); - } else { - if (entity.getPassengers().isEmpty()) { - seat.tryLeavingSeat(player, entity); - seat.seatEntities.remove(entity); - } - } - } - } - - @Override - public void preRemove() { - if (this.seatEntities.isEmpty()) return; - try { - this.seatEntities.keySet().forEach(Entity::remove); - } finally { - this.seatEntities.clear(); - } - } - - public void spawnSeatEntityForPlayer(@NotNull Player player, @NotNull Vector3f offset, float yaw, boolean limitPlayerRotation) { - if (!this.seatEntities.isEmpty() || !this.isValid()) return; - Location location = calculateSeatLocation(player, this.pos, this.blockState, offset, yaw); - Entity seatEntity = limitPlayerRotation ? - EntityUtils.spawnEntity(player.getWorld(), - VersionHelper.isOrAbove1_20_2() ? location.subtract(0, 0.9875, 0) : location.subtract(0, 0.990625, 0), - EntityType.ARMOR_STAND, - entity -> { - ArmorStand armorStand = (ArmorStand) entity; - if (VersionHelper.isOrAbove1_21_3()) { - Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01); - } else { - LegacyAttributeUtils.setMaxHealth(armorStand); - } - armorStand.setSmall(true); - armorStand.setInvisible(true); - armorStand.setSilent(true); - armorStand.setInvulnerable(true); - armorStand.setArms(false); - armorStand.setCanTick(false); - armorStand.setAI(false); - armorStand.setGravity(false); - armorStand.setPersistent(false); - }) : - EntityUtils.spawnEntity(player.getWorld(), - VersionHelper.isOrAbove1_20_2() ? location : location.subtract(0, 0.25, 0), - EntityType.ITEM_DISPLAY, - entity -> { - ItemDisplay itemDisplay = (ItemDisplay) entity; - itemDisplay.setPersistent(false); - }); - if (!seatEntity.addPassenger(player)) { - seatEntity.remove(); - return; - } - this.seatEntities.put(seatEntity, player); - } - - private Location calculateSeatLocation(Player player, BlockPos pos, ImmutableBlockState state, Vector3f offset, float yaw) { - Location location = new Location(player.getWorld(), pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5); - for (Property property : state.getProperties()) { - if (property.name().equals("facing") && property.valueClass() == HorizontalDirection.class) { - switch ((HorizontalDirection) state.get(property)) { - case NORTH -> location.setYaw(0); - case SOUTH -> location.setYaw(180); - case WEST -> location.setYaw(270); - case EAST -> location.setYaw(90); - } - break; - } - if (property.name().equals("facing_clockwise") && property.valueClass() == HorizontalDirection.class) { - switch ((HorizontalDirection) state.get(property)) { - case NORTH -> location.setYaw(90); - case SOUTH -> location.setYaw(270); - case WEST -> location.setYaw(0); - case EAST -> location.setYaw(180); - } - break; - } - } - Vector3f newOffset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - location.getYaw()), 0) - .conjugate() - .transform(new Vector3f(offset)); - double newYaw = yaw + location.getYaw(); - if (newYaw < -180) newYaw += 360; - Location newLocation = location.clone(); - newLocation.setYaw((float) newYaw); - newLocation.add(newOffset.x, newOffset.y + 0.6, -newOffset.z); - return newLocation; - } - - private void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) { - vehicle.remove(); - if (player.getVehicle() != null) return; - Location vehicleLocation = vehicle.getLocation(); - Location originalLocation = vehicleLocation.clone(); - originalLocation.setY(this.pos.y()); - Location targetLocation = originalLocation.clone().add(vehicleLocation.getDirection().multiply(1.1)); - if (!isSafeLocation(targetLocation)) { - targetLocation = findSafeLocationNearby(originalLocation); - if (targetLocation == null) return; - } - targetLocation.setYaw(player.getLocation().getYaw()); - targetLocation.setPitch(player.getLocation().getPitch()); - if (VersionHelper.isFolia()) { - player.teleportAsync(targetLocation); - } else { - player.teleport(targetLocation); - } - } - - private boolean isSafeLocation(Location location) { - World world = location.getWorld(); - if (world == null) return false; - int x = location.getBlockX(); - int y = location.getBlockY(); - int z = location.getBlockZ(); - if (!world.getBlockAt(x, y - 1, z).getType().isSolid()) return false; - if (!world.getBlockAt(x, y, z).isPassable()) return false; - return world.getBlockAt(x, y + 1, z).isPassable(); - } - - @Nullable - private Location findSafeLocationNearby(Location center) { - World world = center.getWorld(); - if (world == null) return null; - int centerX = center.getBlockX(); - int centerY = center.getBlockY(); - int centerZ = center.getBlockZ(); - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) continue; - int x = centerX + dx; - int z = centerZ + dz; - Location nearbyLocation = new Location(world, x + 0.5, centerY, z + 0.5); - if (isSafeLocation(nearbyLocation)) return nearbyLocation; - } - } - return null; - } -} diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 23fbb2cf8..8de04abc3 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -590,9 +590,6 @@ items#misc: - type: bouncing_block bounce-height: 0.66 sync-player-position: false - - type: seat_block - offset: 0,-0.5,0 - limit-player-rotation: false state: id: 0 state: white_bed[facing=west,occupied=false,part=foot] @@ -640,8 +637,6 @@ items#misc: - type: sofa_block - type: bouncing_block bounce-height: 0.66 - - type: seat_block - offset: 0,-0.45,0 states: properties: facing: From df91e5ee8ef295f2cbdd5cf4160cfbe6dcc5f1d6 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 20:36:30 +0800 Subject: [PATCH 097/226] =?UTF-8?q?=E7=AE=80=E5=8C=96=E6=97=A0=E6=84=8F?= =?UTF-8?q?=E4=B9=89=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/block/behavior/EntityBlockBehavior.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java index 5df15876e..b5df01017 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java @@ -20,7 +20,7 @@ public interface EntityBlockBehavior { } @SuppressWarnings("unchecked") - static BlockEntityTicker createTickerHelper(BlockEntityTicker ticker) { - return (BlockEntityTicker) ticker; + static BlockEntityTicker createTickerHelper(BlockEntityTicker ticker) { + return (BlockEntityTicker) ticker; } } From 9128ae9a51a0583931544d59b4a7aa2e976d00a8 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 20:37:22 +0800 Subject: [PATCH 098/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcondition=20component?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../condition/ComponentConditionProperty.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/ComponentConditionProperty.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/ComponentConditionProperty.java index 379ffab71..b0907744e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/ComponentConditionProperty.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/ComponentConditionProperty.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.core.pack.model.condition; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -10,9 +12,9 @@ public class ComponentConditionProperty implements ConditionProperty { public static final Factory FACTORY = new Factory(); public static final Reader READER = new Reader(); private final String predicate; - private final String value; + private final JsonElement value; - public ComponentConditionProperty(String predicate, String value) { + public ComponentConditionProperty(String predicate, JsonElement value) { this.predicate = predicate; this.value = value; } @@ -26,15 +28,15 @@ public class ComponentConditionProperty implements ConditionProperty { public void accept(JsonObject jsonObject) { jsonObject.addProperty("property", type().toString()); jsonObject.addProperty("predicate", this.predicate); - jsonObject.addProperty("value", this.value); + jsonObject.add("value", this.value); } public static class Factory implements ConditionPropertyFactory { @Override public ConditionProperty create(Map arguments) { String predicate = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("predicate"), "warning.config.item.model.condition.component.missing_predicate"); - String value = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value"), "warning.config.item.model.condition.component.missing_value"); - return new ComponentConditionProperty(predicate, value); + JsonElement jsonElement = GsonHelper.get().toJsonTree(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("value"), "warning.config.item.model.condition.component.missing_value")); + return new ComponentConditionProperty(predicate, jsonElement); } } @@ -42,7 +44,7 @@ public class ComponentConditionProperty implements ConditionProperty { @Override public ConditionProperty read(JsonObject json) { String predicate = json.get("predicate").getAsString(); - String value = json.get("value").getAsString(); + JsonElement value = json.get("value"); return new ComponentConditionProperty(predicate, value); } } From 2d7a2eec0a02f4152617a5a6bb8a5800f1309efd Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 21:53:04 +0800 Subject: [PATCH 099/226] =?UTF-8?q?=E9=87=8D=E7=BD=AE=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/SimpleStorageBlockBehavior.java | 2 +- .../TextDisplayBlockEntityElementConfig.java | 6 - .../plugin/network/PacketConsumers.java | 1 - .../default/configuration/block_name.yml | 60 -- .../default/configuration/blocks.yml | 933 ------------------ .../configuration/blocks/chessboard_block.yml | 84 ++ .../configuration/blocks/chinese_lantern.yml | 51 + .../configuration/blocks/copper_coil.yml | 80 ++ .../blocks/ender_pearl_flower.yml | 145 +++ .../configuration/blocks/fairy_flower.yml | 52 + .../configuration/blocks/flame_cane.yml | 112 +++ .../configuration/blocks/gunpowder_block.yml | 91 ++ .../configuration/blocks/netherite_anvil.yml | 94 ++ .../configuration/{ => blocks}/palm_tree.yml | 0 .../default/configuration/blocks/pebble.yml | 130 +++ .../default/configuration/blocks/reed.yml | 42 + .../configuration/blocks/safe_block.yml | 134 +++ .../default/configuration/blocks/sofa.yml | 185 ++++ .../configuration/blocks/table_lamp.yml | 125 +++ .../{ores.yml => blocks/topaz_ore.yml} | 6 +- .../default/configuration/categories.yml | 14 +- .../resources/default/configuration/emoji.yml | 21 - .../default/configuration/furniture/bench.yml | 44 + .../flower_basket.yml} | 96 +- .../configuration/furniture/wooden_chair.yml | 41 + .../resources/default/configuration/i18n.yml | 64 +- .../resources/default/configuration/icons.yml | 9 - .../default/configuration/items/cap.yml | 16 + .../configuration/items/flame_elytra.yml | 17 + .../default/configuration/items/gui_head.yml | 31 + .../configuration/items/topaz_armor.yml | 104 ++ .../topaz_tool_weapon.yml} | 168 +--- .../default/configuration/plants.yml | 350 ------- .../minecraft/textures/font/image/icons.png | Bin 332 -> 0 bytes .../configuration/fix_client_visual.yml | 0 .../core/pack/AbstractPackManager.java | 71 +- .../context/function/ToastFunction.java | 3 - .../gui/category/ItemBrowserManagerImpl.java | 22 +- 38 files changed, 1705 insertions(+), 1699 deletions(-) delete mode 100644 common-files/src/main/resources/resources/default/configuration/block_name.yml delete mode 100644 common-files/src/main/resources/resources/default/configuration/blocks.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml rename common-files/src/main/resources/resources/default/configuration/{ => blocks}/palm_tree.yml (100%) create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/reed.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml rename common-files/src/main/resources/resources/default/configuration/{ores.yml => blocks/topaz_ore.yml} (97%) create mode 100644 common-files/src/main/resources/resources/default/configuration/furniture/bench.yml rename common-files/src/main/resources/resources/default/configuration/{furniture.yml => furniture/flower_basket.yml} (54%) create mode 100644 common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml delete mode 100644 common-files/src/main/resources/resources/default/configuration/icons.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/items/cap.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/items/gui_head.yml create mode 100644 common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml rename common-files/src/main/resources/resources/default/configuration/{items.yml => items/topaz_tool_weapon.yml} (67%) delete mode 100644 common-files/src/main/resources/resources/default/configuration/plants.yml delete mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/font/image/icons.png rename common-files/src/main/resources/resources/{default => internal}/configuration/fix_client_visual.yml (100%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java index 65ae93bad..c745de2d1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -51,7 +51,7 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E SoundData openSound, SoundData closeSound, boolean hasAnalogOutputSignal, - Property openProperty) { + @Nullable Property openProperty) { super(customBlock); this.containerTitle = containerTitle; this.rows = rows; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java index 1a105d91d..88fee6a01 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java @@ -1,21 +1,15 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element; import net.kyori.adventure.text.Component; -import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData; -import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.entity.Billboard; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; -import net.momirealms.craftengine.core.plugin.context.text.TextProvider; import net.momirealms.craftengine.core.util.AdventureHelper; -import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import org.joml.Quaternionf; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 6bd1915bd..87d00a5c0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -41,7 +41,6 @@ import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.entity.furniture.HitBox; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.font.FontManager; import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; diff --git a/common-files/src/main/resources/resources/default/configuration/block_name.yml b/common-files/src/main/resources/resources/default/configuration/block_name.yml deleted file mode 100644 index 97e421dac..000000000 --- a/common-files/src/main/resources/resources/default/configuration/block_name.yml +++ /dev/null @@ -1,60 +0,0 @@ -# This file is for localizing internal block IDs (craftengine:xxx_xx). -# Some other plugins support displaying block names using lang components. -# This might be useful for the client-side, but it's not mandatory. -lang: - en_us: - block_name:default:chinese_lantern: Chinese Lantern - block_name:default:netherite_anvil: Netherite Anvil - block_name:default:topaz_ore: Topaz Ore - block_name:default:deepslate_topaz_ore: Deepslate Topaz Ore - block_name:default:palm_log: Palm Log - block_name:default:stripped_palm_log: Stripped Palm Log - block_name:default:palm_wood: Palm Wood - block_name:default:stripped_palm_wood: Stripped Palm Wood - block_name:default:palm_planks: Palm Planks - block_name:default:palm_sapling: Palm Sapling - block_name:default:palm_leaves: Palm Leaves - block_name:default:palm_trapdoor: Palm Trapdoor - block_name:default:palm_door: Palm Door - block_name:default:palm_fence_gate: Palm Fence Gate - block_name:default:palm_slab: Palm Slab - block_name:default:palm_stairs: Palm Stairs - block_name:default:fairy_flower: Fairy Flower - block_name:default:reed: Reed - block_name:default:flame_cane: Flame Cane - block_name:default:ender_pearl_flower: Ender Pearl Flower - block_name:default:gunpowder_block: GunPowder Block - block_name:default:solid_gunpowder_block: Solid GunPowder Block - block_name:default:copper_coil: Copper Coil - block_name:default:chessboard_block: Chessboard Block - block_name:default:safe_block: Safe Block - block_name:default:sleeper_sofa: Sofa - block_name:default:sofa: Sofa - zh_cn: - block_name:default:chinese_lantern: 灯笼 - block_name:default:netherite_anvil: 下界合金砧 - block_name:default:topaz_ore: 黄玉矿石 - block_name:default:deepslate_topaz_ore: 深层黄玉矿石 - block_name:default:palm_log: 棕榈原木 - block_name:default:stripped_palm_log: 去皮棕榈原木 - block_name:default:palm_wood: 棕榈木 - block_name:default:stripped_palm_wood: 去皮棕榈木 - block_name:default:palm_planks: 棕榈木板 - block_name:default:palm_sapling: 棕榈树苗 - block_name:default:palm_leaves: 棕榈树叶 - block_name:default:palm_trapdoor: 棕榈木活板门 - block_name:default:palm_door: 棕榈木门 - block_name:default:palm_fence_gate: 棕榈木栅栏门 - block_name:default:palm_slab: 棕榈木台阶 - block_name:default:palm_stairs: 棕榈木楼梯 - block_name:default:fairy_flower: 仙灵花 - block_name:default:reed: 芦苇 - block_name:default:flame_cane: 烈焰甘蔗 - block_name:default:ender_pearl_flower: 末影珍珠花 - block_name:default:gunpowder_block: 火药粉末 - block_name:default:solid_gunpowder_block: 凝固火药块 - block_name:default:copper_coil: 铜线圈 - block_name:default:chessboard_block: 棋盘方块 - block_name:default:safe_block: 保险柜 - block_name:default:sleeper_sofa: 沙发 - block_name:default:sofa: 沙发 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml deleted file mode 100644 index 8de04abc3..000000000 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ /dev/null @@ -1,933 +0,0 @@ -items#misc: - default:chinese_lantern: - material: nether_brick - custom-model-data: 3000 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/chinese_lantern - generation: - parent: minecraft:block/custom/chinese_lantern - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: - - default:hardness/wool - - default:burn_data/planks - - default:sound/wood - - default:settings/solid_1x1x1 - overrides: - push-reaction: NORMAL - instrument: HARP - luminance: 15 - map-color: 36 - state: - id: 15 - state: note_block:15 - model: - path: minecraft:block/custom/chinese_lantern - generation: - parent: minecraft:block/cube_column - textures: - end: minecraft:block/custom/chinese_lantern_top - side: minecraft:block/custom/chinese_lantern - default:netherite_anvil: - material: nether_brick - custom-model-data: 3001 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/netherite_anvil - generation: - parent: minecraft:block/custom/netherite_anvil - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - behavior: - type: falling_block - hurt-amount: 4 - max-hurt: 80 - events: - - on: right_click - functions: - - type: open_window - gui-type: anvil - - type: cancel_event - conditions: - - type: expression - expression: '!' - settings: - template: - - default:pickaxe_power/level_4 - overrides: - tags: - - minecraft:mineable/pickaxe - sounds: - break: minecraft:block.anvil.break - step: minecraft:block.anvil.step - place: minecraft:block.anvil.place - hit: minecraft:block.anvil.hit - fall: minecraft:block.anvil.fall - land: minecraft:block.anvil.land - destroy: minecraft:block.anvil.destroy - map-color: 29 - hardness: 10.0 - resistance: 1200 - push-reaction: BLOCK - states: - properties: - facing_clockwise: - type: 4-direction - default: north - appearances: - axisX: - state: minecraft:anvil[facing=east] - model: - path: minecraft:block/custom/netherite_anvil - y: 90 - generation: - parent: minecraft:block/anvil - textures: - top: minecraft:block/custom/netherite_anvil_top - body: minecraft:block/custom/netherite_anvil - particle: minecraft:block/custom/netherite_anvil - axisZ: - state: minecraft:anvil[facing=north] - model: - path: minecraft:block/custom/netherite_anvil - variants: - facing_clockwise=east: - appearance: axisX - id: 0 - facing_clockwise=west: - appearance: axisX - id: 1 - facing_clockwise=north: - appearance: axisZ - id: 2 - facing_clockwise=south: - appearance: axisZ - id: 3 - default:gunpowder_block: - material: nether_brick - custom-model-data: 3002 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/gunpowder_block - generation: - parent: minecraft:block/custom/gunpowder_block - behavior: - type: block_item - block: - behaviors: - - type: concrete_powder_block - solid-block: default:solid_gunpowder_block - - type: falling_block - loot: - template: default:loot_table/self - settings: - template: - - default:sound/sand - - default:settings/solid_1x1x1 - overrides: - hardness: 0.5 - resistance: 0.5 - instrument: SNARE - map-color: 45 - state: - id: 16 - state: note_block:16 - model: - path: minecraft:block/custom/gunpowder_block - generation: - parent: minecraft:block/cube_all - textures: - all: minecraft:block/custom/gunpowder_block - default:solid_gunpowder_block: - material: nether_brick - custom-model-data: 3003 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/solid_gunpowder_block - generation: - parent: minecraft:block/custom/solid_gunpowder_block - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/stone - - default:pickaxe_power/level_1 - - default:settings/solid_1x1x1 - overrides: - hardness: 1.8 - resistance: 1.8 - instrument: BASEDRUM - map-color: 45 - state: - id: 17 - state: note_block:17 - model: - path: minecraft:block/custom/solid_gunpowder_block - generation: - parent: minecraft:block/cube_all - textures: - all: minecraft:block/custom/solid_gunpowder_block - default:copper_coil: - material: nether_brick - custom-model-data: 3004 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/copper_coil - generation: - parent: minecraft:block/custom/copper_coil - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/metal - - default:pickaxe_power/level_1 - overrides: - hardness: 3.0 - resistance: 4.5 - replaceable: false - is-redstone-conductor: true - is-suffocating: true - instrument: BASEDRUM - map-color: 15 - behavior: - type: lamp_block - states: - properties: - lit: - type: boolean - default: false - appearances: - off: - state: cactus:0 - model: - path: minecraft:block/custom/copper_coil - generation: - parent: minecraft:block/cactus - textures: - particle: minecraft:block/custom/copper_coil - bottom: minecraft:block/custom/copper_coil - top: minecraft:block/custom/copper_coil - side: minecraft:block/custom/copper_coil_side - on: - state: cactus:1 - model: - path: minecraft:block/custom/copper_coil_on - generation: - parent: minecraft:block/cactus - textures: - particle: minecraft:block/custom/copper_coil_on - bottom: minecraft:block/custom/copper_coil_on - top: minecraft:block/custom/copper_coil_on - side: minecraft:block/custom/copper_coil_on_side - variants: - lit=false: - appearance: 'off' - id: 0 - lit=true: - appearance: 'on' - id: 1 - settings: - luminance: 8 - default:pebble: - material: nether_brick - custom-model-data: 3005 - data: - item-name: - model: - template: default:model/simplified_generated - arguments: - path: minecraft:item/custom/pebble - behavior: - - type: block_item - block: - settings: - template: - - default:sound/stone - - default:hardness/none - overrides: - map-color: 11 - push-reaction: DESTROY - behaviors: - - type: sturdy_base_block - direction: down - support-types: - - full - - type: stackable_block - property: pebble - items: - - default:pebble - sounds: - stack: minecraft:block.stone.fall - loot: - pools: - - rolls: 1 - entries: - - type: item - item: default:pebble - functions: - - type: set_count - count: 3 - add: false - conditions: - - type: match_block_property - properties: - pebble: 3 - - type: set_count - count: 2 - add: false - conditions: - - type: match_block_property - properties: - pebble: 2 - - type: explosion_decay - states: - properties: - pebble: - type: int - range: 1~3 - default: 1 - appearances: - one: - state: tripwire:2 - models: - - path: minecraft:block/custom/pebble_1 - weight: 1 - - path: minecraft:block/custom/pebble_1 - weight: 1 - y: 90 - - path: minecraft:block/custom/pebble_1 - weight: 1 - y: 180 - - path: minecraft:block/custom/pebble_1 - weight: 1 - y: 270 - two: - state: tripwire:3 - models: - - path: minecraft:block/custom/pebble_2 - weight: 1 - - path: minecraft:block/custom/pebble_2 - weight: 1 - y: 90 - - path: minecraft:block/custom/pebble_2 - weight: 1 - y: 180 - - path: minecraft:block/custom/pebble_2 - weight: 1 - y: 270 - three: - state: tripwire:4 - models: - - path: minecraft:block/custom/pebble_3 - weight: 1 - - path: minecraft:block/custom/pebble_3 - weight: 1 - y: 90 - - path: minecraft:block/custom/pebble_3 - weight: 1 - y: 180 - - path: minecraft:block/custom/pebble_3 - weight: 1 - y: 270 - variants: - pebble=1: - appearance: 'one' - id: 2 - pebble=2: - appearance: 'two' - id: 3 - pebble=3: - appearance: 'three' - id: 4 - default:chessboard_block: - material: nether_brick - custom-model-data: 3006 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/chessboard_block - generation: - parent: minecraft:block/custom/chessboard_block - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - hardness: 1.4 - resistance: 1.4 - is-suffocating: true - is-redstone-conductor: true - push-reaction: PUSH_ONLY - instrument: BASEDRUM - map-color: 36 - sounds: - break: minecraft:block.stone.break - fall: minecraft:block.stone.fall - hit: minecraft:block.stone.hit - place: minecraft:block.stone.place - step: minecraft:block.stone.step - states: - properties: - facing: - type: 4-direction - default: north - appearances: - east: - state: note_block:18 - model: - path: minecraft:block/custom/chessboard_block - y: 270 - generation: - parent: minecraft:block/template_glazed_terracotta - textures: - pattern: minecraft:block/custom/chessboard_block - north: - state: note_block:19 - model: - path: minecraft:block/custom/chessboard_block - y: 180 - south: - state: note_block:20 - model: - path: minecraft:block/custom/chessboard_block - west: - state: note_block:21 - model: - path: minecraft:block/custom/chessboard_block - y: 90 - variants: - facing=east: - appearance: east - id: 18 - facing=north: - appearance: north - id: 19 - facing=south: - appearance: south - id: 20 - facing=west: - appearance: west - id: 21 - default:safe_block: - material: nether_brick - custom-model-data: 3007 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/safe_block - generation: - parent: minecraft:block/custom/safe_block - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - hardness: 5 - resistance: 5 - is-suffocating: true - is-redstone-conductor: true - push-reaction: BLOCK - instrument: BASEDRUM - map-color: 6 - sounds: - break: minecraft:block.stone.break - fall: minecraft:block.stone.fall - hit: minecraft:block.stone.hit - place: minecraft:block.stone.place - step: minecraft:block.stone.step - behavior: - type: simple_storage_block - title: "" - rows: 1 - sounds: - open: minecraft:block.iron_trapdoor.open - close: minecraft:block.iron_trapdoor.close - states: - properties: - facing: - type: 4-direction - default: north - open: - type: boolean - default: false - appearances: - east: - state: note_block:22 - model: - path: minecraft:block/custom/safe_block - y: 90 - generation: - parent: minecraft:block/orientable - textures: - front: minecraft:block/custom/safe_block_front - side: minecraft:block/custom/safe_block_side - top: minecraft:block/custom/safe_block_top - east_open: - state: note_block:23 - model: - path: minecraft:block/custom/safe_block_open - y: 90 - generation: - parent: minecraft:block/orientable - textures: - front: minecraft:block/custom/safe_block_front_open - side: minecraft:block/custom/safe_block_side - top: minecraft:block/custom/safe_block_top - north: - state: note_block:24 - model: - path: minecraft:block/custom/safe_block - north_open: - state: note_block:25 - model: - path: minecraft:block/custom/safe_block_open - south: - state: note_block:26 - model: - path: minecraft:block/custom/safe_block - y: 180 - south_open: - state: note_block:27 - model: - path: minecraft:block/custom/safe_block_open - y: 180 - west: - state: note_block:28 - model: - path: minecraft:block/custom/safe_block - y: 270 - west_open: - state: note_block:29 - model: - path: minecraft:block/custom/safe_block_open - y: 270 - variants: - facing=east,open=false: - appearance: east - id: 22 - facing=east,open=true: - appearance: east_open - id: 23 - facing=north,open=false: - appearance: north - id: 24 - facing=north,open=true: - appearance: north_open - id: 25 - facing=south,open=false: - appearance: south - id: 26 - facing=south,open=true: - appearance: south_open - id: 27 - facing=west,open=false: - appearance: west - id: 28 - facing=west,open=true: - appearance: west_open - id: 29 - default:sleeper_sofa: - material: nether_brick - custom-model-data: 3008 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/sleeper_sofa - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - hardness: 0.5 - resistance: 0.5 - map-color: 27 - burn-chance: 5 - fire-spread-chance: 20 - burnable: true - is-suffocating: false - is-redstone-conductor: false - push-reaction: BLOCK - instrument: BASS - sounds: - break: minecraft:block.wood.break - fall: minecraft:block.wood.fall - hit: minecraft:block.wood.hit - place: minecraft:block.wood.place - step: minecraft:block.wood.step - tags: - - minecraft:mineable/axe - behaviors: - - type: bouncing_block - bounce-height: 0.66 - sync-player-position: false - state: - id: 0 - state: white_bed[facing=west,occupied=false,part=foot] - entity-renderer: - item: default:sleeper_sofa - default:sofa_inner: - material: nether_brick - custom-model-data: 3009 - model: - type: minecraft:model - path: minecraft:item/custom/sofa_inner - default:sofa: - material: nether_brick - custom-model-data: 3010 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/sofa - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - hardness: 0.5 - resistance: 0.5 - map-color: 27 - burn-chance: 5 - fire-spread-chance: 20 - burnable: true - is-suffocating: false - is-redstone-conductor: false - push-reaction: BLOCK - instrument: BASS - sounds: - break: minecraft:block.wood.break - fall: minecraft:block.wood.fall - hit: minecraft:block.wood.hit - place: minecraft:block.wood.place - step: minecraft:block.wood.step - tags: - - minecraft:mineable/axe - behaviors: - - type: sofa_block - - type: bouncing_block - bounce-height: 0.66 - states: - properties: - facing: - type: horizontal_direction - shape: - type: sofa_shape - appearances: - facing=east,shape=straight: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa - yaw: 90 - facing=north,shape=straight: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa - facing=south,shape=straight: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa - yaw: 180 - facing=west,shape=straight: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa - yaw: 270 - facing=east,shape=inner_left: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - facing=north,shape=inner_left: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - yaw: 270 - facing=south,shape=inner_left: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - yaw: 90 - facing=west,shape=inner_left: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - yaw: 180 - facing=east,shape=inner_right: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - yaw: 90 - facing=north,shape=inner_right: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - facing=south,shape=inner_right: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - yaw: 180 - facing=west,shape=inner_right: - state: barrier[waterlogged=false] - entity-renderer: - item: default:sofa_inner - yaw: 270 - variants: - facing=east,shape=inner_left: - appearance: facing=east,shape=inner_left - id: 0 - facing=east,shape=inner_right: - appearance: facing=east,shape=inner_right - id: 1 - facing=east,shape=straight: - appearance: facing=east,shape=straight - id: 2 - facing=north,shape=inner_left: - appearance: facing=north,shape=inner_left - id: 3 - facing=north,shape=inner_right: - appearance: facing=north,shape=inner_right - id: 4 - facing=north,shape=straight: - appearance: facing=north,shape=straight - id: 5 - facing=south,shape=inner_left: - appearance: facing=south,shape=inner_left - id: 6 - facing=south,shape=inner_right: - appearance: facing=south,shape=inner_right - id: 7 - facing=south,shape=straight: - appearance: facing=south,shape=straight - id: 8 - facing=west,shape=inner_left: - appearance: facing=west,shape=inner_left - id: 9 - facing=west,shape=inner_right: - appearance: facing=west,shape=inner_right - id: 10 - facing=west,shape=straight: - appearance: facing=west,shape=straight - id: 11 - default:table_lamp: - material: nether_brick - custom-model-data: 3011 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/table_lamp - behavior: - type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/metal - overrides: - hardness: 1 - resistance: 1 - replaceable: false - is-redstone-conductor: false - is-suffocating: false - behaviors: - - type: toggleable_lamp_block - can-open-with-hand: true - - type: sturdy_base_block - direction: down - support-types: - - full - - center - states: - properties: - lit: - type: boolean - default: false - facing: - type: 4-direction - default: north - appearances: - east_off: - state: barrier - entity-renderer: - item: default:table_lamp - north_off: - state: barrier - entity-renderer: - item: default:table_lamp - yaw: -90 - south_off: - state: barrier - entity-renderer: - item: default:table_lamp - yaw: 90 - west_off: - state: barrier - entity-renderer: - item: default:table_lamp - yaw: 180 - east_on: - state: barrier - entity-renderer: - item: default:table_lamp - north_on: - state: barrier - entity-renderer: - item: default:table_lamp - yaw: -90 - south_on: - state: barrier - entity-renderer: - item: default:table_lamp - yaw: 90 - west_on: - state: barrier - entity-renderer: - item: default:table_lamp - yaw: 180 - variants: - facing=east,lit=false: - appearance: east_off - id: 12 - facing=north,lit=false: - appearance: north_off - id: 13 - facing=south,lit=false: - appearance: south_off - id: 14 - facing=west,lit=false: - appearance: west_off - id: 15 - facing=east,lit=true: - appearance: east_on - id: 16 - settings: - luminance: 15 - facing=north,lit=true: - appearance: north_on - id: 17 - settings: - luminance: 15 - facing=south,lit=true: - appearance: south_on - id: 18 - settings: - luminance: 15 - facing=west,lit=true: - appearance: west_on - id: 19 - settings: - luminance: 15 -recipes#misc: - default:chinese_lantern: - type: shaped - pattern: - - ABA - - BCB - - ABA - ingredients: - A: '#minecraft:planks' - B: minecraft:stick - C: minecraft:torch - result: - id: default:chinese_lantern - count: 1 - default:netherite_anvil: - type: shaped - pattern: - - ' B ' - - BAB - - ' B ' - ingredients: - A: minecraft:anvil - B: minecraft:netherite_ingot - result: - id: default:netherite_anvil - count: 1 - default:gunpowder_from_block: - type: shapeless - ingredients: - A: default:gunpowder_block - result: - id: minecraft:gunpowder - count: 9 - default:gunpowder_block: - type: shaped - pattern: - - AAA - - AAA - - AAA - ingredients: - A: minecraft:gunpowder - result: - id: default:gunpowder_block - count: 1 - default:copper_coil: - type: shaped - pattern: - - AAA - - A A - - AAA - ingredients: - A: minecraft:copper_ingot - result: - id: default:copper_coil - count: 1 - default:pebble: - type: shapeless - ingredients: - - minecraft:cobblestone - result: - id: default:pebble - count: 4 - default:cobblestone_from_pebble: - type: shaped - pattern: - - AA - - AA - ingredients: - A: default:pebble - result: - id: minecraft:cobblestone - count: 1 - default:chessboard_block: - type: shaped - pattern: - - AB - - BA - ingredients: - A: minecraft:white_terracotta - B: minecraft:black_terracotta - result: - id: default:chessboard_block - count: 4 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml new file mode 100644 index 000000000..90f89d869 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml @@ -0,0 +1,84 @@ +items: + default:chessboard_block: + material: nether_brick + custom-model-data: 3000 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/chessboard_block + generation: + parent: minecraft:block/custom/chessboard_block + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 1.4 + resistance: 1.4 + is-suffocating: true + is-redstone-conductor: true + push-reaction: PUSH_ONLY + instrument: BASEDRUM + map-color: 36 + sounds: + break: minecraft:block.stone.break + fall: minecraft:block.stone.fall + hit: minecraft:block.stone.hit + place: minecraft:block.stone.place + step: minecraft:block.stone.step + states: + properties: + facing: + type: 4-direction + default: north + appearances: + east: + state: note_block:18 + model: + path: minecraft:block/custom/chessboard_block + y: 270 + generation: + parent: minecraft:block/template_glazed_terracotta + textures: + pattern: minecraft:block/custom/chessboard_block + north: + state: note_block:19 + model: + path: minecraft:block/custom/chessboard_block + y: 180 + south: + state: note_block:20 + model: + path: minecraft:block/custom/chessboard_block + west: + state: note_block:21 + model: + path: minecraft:block/custom/chessboard_block + y: 90 + variants: + facing=east: + appearance: east + id: 18 + facing=north: + appearance: north + id: 19 + facing=south: + appearance: south + id: 20 + facing=west: + appearance: west + id: 21 +recipes: + default:chessboard_block: + type: shaped + pattern: + - AB + - BA + ingredients: + A: minecraft:white_terracotta + B: minecraft:black_terracotta + result: + id: default:chessboard_block + count: 4 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml new file mode 100644 index 000000000..dbfe8ea9c --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml @@ -0,0 +1,51 @@ +items: + default:chinese_lantern: + material: nether_brick + custom-model-data: 3001 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/chinese_lantern + generation: + parent: minecraft:block/custom/chinese_lantern + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + template: + - default:hardness/wool + - default:burn_data/planks + - default:sound/wood + - default:settings/solid_1x1x1 + overrides: + push-reaction: NORMAL + instrument: HARP + luminance: 15 + map-color: 36 + state: + id: 15 + state: note_block:15 + model: + path: minecraft:block/custom/chinese_lantern + generation: + parent: minecraft:block/cube_column + textures: + end: minecraft:block/custom/chinese_lantern_top + side: minecraft:block/custom/chinese_lantern +recipes: + default:chinese_lantern: + type: shaped + pattern: + - ABA + - BCB + - ABA + ingredients: + A: '#minecraft:planks' + B: minecraft:stick + C: minecraft:torch + result: + id: default:chinese_lantern + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml new file mode 100644 index 000000000..6bc0d8394 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml @@ -0,0 +1,80 @@ +items: + default:copper_coil: + material: nether_brick + custom-model-data: 3002 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/copper_coil + generation: + parent: minecraft:block/custom/copper_coil + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/metal + - default:pickaxe_power/level_1 + overrides: + hardness: 3.0 + resistance: 4.5 + replaceable: false + is-redstone-conductor: true + is-suffocating: true + instrument: BASEDRUM + map-color: 15 + behavior: + type: lamp_block + states: + properties: + lit: + type: boolean + default: false + appearances: + off: + state: cactus:0 + model: + path: minecraft:block/custom/copper_coil + generation: + parent: minecraft:block/cactus + textures: + particle: minecraft:block/custom/copper_coil + bottom: minecraft:block/custom/copper_coil + top: minecraft:block/custom/copper_coil + side: minecraft:block/custom/copper_coil_side + on: + state: cactus:1 + model: + path: minecraft:block/custom/copper_coil_on + generation: + parent: minecraft:block/cactus + textures: + particle: minecraft:block/custom/copper_coil_on + bottom: minecraft:block/custom/copper_coil_on + top: minecraft:block/custom/copper_coil_on + side: minecraft:block/custom/copper_coil_on_side + variants: + lit=false: + appearance: 'off' + id: 0 + lit=true: + appearance: 'on' + id: 1 + settings: + luminance: 8 +recipes: + default:copper_coil: + type: shaped + pattern: + - AAA + - ABA + - AAA + ingredients: + A: minecraft:copper_ingot + B: minecraft:redstone + result: + id: default:copper_coil + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml new file mode 100644 index 000000000..5e346254b --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml @@ -0,0 +1,145 @@ +items: + default:ender_pearl_flower_seeds: + material: nether_brick + custom-model-data: 3003 + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/ender_pearl_flower_seeds + behavior: + type: block_item + block: default:ender_pearl_flower +blocks: + default:ender_pearl_flower: + settings: + template: + - default:hardness/none + - default:sound/grass + overrides: + item: default:ender_pearl_flower_seeds + push-reaction: DESTROY + map-color: 24 + is-randomly-ticking: true + behaviors: + - type: bush_block + bottom-blocks: + - minecraft:end_stone + - type: crop_block + grow-speed: 0.25 + light-requirement: 9 + is-bone-meal-target: true + bone-meal-age-bonus: 1 + loot: + template: default:loot_table/seed_crop + arguments: + crop_item: minecraft:ender_pearl + crop_seed: default:ender_pearl_flower_seeds + ripe_age: 2 + events: + - on: break + conditions: + - type: match_block_property + properties: + age: 2 + functions: + - type: particle + x: + 0.5 + y: + 0.5 + z: + 0.5 + particle: minecraft:end_rod + count: 15 + offset-x: 0.05 + offset-y: 0.05 + offset-z: 0.05 + speed: 0.1 + - type: play_sound + sound: minecraft:entity.enderman.teleport + x: + 0.5 + y: + 0.5 + z: + 0.5 + - on: right_click + conditions: + - type: match_block_property + properties: + age: 2 + - type: '!is_null' + argument: item_in_hand + - type: equals + value1: + value2: default:ender_pearl_flower_seeds + functions: + - type: break_block + x: + y: + z: + - type: place_block + x: + y: + z: + block-state: default:ender_pearl_flower[age=0] + - type: set_count + add: true + count: -1 + - type: swing_hand + states: + properties: + age: + type: int + default: 0 + range: 0~2 + appearances: + stage_0: + state: tripwire:1 + models: + - path: minecraft:block/custom/ender_pearl_flower_stage_0 + generation: + parent: minecraft:block/cross + textures: + cross: minecraft:block/custom/ender_pearl_flower_stage_0 + stage_1: + state: tripwire:0 + models: + - path: minecraft:block/custom/ender_pearl_flower_stage_1 + generation: + parent: minecraft:block/cross + textures: + cross: minecraft:block/custom/ender_pearl_flower_stage_1 + stage_2: + state: sugar_cane:3 + models: + - path: minecraft:block/custom/ender_pearl_flower_stage_2 + generation: + parent: minecraft:block/cross + textures: + cross: minecraft:block/custom/ender_pearl_flower_stage_2 + variants: + age=0: + appearance: stage_0 + id: 0 + age=1: + appearance: stage_1 + id: 1 + age=2: + appearance: stage_2 + id: 8 +vanilla-loots: + default:ender_pearl_flower_seeds_from_endermite: + type: entity + target: minecraft:endermite + override: false + loot: + pools: + - rolls: 1 + conditions: + - type: table_bonus + enchantment: minecraft:looting + chances: + - 0.1 + - 0.5 + - 0.8 + - 1 + entries: + - type: item + item: default:ender_pearl_flower_seeds \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml new file mode 100644 index 000000000..188ae9b80 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml @@ -0,0 +1,52 @@ +items: + default:fairy_flower: + material: nether_brick + custom-model-data: 3004 + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/fairy_flower + behavior: + type: block_item + block: + settings: + template: + - default:hardness/none + - default:sound/grass + overrides: + item: default:fairy_flower + push-reaction: DESTROY + map-color: 19 + behavior: + type: bush_block + bottom-block-tags: + - minecraft:dirt + - minecraft:farmland + loot: + template: default:loot_table/self + state: + id: 0 + state: sugar_cane:0 + models: + - path: minecraft:block/custom/fairy_flower_1 + weight: 100 + - path: minecraft:block/custom/fairy_flower_2 + weight: 5 + generation: + parent: minecraft:block/custom/fairy_flower_1 + textures: + '0': minecraft:block/custom/fairy_flower_2 + - path: minecraft:block/custom/fairy_flower_3 + weight: 5 + generation: + parent: minecraft:block/custom/fairy_flower_1 + textures: + '0': minecraft:block/custom/fairy_flower_3 + - path: minecraft:block/custom/fairy_flower_4 + weight: 5 + generation: + parent: minecraft:block/custom/fairy_flower_1 + textures: + '0': minecraft:block/custom/fairy_flower_4 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml new file mode 100644 index 000000000..b5544c681 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml @@ -0,0 +1,112 @@ +items: + default:flame_cane: + material: nether_brick + custom-model-data: 3005 + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/flame_cane + behavior: + type: block_item + block: + settings: + template: + - default:hardness/none + - default:sound/grass + overrides: + push-reaction: DESTROY + map-color: 15 + is-randomly-ticking: true + behaviors: + - type: vertical_crop_block + max-height: 4 + grow-speed: 0.333 + direction: up + - type: bush_block + stackable: true + delay: 1 + bottom-blocks: + - minecraft:netherrack + - minecraft:soul_sand + - minecraft:soul_soil + - minecraft:magma_block + - minecraft:warped_nylium + - minecraft:crimson_nylium + - minecraft:basalt + - type: near_liquid_block + liquid-type: lava + delay: 1 + stackable: true + positions: + - -1,-1,0 + - 1,-1,0 + - 0,-1,-1 + - 0,-1,1 + loot: + template: default:loot_table/self + states: + properties: + age: + type: int + default: 0 + range: 0~5 + appearances: + default: + state: sugar_cane:2 + models: + - path: minecraft:block/custom/flame_cane_1 + weight: 1 + generation: + parent: minecraft:block/sugar_cane + textures: + cross: minecraft:block/custom/flame_cane_1 + - path: minecraft:block/custom/flame_cane_2 + weight: 1 + generation: + parent: minecraft:block/sugar_cane + textures: + cross: minecraft:block/custom/flame_cane_2 + variants: + age=0: + appearance: default + id: 2 + age=1: + appearance: default + id: 3 + age=2: + appearance: default + id: 4 + age=3: + appearance: default + id: 5 + age=4: + appearance: default + id: 6 + age=5: + appearance: default + id: 7 +recipes: + default:magma_cream: + type: shaped + pattern: + - ' A ' + - 'ABA' + - ' A ' + ingredients: + A: default:flame_cane + B: minecraft:slime_ball + result: + id: minecraft:magma_cream + count: 1 + default:magma_block: + type: shapeless + ingredients: + A1: minecraft:cobblestone + A2: minecraft:cobblestone + B1: default:flame_cane + B2: default:flame_cane + result: + id: minecraft:magma_block + count: 2 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml new file mode 100644 index 000000000..3cb8fb486 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml @@ -0,0 +1,91 @@ +items: + default:gunpowder_block: + material: nether_brick + custom-model-data: 3006 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/gunpowder_block + generation: + parent: minecraft:block/custom/gunpowder_block + behavior: + type: block_item + block: + behaviors: + - type: concrete_powder_block + solid-block: default:solid_gunpowder_block + - type: falling_block + loot: + template: default:loot_table/self + settings: + template: + - default:sound/sand + - default:settings/solid_1x1x1 + overrides: + hardness: 0.5 + resistance: 0.5 + instrument: SNARE + map-color: 45 + state: + id: 16 + state: note_block:16 + model: + path: minecraft:block/custom/gunpowder_block + generation: + parent: minecraft:block/cube_all + textures: + all: minecraft:block/custom/gunpowder_block + default:solid_gunpowder_block: + material: nether_brick + custom-model-data: 3007 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/solid_gunpowder_block + generation: + parent: minecraft:block/custom/solid_gunpowder_block + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/stone + - default:pickaxe_power/level_1 + - default:settings/solid_1x1x1 + overrides: + hardness: 1.8 + resistance: 1.8 + instrument: BASEDRUM + map-color: 45 + state: + id: 17 + state: note_block:17 + model: + path: minecraft:block/custom/solid_gunpowder_block + generation: + parent: minecraft:block/cube_all + textures: + all: minecraft:block/custom/solid_gunpowder_block +recipes: + default:gunpowder_from_block: + type: shapeless + ingredients: + A: default:gunpowder_block + result: + id: minecraft:gunpowder + count: 9 + default:gunpowder_block: + type: shaped + pattern: + - AAA + - AAA + - AAA + ingredients: + A: minecraft:gunpowder + result: + id: default:gunpowder_block + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml new file mode 100644 index 000000000..7b000d9ae --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml @@ -0,0 +1,94 @@ +items: + default:netherite_anvil: + material: nether_brick + custom-model-data: 3008 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/netherite_anvil + generation: + parent: minecraft:block/custom/netherite_anvil + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + behavior: + type: falling_block + hurt-amount: 4 + max-hurt: 80 + events: + - on: right_click + functions: + - type: open_window + gui-type: anvil + - type: cancel_event + conditions: + - type: expression + expression: '!' + settings: + template: + - default:pickaxe_power/level_4 + overrides: + tags: + - minecraft:mineable/pickaxe + sounds: + break: minecraft:block.anvil.break + step: minecraft:block.anvil.step + place: minecraft:block.anvil.place + hit: minecraft:block.anvil.hit + fall: minecraft:block.anvil.fall + land: minecraft:block.anvil.land + destroy: minecraft:block.anvil.destroy + map-color: 29 + hardness: 10.0 + resistance: 1200 + push-reaction: BLOCK + states: + properties: + facing_clockwise: + type: 4-direction + default: north + appearances: + axisX: + state: minecraft:anvil[facing=east] + model: + path: minecraft:block/custom/netherite_anvil + y: 90 + generation: + parent: minecraft:block/anvil + textures: + top: minecraft:block/custom/netherite_anvil_top + body: minecraft:block/custom/netherite_anvil + particle: minecraft:block/custom/netherite_anvil + axisZ: + state: minecraft:anvil[facing=north] + model: + path: minecraft:block/custom/netherite_anvil + variants: + facing_clockwise=east: + appearance: axisX + id: 0 + facing_clockwise=west: + appearance: axisX + id: 1 + facing_clockwise=north: + appearance: axisZ + id: 2 + facing_clockwise=south: + appearance: axisZ + id: 3 +recipes: + default:netherite_anvil: + type: shaped + pattern: + - ' B ' + - BAB + - ' B ' + ingredients: + A: minecraft:anvil + B: minecraft:netherite_ingot + result: + id: default:netherite_anvil + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml similarity index 100% rename from common-files/src/main/resources/resources/default/configuration/palm_tree.yml rename to common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml new file mode 100644 index 000000000..c275b52b8 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml @@ -0,0 +1,130 @@ +items: + default:pebble: + material: nether_brick + custom-model-data: 3009 + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/pebble + behavior: + - type: block_item + block: + settings: + template: + - default:sound/stone + - default:hardness/none + overrides: + map-color: 11 + push-reaction: DESTROY + behaviors: + - type: sturdy_base_block + direction: down + support-types: + - full + - type: stackable_block + property: pebble + items: + - default:pebble + sounds: + stack: minecraft:block.stone.fall + loot: + pools: + - rolls: 1 + entries: + - type: item + item: default:pebble + functions: + - type: set_count + count: 3 + add: false + conditions: + - type: match_block_property + properties: + pebble: 3 + - type: set_count + count: 2 + add: false + conditions: + - type: match_block_property + properties: + pebble: 2 + - type: explosion_decay + states: + properties: + pebble: + type: int + range: 1~3 + default: 1 + appearances: + one: + state: tripwire:2 + models: + - path: minecraft:block/custom/pebble_1 + weight: 1 + - path: minecraft:block/custom/pebble_1 + weight: 1 + y: 90 + - path: minecraft:block/custom/pebble_1 + weight: 1 + y: 180 + - path: minecraft:block/custom/pebble_1 + weight: 1 + y: 270 + two: + state: tripwire:3 + models: + - path: minecraft:block/custom/pebble_2 + weight: 1 + - path: minecraft:block/custom/pebble_2 + weight: 1 + y: 90 + - path: minecraft:block/custom/pebble_2 + weight: 1 + y: 180 + - path: minecraft:block/custom/pebble_2 + weight: 1 + y: 270 + three: + state: tripwire:4 + models: + - path: minecraft:block/custom/pebble_3 + weight: 1 + - path: minecraft:block/custom/pebble_3 + weight: 1 + y: 90 + - path: minecraft:block/custom/pebble_3 + weight: 1 + y: 180 + - path: minecraft:block/custom/pebble_3 + weight: 1 + y: 270 + variants: + pebble=1: + appearance: 'one' + id: 2 + pebble=2: + appearance: 'two' + id: 3 + pebble=3: + appearance: 'three' + id: 4 +recipes: + default:pebble: + type: shapeless + ingredients: + - minecraft:cobblestone + result: + id: default:pebble + count: 4 + default:cobblestone_from_pebble: + type: shaped + pattern: + - AA + - AA + ingredients: + A: default:pebble + result: + id: minecraft:cobblestone + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml new file mode 100644 index 000000000..3ecbd29a2 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml @@ -0,0 +1,42 @@ +items: + default:reed: + material: nether_brick + custom-model-data: 3010 + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/reed + behavior: + type: liquid_collision_block_item + block: + settings: + template: + - default:hardness/none + - default:sound/grass + overrides: + push-reaction: DESTROY + map-color: 60 + behavior: + type: on_liquid_block + liquid-type: water + positions: + - 0,-1,0 + loot: + template: default:loot_table/self + state: + id: 1 + state: sugar_cane:1 + model: + path: minecraft:block/custom/reed +recipes: + default:paper_from_reed: + type: shaped + pattern: + - AAA + ingredients: + A: default:reed + result: + id: minecraft:paper + count: 3 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml new file mode 100644 index 000000000..5f008583e --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -0,0 +1,134 @@ +items: + default:safe_block: + material: nether_brick + custom-model-data: 3011 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/safe_block + generation: + parent: minecraft:block/custom/safe_block + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 5 + resistance: 1200 + is-suffocating: true + is-redstone-conductor: true + push-reaction: BLOCK + instrument: BASEDRUM + map-color: 6 + sounds: + break: minecraft:block.stone.break + fall: minecraft:block.stone.fall + hit: minecraft:block.stone.hit + place: minecraft:block.stone.place + step: minecraft:block.stone.step + behavior: + type: simple_storage_block + title: "" + rows: 1 + sounds: + open: minecraft:block.iron_trapdoor.open + close: minecraft:block.iron_trapdoor.close + states: + properties: + facing: + type: 4-direction + default: north + open: + type: boolean + default: false + appearances: + east: + state: note_block:22 + model: + path: minecraft:block/custom/safe_block + y: 90 + generation: + parent: minecraft:block/orientable + textures: + front: minecraft:block/custom/safe_block_front + side: minecraft:block/custom/safe_block_side + top: minecraft:block/custom/safe_block_top + east_open: + state: note_block:23 + model: + path: minecraft:block/custom/safe_block_open + y: 90 + generation: + parent: minecraft:block/orientable + textures: + front: minecraft:block/custom/safe_block_front_open + side: minecraft:block/custom/safe_block_side + top: minecraft:block/custom/safe_block_top + north: + state: note_block:24 + model: + path: minecraft:block/custom/safe_block + north_open: + state: note_block:25 + model: + path: minecraft:block/custom/safe_block_open + south: + state: note_block:26 + model: + path: minecraft:block/custom/safe_block + y: 180 + south_open: + state: note_block:27 + model: + path: minecraft:block/custom/safe_block_open + y: 180 + west: + state: note_block:28 + model: + path: minecraft:block/custom/safe_block + y: 270 + west_open: + state: note_block:29 + model: + path: minecraft:block/custom/safe_block_open + y: 270 + variants: + facing=east,open=false: + appearance: east + id: 22 + facing=east,open=true: + appearance: east_open + id: 23 + facing=north,open=false: + appearance: north + id: 24 + facing=north,open=true: + appearance: north_open + id: 25 + facing=south,open=false: + appearance: south + id: 26 + facing=south,open=true: + appearance: south_open + id: 27 + facing=west,open=false: + appearance: west + id: 28 + facing=west,open=true: + appearance: west_open + id: 29 +recipes: + default:safe_block: + type: shaped + pattern: + - 'AAA' + - 'ABA' + - 'AAA' + ingredients: + A: minecraft:iron_ingot + B: ["minecraft:barrel", "minecraft:chest"] + result: + id: default:safe_block + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml new file mode 100644 index 000000000..d13094095 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -0,0 +1,185 @@ +items: + default:sleeper_sofa: + material: nether_brick + custom-model-data: 3012 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/sleeper_sofa + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 0.5 + resistance: 0.5 + map-color: 27 + burn-chance: 5 + fire-spread-chance: 20 + burnable: true + is-suffocating: false + is-redstone-conductor: false + push-reaction: BLOCK + instrument: BASS + sounds: + break: minecraft:block.wood.break + fall: minecraft:block.wood.fall + hit: minecraft:block.wood.hit + place: minecraft:block.wood.place + step: minecraft:block.wood.step + tags: + - minecraft:mineable/axe + behaviors: + - type: bouncing_block + bounce-height: 0.66 + sync-player-position: false + state: + id: 0 + state: white_bed[facing=west,occupied=false,part=foot] + entity-renderer: + item: default:sleeper_sofa + default:sofa_inner: + material: nether_brick + custom-model-data: 3013 + model: + type: minecraft:model + path: minecraft:item/custom/sofa_inner + default:sofa: + material: nether_brick + custom-model-data: 3014 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/sofa + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + hardness: 0.5 + resistance: 0.5 + map-color: 27 + burn-chance: 5 + fire-spread-chance: 20 + burnable: true + is-suffocating: false + is-redstone-conductor: false + push-reaction: BLOCK + instrument: BASS + sounds: + break: minecraft:block.wood.break + fall: minecraft:block.wood.fall + hit: minecraft:block.wood.hit + place: minecraft:block.wood.place + step: minecraft:block.wood.step + tags: + - minecraft:mineable/axe + behaviors: + - type: sofa_block + - type: bouncing_block + bounce-height: 0.66 + states: + properties: + facing: + type: horizontal_direction + shape: + type: sofa_shape + appearances: + facing=east,shape=straight: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa + yaw: 90 + facing=north,shape=straight: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa + facing=south,shape=straight: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa + yaw: 180 + facing=west,shape=straight: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa + yaw: 270 + facing=east,shape=inner_left: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + facing=north,shape=inner_left: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + yaw: 270 + facing=south,shape=inner_left: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + yaw: 90 + facing=west,shape=inner_left: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + yaw: 180 + facing=east,shape=inner_right: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + yaw: 90 + facing=north,shape=inner_right: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + facing=south,shape=inner_right: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + yaw: 180 + facing=west,shape=inner_right: + state: barrier[waterlogged=false] + entity-renderer: + item: default:sofa_inner + yaw: 270 + variants: + facing=east,shape=inner_left: + appearance: facing=east,shape=inner_left + id: 0 + facing=east,shape=inner_right: + appearance: facing=east,shape=inner_right + id: 1 + facing=east,shape=straight: + appearance: facing=east,shape=straight + id: 2 + facing=north,shape=inner_left: + appearance: facing=north,shape=inner_left + id: 3 + facing=north,shape=inner_right: + appearance: facing=north,shape=inner_right + id: 4 + facing=north,shape=straight: + appearance: facing=north,shape=straight + id: 5 + facing=south,shape=inner_left: + appearance: facing=south,shape=inner_left + id: 6 + facing=south,shape=inner_right: + appearance: facing=south,shape=inner_right + id: 7 + facing=south,shape=straight: + appearance: facing=south,shape=straight + id: 8 + facing=west,shape=inner_left: + appearance: facing=west,shape=inner_left + id: 9 + facing=west,shape=inner_right: + appearance: facing=west,shape=inner_right + id: 10 + facing=west,shape=straight: + appearance: facing=west,shape=straight + id: 11 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml new file mode 100644 index 000000000..e6dbf6ceb --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml @@ -0,0 +1,125 @@ +items: + default:table_lamp: + material: nether_brick + custom-model-data: 3015 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/table_lamp + behavior: + type: block_item + block: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/metal + overrides: + hardness: 1 + resistance: 1 + replaceable: false + is-redstone-conductor: false + is-suffocating: false + behaviors: + - type: toggleable_lamp_block + can-open-with-hand: true + - type: sturdy_base_block + direction: down + support-types: + - full + - center + states: + properties: + lit: + type: boolean + default: false + facing: + type: 4-direction + default: north + appearances: + east_off: + state: barrier + entity-renderer: + item: default:table_lamp + north_off: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: -90 + south_off: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 90 + west_off: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 180 + east_on: + state: barrier + entity-renderer: + item: default:table_lamp + north_on: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: -90 + south_on: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 90 + west_on: + state: barrier + entity-renderer: + item: default:table_lamp + yaw: 180 + variants: + facing=east,lit=false: + appearance: east_off + id: 12 + facing=north,lit=false: + appearance: north_off + id: 13 + facing=south,lit=false: + appearance: south_off + id: 14 + facing=west,lit=false: + appearance: west_off + id: 15 + facing=east,lit=true: + appearance: east_on + id: 16 + settings: + luminance: 15 + facing=north,lit=true: + appearance: north_on + id: 17 + settings: + luminance: 15 + facing=south,lit=true: + appearance: south_on + id: 18 + settings: + luminance: 15 + facing=west,lit=true: + appearance: west_on + id: 19 + settings: + luminance: 15 +recipes: + default:table_lamp: + type: shaped + pattern: + - 'BA' + - ' A' + - 'CA' + ingredients: + A: minecraft:iron_ingot + B: minecraft:redstone_lamp + C: minecraft:lever + result: + id: default:table_lamp + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/ores.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml similarity index 97% rename from common-files/src/main/resources/resources/default/configuration/ores.yml rename to common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index 0513a1191..eda1e9f16 100644 --- a/common-files/src/main/resources/resources/default/configuration/ores.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -1,7 +1,7 @@ items: default:topaz_ore: material: nether_brick - custom-model-data: 1010 + custom-model-data: 3016 data: item-name: model: @@ -14,7 +14,7 @@ items: block: default:topaz_ore default:deepslate_topaz_ore: material: nether_brick - custom-model-data: 1011 + custom-model-data: 3017 data: item-name: model: @@ -27,7 +27,7 @@ items: block: default:deepslate_topaz_ore default:topaz: material: nether_brick - custom-model-data: 1012 + custom-model-data: 3018 settings: anvil-repair-item: - target: diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 8dc018294..7514c7909 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -8,7 +8,6 @@ categories: list: - '#default:palm_tree' - '#default:topaz' - - '#default:furniture' - '#default:misc' default:palm_tree: name: @@ -49,14 +48,6 @@ categories: - default:topaz_chestplate - default:topaz_leggings - default:topaz_boots - default:furniture: - name: <#FFD700> - hidden: true - icon: default:flower_basket - list: - - default:bench - - default:wooden_chair - - default:flower_basket default:misc: name: hidden: true @@ -83,4 +74,7 @@ categories: - minecraft:air - minecraft:air - default:sofa - - default:table_lamp \ No newline at end of file + - default:table_lamp + - default:bench + - default:wooden_chair + - default:flower_basket \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/emoji.yml b/common-files/src/main/resources/resources/default/configuration/emoji.yml index ef8173f57..49c0ca5c6 100644 --- a/common-files/src/main/resources/resources/default/configuration/emoji.yml +++ b/common-files/src/main/resources/resources/default/configuration/emoji.yml @@ -1,28 +1,7 @@ templates: default:emoji/basic: content: '> - default:emoji/addition_info: - content: '>${text} emoji: - default:emoji_location: - template: default:emoji/addition_info - arguments: - text: - overrides: - image: default:icons:0:0 - permission: emoji.location - keywords: - - ':location:' - - ':pos:' - default:emoji_time: - template: default:emoji/addition_info - arguments: - text: - overrides: - image: default:icons:0:1 - permission: emoji.time - keywords: - - ':time:' default:emoji_smiley: template: default:emoji/basic overrides: diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml b/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml new file mode 100644 index 000000000..e5eb93aa2 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml @@ -0,0 +1,44 @@ +items: + default:bench: + material: nether_brick + custom-model-data: 2000 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/bench + behavior: + type: furniture_item + furniture: + settings: + item: default:bench + sounds: + break: minecraft:block.bamboo_wood.break + place: minecraft:block.bamboo_wood.place + placement: + ground: + loot-spawn-offset: 0.5,0.5,0 + rules: + rotation: FOUR + alignment: CENTER + elements: + - item: default:bench + display-transform: NONE + billboard: FIXED + position: 0.5,0,0 + translation: 0,0.5,0 + hitboxes: + - position: 0,0,0 + type: shulker + direction: east + peek: 100 + blocks-building: true + interactive: true + interaction-entity: true + seats: + - 0,0,-0.1 0 + - 1,0,-0.1 0 + loot: + template: default:loot_table/furniture + arguments: + item: default:bench \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/furniture.yml b/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml similarity index 54% rename from common-files/src/main/resources/resources/default/configuration/furniture.yml rename to common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml index e54a3ccb0..2a008d707 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml @@ -1,92 +1,7 @@ items: - default:bench: - material: nether_brick - custom-model-data: 2000 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/bench - behavior: - type: furniture_item - furniture: - settings: - item: default:bench - sounds: - break: minecraft:block.bamboo_wood.break - place: minecraft:block.bamboo_wood.place - placement: - ground: - loot-spawn-offset: 0.5,0.5,0 - rules: - rotation: FOUR - alignment: CENTER - elements: - - item: default:bench - display-transform: NONE - billboard: FIXED - position: 0.5,0,0 - translation: 0,0.5,0 - hitboxes: - - position: 0,0,0 - type: shulker - direction: east - peek: 100 - blocks-building: true - interactive: true - interaction-entity: true - seats: - - 0,0,-0.1 0 - - 1,0,-0.1 0 - loot: - template: default:loot_table/furniture - arguments: - item: default:bench - default:wooden_chair: - material: nether_brick - custom-model-data: 2002 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/wooden_chair - behavior: - type: furniture_item - furniture: - settings: - item: default:wooden_chair - sounds: - break: minecraft:block.bamboo_wood.break - place: minecraft:block.bamboo_wood.place - placement: - ground: - loot-spawn-offset: 0,0.4,0 - rules: - rotation: ANY - alignment: ANY - elements: - - item: default:wooden_chair - display-transform: NONE - billboard: FIXED - translation: 0,0.5,0 - hitboxes: - - position: 0,0,0 - type: interaction - blocks-building: true - width: 0.7 - height: 1.2 - interactive: true - seats: - - 0,0,-0.1 0 - loot: - template: default:loot_table/furniture - arguments: - item: default:wooden_chair - -items#flower_basket: default:flower_basket: material: nether_brick - custom-model-data: 2003 + custom-model-data: 2001 data: item-name: model: @@ -98,24 +13,23 @@ items#flower_basket: furniture: default:flower_basket default:flower_basket_ground: material: nether_brick - custom-model-data: 2004 + custom-model-data: 2002 model: type: minecraft:model path: minecraft:item/custom/flower_basket_ground default:flower_basket_wall: material: nether_brick - custom-model-data: 2005 + custom-model-data: 2003 model: type: minecraft:model path: minecraft:item/custom/flower_basket_wall default:flower_basket_ceiling: material: nether_brick - custom-model-data: 2006 + custom-model-data: 2004 model: type: minecraft:model path: minecraft:item/custom/flower_basket_ceiling - -furniture#flower_basket: +furniture: default:flower_basket: settings: item: default:flower_basket diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml b/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml new file mode 100644 index 000000000..f835379c3 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml @@ -0,0 +1,41 @@ +items: + default:wooden_chair: + material: nether_brick + custom-model-data: 2005 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/wooden_chair + behavior: + type: furniture_item + furniture: + settings: + item: default:wooden_chair + sounds: + break: minecraft:block.bamboo_wood.break + place: minecraft:block.bamboo_wood.place + placement: + ground: + loot-spawn-offset: 0,0.4,0 + rules: + rotation: ANY + alignment: ANY + elements: + - item: default:wooden_chair + display-transform: NONE + billboard: FIXED + translation: 0,0.5,0 + hitboxes: + - position: 0,0,0 + type: interaction + blocks-building: true + width: 0.7 + height: 1.2 + interactive: true + seats: + - 0,0,-0.1 0 + loot: + template: default:loot_table/furniture + arguments: + item: default:wooden_chair \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index 6af2e7556..465a6f86e 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -52,7 +52,6 @@ i18n: category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree category.topaz: Topaz - category.furniture: Furniture category.misc: Misc emoji.tip: Use '' to send the '' emoji emoji.time: 'Current time: ' @@ -110,8 +109,67 @@ i18n: category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 category.topaz: 黄玉 - category.furniture: 家具 category.misc: 杂项 emoji.tip: 使用''来发送表情'' emoji.time: '当前时间: ' - emoji.location: '当前坐标: ,,' \ No newline at end of file + emoji.location: '当前坐标: ,,' +# This section is for localizing internal block IDs (craftengine:xxx_xx). +# Some other plugins support displaying block names using lang components. +# This might be useful for the client-side, but it's not mandatory. +lang: + en_us: + block_name:default:chinese_lantern: Chinese Lantern + block_name:default:netherite_anvil: Netherite Anvil + block_name:default:topaz_ore: Topaz Ore + block_name:default:deepslate_topaz_ore: Deepslate Topaz Ore + block_name:default:palm_log: Palm Log + block_name:default:stripped_palm_log: Stripped Palm Log + block_name:default:palm_wood: Palm Wood + block_name:default:stripped_palm_wood: Stripped Palm Wood + block_name:default:palm_planks: Palm Planks + block_name:default:palm_sapling: Palm Sapling + block_name:default:palm_leaves: Palm Leaves + block_name:default:palm_trapdoor: Palm Trapdoor + block_name:default:palm_door: Palm Door + block_name:default:palm_fence_gate: Palm Fence Gate + block_name:default:palm_slab: Palm Slab + block_name:default:palm_stairs: Palm Stairs + block_name:default:fairy_flower: Fairy Flower + block_name:default:reed: Reed + block_name:default:flame_cane: Flame Cane + block_name:default:ender_pearl_flower: Ender Pearl Flower + block_name:default:gunpowder_block: GunPowder Block + block_name:default:solid_gunpowder_block: Solid GunPowder Block + block_name:default:copper_coil: Copper Coil + block_name:default:chessboard_block: Chessboard Block + block_name:default:safe_block: Safe Block + block_name:default:sleeper_sofa: Sofa + block_name:default:sofa: Sofa + zh_cn: + block_name:default:chinese_lantern: 灯笼 + block_name:default:netherite_anvil: 下界合金砧 + block_name:default:topaz_ore: 黄玉矿石 + block_name:default:deepslate_topaz_ore: 深层黄玉矿石 + block_name:default:palm_log: 棕榈原木 + block_name:default:stripped_palm_log: 去皮棕榈原木 + block_name:default:palm_wood: 棕榈木 + block_name:default:stripped_palm_wood: 去皮棕榈木 + block_name:default:palm_planks: 棕榈木板 + block_name:default:palm_sapling: 棕榈树苗 + block_name:default:palm_leaves: 棕榈树叶 + block_name:default:palm_trapdoor: 棕榈木活板门 + block_name:default:palm_door: 棕榈木门 + block_name:default:palm_fence_gate: 棕榈木栅栏门 + block_name:default:palm_slab: 棕榈木台阶 + block_name:default:palm_stairs: 棕榈木楼梯 + block_name:default:fairy_flower: 仙灵花 + block_name:default:reed: 芦苇 + block_name:default:flame_cane: 烈焰甘蔗 + block_name:default:ender_pearl_flower: 末影珍珠花 + block_name:default:gunpowder_block: 火药粉末 + block_name:default:solid_gunpowder_block: 凝固火药块 + block_name:default:copper_coil: 铜线圈 + block_name:default:chessboard_block: 棋盘方块 + block_name:default:safe_block: 保险柜 + block_name:default:sleeper_sofa: 沙发 + block_name:default:sofa: 沙发 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/icons.yml b/common-files/src/main/resources/resources/default/configuration/icons.yml deleted file mode 100644 index 62b8500e6..000000000 --- a/common-files/src/main/resources/resources/default/configuration/icons.yml +++ /dev/null @@ -1,9 +0,0 @@ -images: - default:icons: - height: 10 - ascent: 9 - font: minecraft:icons - file: minecraft:font/image/icons.png - chars: - - \ub000\ub001 - - \ub002\ub003 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/items/cap.yml b/common-files/src/main/resources/resources/default/configuration/items/cap.yml new file mode 100644 index 000000000..ab797c99a --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/items/cap.yml @@ -0,0 +1,16 @@ +items: + default:cap: + material: leather_helmet + client-bound-material: leather_horse_armor + custom-model-data: 1000 + data: + item-name: + unbreakable: true + remove-components: + - attribute_modifiers + model: + type: minecraft:model + path: minecraft:item/custom/cap + tints: + - type: minecraft:dye + default: -6265536 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml b/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml new file mode 100644 index 000000000..6ec20e315 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml @@ -0,0 +1,17 @@ +items: + $$>=1.21.2#flame_elytra: + default:flame_elytra: + material: elytra + custom-model-data: 1000 + settings: + equippable: + slot: chest + asset-id: flame + wings: flame_elytra + data: + item-name: <#FF8C00> + model: + template: default:model/simplified_elytra + arguments: + path: minecraft:item/custom/flame_elytra + broken_path: minecraft:item/custom/broken_flame_elytra \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml b/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml new file mode 100644 index 000000000..effc996c8 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml @@ -0,0 +1,31 @@ +items: + default:gui_head_size_1: + material: player_head + custom-model-data: 1000 + model: + type: minecraft:special + path: minecraft:item/custom/gui_head_size_1 + generation: + parent: minecraft:item/template_skull + gui-light: front + display: + gui: + translation: 0,8,0 + scale: 2,2,2 + model: + type: minecraft:player_head + default:gui_head_size_4: + material: player_head + custom-model-data: 1001 + model: + type: minecraft:special + path: minecraft:item/custom/gui_head_size_4 + generation: + parent: minecraft:item/template_skull + gui-light: front + display: + gui: + translation: 9,7,0 + scale: 4,4,4 + model: + type: minecraft:player_head \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml b/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml new file mode 100644 index 000000000..bde573c46 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml @@ -0,0 +1,104 @@ +templates: + default:armor/topaz: + material: chainmail_${part} + custom-model-data: 1000 + data: + item-name: <#FF8C00> + tooltip-style: minecraft:topaz + settings: + tags: + - default:topaz_tools + - minecraft:trimmable_armor + equipment: + asset-id: default:topaz + $$>=1.21.2: + slot: ${slot} + model: + template: default:model/armor_trim +items: + default:topaz_helmet: + template: + - default:armor/topaz + arguments: + part: helmet + slot: head + material: topaz + default:topaz_chestplate: + template: + - default:armor/topaz + arguments: + part: chestplate + slot: chest + material: topaz + default:topaz_leggings: + template: + - default:armor/topaz + arguments: + part: leggings + slot: legs + material: topaz + default:topaz_boots: + template: + - default:armor/topaz + arguments: + part: boots + slot: feet + material: topaz +equipments: + $$>=1.21.2: + default:topaz: + type: component + humanoid: minecraft:topaz + humanoid-leggings: minecraft:topaz + $$<1.21.2: + default:topaz: + type: trim + humanoid: minecraft:entity/equipment/humanoid/topaz + humanoid-leggings: minecraft:entity/equipment/humanoid_leggings/topaz +recipes: + default:topaz_helmet: + type: shaped + category: equipment + pattern: + - AAA + - A A + ingredients: + A: default:topaz + result: + id: default:topaz_helmet + count: 1 + default:topaz_chestplate: + type: shaped + category: equipment + pattern: + - A A + - AAA + - AAA + ingredients: + A: default:topaz + result: + id: default:topaz_chestplate + count: 1 + default:topaz_leggings: + type: shaped + category: equipment + pattern: + - AAA + - A A + - A A + ingredients: + A: default:topaz + result: + id: default:topaz_leggings + count: 1 + default:topaz_boots: + type: shaped + category: equipment + pattern: + - A A + - A A + ingredients: + A: default:topaz + result: + id: default:topaz_boots + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/items.yml b/common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml similarity index 67% rename from common-files/src/main/resources/resources/default/configuration/items.yml rename to common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml index b25111d18..5102ab3f6 100644 --- a/common-files/src/main/resources/resources/default/configuration/items.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml @@ -1,35 +1,4 @@ -items#gui_head: - default:gui_head_size_1: - material: player_head - custom-model-data: 1000 - model: - type: minecraft:special - path: minecraft:item/custom/gui_head_size_1 - generation: - parent: minecraft:item/template_skull - gui-light: front - display: - gui: - translation: 0,8,0 - scale: 2,2,2 - model: - type: minecraft:player_head - default:gui_head_size_4: - material: player_head - custom-model-data: 1001 - model: - type: minecraft:special - path: minecraft:item/custom/gui_head_size_4 - generation: - parent: minecraft:item/template_skull - gui-light: front - display: - gui: - translation: 9,7,0 - scale: 4,4,4 - model: - type: minecraft:player_head -items#topaz_gears: +items: default:topaz_rod: material: fishing_rod custom-model-data: 1000 @@ -236,94 +205,7 @@ items#topaz_gears: on-false: type: minecraft:model path: minecraft:item/custom/topaz_trident_in_hand - $$>=1.21.2#flame_elytra: - default:flame_elytra: - material: elytra - custom-model-data: 1000 - settings: - equippable: - slot: chest - asset-id: flame - wings: flame_elytra - data: - item-name: <#FF8C00> - model: - template: default:model/simplified_elytra - arguments: - path: minecraft:item/custom/flame_elytra - broken_path: minecraft:item/custom/broken_flame_elytra - default:cap: - material: leather_helmet - client-bound-material: leather_horse_armor - custom-model-data: 1000 - data: - item-name: - unbreakable: true - remove-components: - - attribute_modifiers - model: - type: minecraft:model - path: minecraft:item/custom/cap - tints: - - type: minecraft:dye - default: -6265536 - default:topaz_helmet: - template: - - default:armor/topaz - arguments: - part: helmet - slot: head - material: topaz - default:topaz_chestplate: - template: - - default:armor/topaz - arguments: - part: chestplate - slot: chest - material: topaz - default:topaz_leggings: - template: - - default:armor/topaz - arguments: - part: leggings - slot: legs - material: topaz - default:topaz_boots: - template: - - default:armor/topaz - arguments: - part: boots - slot: feet - material: topaz -templates: - default:armor/topaz: - material: chainmail_${part} - custom-model-data: 1000 - data: - item-name: <#FF8C00> - tooltip-style: minecraft:topaz - settings: - tags: - - default:topaz_tools - - minecraft:trimmable_armor - equipment: - asset-id: default:topaz - $$>=1.21.2: - slot: ${slot} - model: - template: default:model/armor_trim -equipments#topaz: - $$>=1.21.2: - default:topaz: - type: component - humanoid: minecraft:topaz - humanoid-leggings: minecraft:topaz - $$<1.21.2: - default:topaz: - type: trim - humanoid: minecraft:entity/equipment/humanoid/topaz - humanoid-leggings: minecraft:entity/equipment/humanoid_leggings/topaz -recipes#topaz: +recipes: default:topaz_shovel: type: shaped category: equipment @@ -389,52 +271,6 @@ recipes#topaz: result: id: default:topaz_pickaxe count: 1 - default:topaz_helmet: - type: shaped - category: equipment - pattern: - - AAA - - A A - ingredients: - A: default:topaz - result: - id: default:topaz_helmet - count: 1 - default:topaz_chestplate: - type: shaped - category: equipment - pattern: - - A A - - AAA - - AAA - ingredients: - A: default:topaz - result: - id: default:topaz_chestplate - count: 1 - default:topaz_leggings: - type: shaped - category: equipment - pattern: - - AAA - - A A - - A A - ingredients: - A: default:topaz - result: - id: default:topaz_leggings - count: 1 - default:topaz_boots: - type: shaped - category: equipment - pattern: - - A A - - A A - ingredients: - A: default:topaz - result: - id: default:topaz_boots - count: 1 default:topaz_bow: type: smithing_transform base: minecraft:bow diff --git a/common-files/src/main/resources/resources/default/configuration/plants.yml b/common-files/src/main/resources/resources/default/configuration/plants.yml deleted file mode 100644 index b5bf8d252..000000000 --- a/common-files/src/main/resources/resources/default/configuration/plants.yml +++ /dev/null @@ -1,350 +0,0 @@ -items: - default:fairy_flower: - material: nether_brick - custom-model-data: 4000 - data: - item-name: - model: - template: default:model/simplified_generated - arguments: - path: minecraft:item/custom/fairy_flower - behavior: - type: block_item - block: default:fairy_flower - default:reed: - material: nether_brick - custom-model-data: 4001 - data: - item-name: - model: - template: default:model/simplified_generated - arguments: - path: minecraft:item/custom/reed - behavior: - type: liquid_collision_block_item - block: default:reed - default:flame_cane: - material: nether_brick - custom-model-data: 4002 - data: - item-name: - model: - template: default:model/simplified_generated - arguments: - path: minecraft:item/custom/flame_cane - behavior: - type: block_item - block: default:flame_cane - default:ender_pearl_flower_seeds: - material: nether_brick - custom-model-data: 4003 - data: - item-name: - model: - template: default:model/simplified_generated - arguments: - path: minecraft:item/custom/ender_pearl_flower_seeds - behavior: - type: block_item - block: default:ender_pearl_flower -blocks: - default:fairy_flower: - settings: - template: - - default:hardness/none - - default:sound/grass - overrides: - item: default:fairy_flower - push-reaction: DESTROY - map-color: 19 - behavior: - type: bush_block - bottom-block-tags: - - minecraft:dirt - - minecraft:farmland - loot: - template: default:loot_table/self - state: - id: 0 - state: sugar_cane:0 - models: - - path: minecraft:block/custom/fairy_flower_1 - weight: 100 - - path: minecraft:block/custom/fairy_flower_2 - weight: 5 - generation: - parent: minecraft:block/custom/fairy_flower_1 - textures: - '0': minecraft:block/custom/fairy_flower_2 - - path: minecraft:block/custom/fairy_flower_3 - weight: 5 - generation: - parent: minecraft:block/custom/fairy_flower_1 - textures: - '0': minecraft:block/custom/fairy_flower_3 - - path: minecraft:block/custom/fairy_flower_4 - weight: 5 - generation: - parent: minecraft:block/custom/fairy_flower_1 - textures: - '0': minecraft:block/custom/fairy_flower_4 - default:reed: - settings: - template: - - default:hardness/none - - default:sound/grass - overrides: - push-reaction: DESTROY - map-color: 60 - behavior: - type: on_liquid_block - liquid-type: water - positions: - - 0,-1,0 - loot: - template: default:loot_table/self - state: - id: 1 - state: sugar_cane:1 - model: - path: minecraft:block/custom/reed - default:flame_cane: - settings: - template: - - default:hardness/none - - default:sound/grass - overrides: - push-reaction: DESTROY - map-color: 15 - is-randomly-ticking: true - behaviors: - - type: vertical_crop_block - max-height: 4 - grow-speed: 0.333 - direction: up - - type: bush_block - stackable: true - delay: 1 - bottom-blocks: - - minecraft:netherrack - - minecraft:soul_sand - - minecraft:soul_soil - - minecraft:magma_block - - minecraft:warped_nylium - - minecraft:crimson_nylium - - minecraft:basalt - - type: near_liquid_block - liquid-type: lava - delay: 1 - stackable: true - positions: - - -1,-1,0 - - 1,-1,0 - - 0,-1,-1 - - 0,-1,1 - loot: - template: default:loot_table/self - states: - properties: - age: - type: int - default: 0 - range: 0~5 - appearances: - default: - state: sugar_cane:2 - models: - - path: minecraft:block/custom/flame_cane_1 - weight: 1 - generation: - parent: minecraft:block/sugar_cane - textures: - cross: minecraft:block/custom/flame_cane_1 - - path: minecraft:block/custom/flame_cane_2 - weight: 1 - generation: - parent: minecraft:block/sugar_cane - textures: - cross: minecraft:block/custom/flame_cane_2 - variants: - age=0: - appearance: default - id: 2 - age=1: - appearance: default - id: 3 - age=2: - appearance: default - id: 4 - age=3: - appearance: default - id: 5 - age=4: - appearance: default - id: 6 - age=5: - appearance: default - id: 7 - default:ender_pearl_flower: - settings: - template: - - default:hardness/none - - default:sound/grass - overrides: - item: default:ender_pearl_flower_seeds - push-reaction: DESTROY - map-color: 24 - is-randomly-ticking: true - behaviors: - - type: bush_block - bottom-blocks: - - minecraft:end_stone - - type: crop_block - grow-speed: 0.25 - light-requirement: 9 - is-bone-meal-target: true - bone-meal-age-bonus: 1 - loot: - template: default:loot_table/seed_crop - arguments: - crop_item: minecraft:ender_pearl - crop_seed: default:ender_pearl_flower_seeds - ripe_age: 2 - events: - - on: break - conditions: - - type: match_block_property - properties: - age: 2 - functions: - - type: particle - x: + 0.5 - y: + 0.5 - z: + 0.5 - particle: minecraft:end_rod - count: 15 - offset-x: 0.05 - offset-y: 0.05 - offset-z: 0.05 - speed: 0.1 - - type: play_sound - sound: minecraft:entity.enderman.teleport - x: + 0.5 - y: + 0.5 - z: + 0.5 - - on: right_click - conditions: - - type: match_block_property - properties: - age: 2 - - type: '!is_null' - argument: item_in_hand - - type: equals - value1: - value2: default:ender_pearl_flower_seeds - functions: - - type: break_block - x: - y: - z: - - type: place_block - x: - y: - z: - block-state: default:ender_pearl_flower[age=0] - - type: set_count - add: true - count: -1 - - type: swing_hand - states: - properties: - age: - type: int - default: 0 - range: 0~2 - appearances: - stage_0: - state: tripwire:1 - models: - - path: minecraft:block/custom/ender_pearl_flower_stage_0 - generation: - parent: minecraft:block/cross - textures: - cross: minecraft:block/custom/ender_pearl_flower_stage_0 - stage_1: - state: tripwire:0 - models: - - path: minecraft:block/custom/ender_pearl_flower_stage_1 - generation: - parent: minecraft:block/cross - textures: - cross: minecraft:block/custom/ender_pearl_flower_stage_1 - stage_2: - state: sugar_cane:3 - models: - - path: minecraft:block/custom/ender_pearl_flower_stage_2 - generation: - parent: minecraft:block/cross - textures: - cross: minecraft:block/custom/ender_pearl_flower_stage_2 - variants: - age=0: - appearance: stage_0 - id: 0 - age=1: - appearance: stage_1 - id: 1 - age=2: - appearance: stage_2 - id: 8 -recipes: - default:paper_from_reed: - type: shaped - pattern: - - AAA - ingredients: - A: default:reed - result: - id: minecraft:paper - count: 3 - default:magma_cream: - type: shaped - pattern: - - ' A ' - - ABA - - ' A ' - ingredients: - A: default:flame_cane - B: minecraft:slime_ball - result: - id: minecraft:magma_cream - count: 1 - default:magma_block: - type: shapeless - ingredients: - A1: minecraft:cobblestone - A2: minecraft:cobblestone - B1: default:flame_cane - B2: default:flame_cane - result: - id: minecraft:magma_block - count: 2 -vanilla-loots: - minecraft:ender_pearl_flower_seeds_from_endermite: - type: entity - target: minecraft:endermite - override: false - loot: - pools: - - rolls: 1 - conditions: - - type: table_bonus - enchantment: minecraft:looting - chances: - - 0.1 - - 0.5 - - 0.8 - - 1 - entries: - - type: item - item: default:ender_pearl_flower_seeds \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/font/image/icons.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/font/image/icons.png deleted file mode 100644 index c65accf81a07942ad1ce7c2db29b6708e07a77e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!forvRT2*ZcSHYiert_xE4DdbP!5;Vw3|Wy_Won6`Cxc0OD3 z??m^#XQrkHJl;%`JA8k$&Hua2`uh4m|NZ~);RDbBMn=Z}|Nn2F>(vD0h?NBS1p~R5 zfPwMSfhRx}b)GJcAr-e$&Yc!(R^VZ|&~P>DwDXxK|Ld=E+E#5j$8;jrN5I%1?YM56 zqstGc)P$zYRF)OIYePAbDh`y(urII>IV9s0VQnIp@KWaSlclYRFN@!DF3b8WFa3Ct z_acw^v0o+!UDGc3*E}cwmFz0}2Y<>ww9fv%`n+sjb~z&(!yaZq-s~*L9H2`WJYD@< J);T3K0RX^0l1u;q diff --git a/common-files/src/main/resources/resources/default/configuration/fix_client_visual.yml b/common-files/src/main/resources/resources/internal/configuration/fix_client_visual.yml similarity index 100% rename from common-files/src/main/resources/resources/default/configuration/fix_client_visual.yml rename to common-files/src/main/resources/resources/internal/configuration/fix_client_visual.yml diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 3e106923e..45ab193d4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -355,24 +355,26 @@ public abstract class AbstractPackManager implements PackManager { } public void saveDefaultConfigs() { - // internal + // remove shulker head plugin.saveResource("resources/remove_shulker_head/resourcepack/pack.mcmeta"); plugin.saveResource("resources/remove_shulker_head/resourcepack/assets/minecraft/shaders/core/rendertype_entity_solid.fsh"); plugin.saveResource("resources/remove_shulker_head/resourcepack/1_20_5_remove_shulker_head_overlay/minecraft/shaders/core/rendertype_entity_solid.fsh"); plugin.saveResource("resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png"); plugin.saveResource("resources/remove_shulker_head/pack.yml"); + + // legacy armor plugin.saveResource("resources/legacy_armor/resourcepack/assets/minecraft/textures/trims/entity/humanoid/chainmail.png"); plugin.saveResource("resources/legacy_armor/resourcepack/assets/minecraft/textures/trims/entity/humanoid_leggings/chainmail.png"); plugin.saveResource("resources/legacy_armor/configuration/chainmail.yml"); plugin.saveResource("resources/legacy_armor/pack.yml"); + + // internal plugin.saveResource("resources/internal/pack.yml"); - // i18n plugin.saveResource("resources/internal/configuration/i18n.yml"); - // offset + plugin.saveResource("resources/internal/configuration/fix_client_visual.yml"); plugin.saveResource("resources/internal/configuration/offset_chars.yml"); - plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/offset/space_split.png"); - // gui plugin.saveResource("resources/internal/configuration/gui.yml"); + plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/offset/space_split.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/item_browser.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/category.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/blasting.png"); @@ -394,29 +396,42 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/item/custom/gui/exit.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/item/custom/gui/cooking_info.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/item/custom/gui/cooking_info.png.mcmeta"); - // default pack + + // default plugin.saveResource("resources/default/pack.yml"); // pack meta plugin.saveResource("resources/default/resourcepack/pack.mcmeta"); plugin.saveResource("resources/default/resourcepack/pack.png"); - // templates + // configs plugin.saveResource("resources/default/configuration/templates.yml"); - // emoji - plugin.saveResource("resources/default/configuration/emoji.yml"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png"); - // i18n - plugin.saveResource("resources/default/configuration/i18n.yml"); - // block_name - plugin.saveResource("resources/default/configuration/block_name.yml"); - // categories plugin.saveResource("resources/default/configuration/categories.yml"); - // for mods - plugin.saveResource("resources/default/configuration/fix_client_visual.yml"); - // icons - plugin.saveResource("resources/default/configuration/icons.yml"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/icons.png"); - // blocks - plugin.saveResource("resources/default/configuration/blocks.yml"); + plugin.saveResource("resources/default/configuration/emoji.yml"); + plugin.saveResource("resources/default/configuration/i18n.yml"); + plugin.saveResource("resources/default/configuration/items/cap.yml"); + plugin.saveResource("resources/default/configuration/items/flame_elytra.yml"); + plugin.saveResource("resources/default/configuration/items/gui_head.yml"); + plugin.saveResource("resources/default/configuration/items/topaz_armor.yml"); + plugin.saveResource("resources/default/configuration/items/topaz_tool_weapon.yml"); + plugin.saveResource("resources/default/configuration/furniture/bench.yml"); + plugin.saveResource("resources/default/configuration/furniture/wooden_chair.yml"); + plugin.saveResource("resources/default/configuration/furniture/flower_basket.yml"); + plugin.saveResource("resources/default/configuration/blocks/chessboard_block.yml"); + plugin.saveResource("resources/default/configuration/blocks/chinese_lantern.yml"); + plugin.saveResource("resources/default/configuration/blocks/copper_coil.yml"); + plugin.saveResource("resources/default/configuration/blocks/ender_pearl_flower.yml"); + plugin.saveResource("resources/default/configuration/blocks/fairy_flower.yml"); + plugin.saveResource("resources/default/configuration/blocks/flame_cane.yml"); + plugin.saveResource("resources/default/configuration/blocks/gunpowder_block.yml"); + plugin.saveResource("resources/default/configuration/blocks/palm_tree.yml"); + plugin.saveResource("resources/default/configuration/blocks/pebble.yml"); + plugin.saveResource("resources/default/configuration/blocks/reed.yml"); + plugin.saveResource("resources/default/configuration/blocks/safe_block.yml"); + plugin.saveResource("resources/default/configuration/blocks/sofa.yml"); + plugin.saveResource("resources/default/configuration/blocks/table_lamp.yml"); + plugin.saveResource("resources/default/configuration/blocks/topaz_ore.yml"); + plugin.saveResource("resources/default/configuration/blocks/netherite_anvil.yml"); + // assets + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png.mcmeta"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern_top.png"); @@ -435,8 +450,6 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_side.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/safe_block_front_open.png"); - // items - plugin.saveResource("resources/default/configuration/items.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod_cast.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_bow.png"); @@ -471,17 +484,12 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa_inner.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/sofa.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/sofa.png"); - - // ores - plugin.saveResource("resources/default/configuration/ores.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/deepslate_topaz_ore.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/deepslate_topaz_ore.png.mcmeta"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/topaz_ore.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/topaz_ore.png.mcmeta"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz.png.mcmeta"); - // palm tree - plugin.saveResource("resources/default/configuration/palm_tree.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_sapling.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_planks.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_log.png"); @@ -493,8 +501,6 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_door_top.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/palm_door_bottom.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/palm_door.png"); - // plants - plugin.saveResource("resources/default/configuration/plants.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_1.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_2.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/fairy_flower_3.png"); @@ -511,8 +517,6 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/ender_pearl_flower_seeds.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fairy_flower_1.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/reed.json"); - // furniture - plugin.saveResource("resources/default/configuration/furniture.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_in_hand.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/topaz_trident_throwing.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/table_lamp.json"); @@ -526,7 +530,6 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/item/custom/flower_basket_wall.json"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/flower_basket.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/flower_basket_2d.png"); - // tooltip plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_background.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_background.png.mcmeta"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java index 833a57d3c..853cc7c28 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ToastFunction.java @@ -3,14 +3,11 @@ package net.momirealms.craftengine.core.plugin.context.function; import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.*; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; -import net.momirealms.craftengine.core.plugin.context.text.TextProvider; -import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.EnumUtils; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index b4f530ae8..44930a9fc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -240,21 +240,27 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { if (it.charAt(0) == '#') { String subCategoryId = it.substring(1); Category subCategory = this.byId.get(Key.of(subCategoryId)); - if (subCategory == null) return null; - Item item = this.plugin.itemManager().createWrappedItem(subCategory.icon(), player); - if (ItemUtils.isEmpty(item)) { - if (!subCategory.icon().equals(ItemKeys.AIR)) { - item = this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player); + Item item; + if (subCategory == null) { + item = Objects.requireNonNull(this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player)); + item.customNameJson(AdventureHelper.componentToJson(Component.text(subCategoryId).color(NamedTextColor.RED).decoration(TextDecoration.ITALIC, false))); + } else { + item = this.plugin.itemManager().createWrappedItem(subCategory.icon(), player); + if (ItemUtils.isEmpty(item)) { + if (!subCategory.icon().equals(ItemKeys.AIR)) { + item = Objects.requireNonNull(this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player)); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY_RESOLVERS))); + item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY_RESOLVERS))).toList()); + } + } else { item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY_RESOLVERS))); item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY_RESOLVERS))).toList()); } - } else { - item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY_RESOLVERS))); - item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY_RESOLVERS))).toList()); } return new ItemWithAction(item, (element, click) -> { click.cancel(); player.playSound(Constants.SOUND_CLICK_BUTTON); + if (subCategory == null) return; openCategoryPage(click.clicker(), subCategory.id(), element.gui(), canOpenNoRecipePage); }); } else { From 6908c23c311f09872969ffe79b2722aa177443ed Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 22:09:41 +0800 Subject: [PATCH 100/226] Update table_lamp.yml --- .../resources/default/configuration/blocks/table_lamp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml index e6dbf6ceb..09671ba0e 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml @@ -14,9 +14,9 @@ items: template: default:loot_table/self settings: template: - - default:sound/metal + - default:sound/lantern overrides: - hardness: 1 + hardness: 0.5 resistance: 1 replaceable: false is-redstone-conductor: false From 9205a313c33df78c3e96432950fa899e680d8e4f Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 22:14:09 +0800 Subject: [PATCH 101/226] =?UTF-8?q?fix(block):=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E4=B8=8B=E8=90=BD=E6=96=B9=E5=9D=97=E7=9A=84=E5=90=84=E7=A7=8D?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 6 --- .../block/FallingBlockRemoveListener.java | 51 ------------------- .../block/behavior/FallingBlockBehavior.java | 5 +- gradle.properties | 2 +- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java 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 f32cb3a5b..3530fa199 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 @@ -76,7 +76,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { private List blockRegisterOrder = new ObjectArrayList<>(); // Event listeners private BlockEventListener blockEventListener; - private FallingBlockRemoveListener fallingBlockRemoveListener; // cached tag packet private Object cachedUpdateTagsPacket; @@ -101,7 +100,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { if (enableNoteBlocks) { this.recordVanillaNoteBlocks(); } - this.fallingBlockRemoveListener = VersionHelper.isOrAbove1_20_3() ? new FallingBlockRemoveListener() : null; this.stateId2ImmutableBlockStates = new ImmutableBlockState[this.customBlockCount]; Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); this.resetPacketConsumers(); @@ -123,9 +121,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedInit() { Bukkit.getPluginManager().registerEvents(this.blockEventListener, this.plugin.javaPlugin()); - if (this.fallingBlockRemoveListener != null) { - Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, this.plugin.javaPlugin()); - } } @Override @@ -145,7 +140,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { public void disable() { this.unload(); HandlerList.unregisterAll(this.blockEventListener); - if (this.fallingBlockRemoveListener != null) HandlerList.unregisterAll(this.fallingBlockRemoveListener); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java deleted file mode 100644 index 5be81793d..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/FallingBlockRemoveListener.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.momirealms.craftengine.bukkit.block; - -import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; -import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.world.WorldPosition; -import org.bukkit.entity.FallingBlock; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -import java.util.Optional; - -@SuppressWarnings("DuplicatedCode") -public final class FallingBlockRemoveListener implements Listener { - - @EventHandler - public void onFallingBlockBreak(org.bukkit.event.entity.EntityRemoveEvent event) { - if (event.getCause() == org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP && event.getEntity() instanceof FallingBlock fallingBlock) { - try { - Object fallingBlockEntity = CraftBukkitReflections.field$CraftEntity$entity.get(fallingBlock); - boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); - if (cancelDrop) return; - Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); - Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); - if (optionalCustomState.isEmpty()) return; - ImmutableBlockState customState = optionalCustomState.get(); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(fallingBlock.getWorld()); - WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.FALLING_BLOCK, true) - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : customState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); - } - Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); - boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); - if (!isSilent) { - world.playBlockSound(position, customState.settings().sounds().destroySound()); - } - } catch (ReflectiveOperationException e) { - CraftEngine.instance().logger().warn("Failed to handle EntityRemoveEvent", e); - } - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index 308e0168e..fa353e850 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -14,7 +14,6 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldPosition; @@ -67,7 +66,7 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { return; } Object blockState = args[0]; - Object fallingBlockEntity = CoreReflections.method$FallingBlockEntity$fall.invoke(null, world, blockPos, blockState); + Object fallingBlockEntity = FastNMS.INSTANCE.createInjectedFallingBlockEntity(world, blockPos, blockState); if (this.hurtAmount > 0 && this.maxHurt > 0) { CoreReflections.method$FallingBlockEntity$setHurtsEntities.invoke(fallingBlockEntity, this.hurtAmount, this.maxHurt); } @@ -76,8 +75,6 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("DuplicatedCode") @Override public void onBrokenAfterFall(Object thisBlock, Object[] args) throws Exception { - // Use EntityRemoveEvent for 1.20.3+ - if (VersionHelper.isOrAbove1_20_3()) return; Object level = args[0]; Object fallingBlockEntity = args[2]; boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); diff --git a/gradle.properties b/gradle.properties index 7f85f1eb8..a73151edb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.83 +nms_helper_version=1.0.84 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 918bd6380d89fed62356483a7701b5a760f28318 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 12 Sep 2025 22:57:06 +0800 Subject: [PATCH 102/226] =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../default/configuration/blocks/sofa.yml | 24 +++++----- .../configuration/fix_client_visual.yml | 46 +++++++++++++------ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index d13094095..5a94e2c20 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -90,59 +90,59 @@ items: type: sofa_shape appearances: facing=east,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa yaw: 90 facing=north,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa facing=south,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa yaw: 180 facing=west,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa yaw: 270 facing=east,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner facing=north,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 270 facing=south,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 90 facing=west,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 180 facing=east,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 90 facing=north,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner facing=south,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 180 facing=west,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 270 diff --git a/common-files/src/main/resources/resources/internal/configuration/fix_client_visual.yml b/common-files/src/main/resources/resources/internal/configuration/fix_client_visual.yml index 440e9964b..17def2bac 100644 --- a/common-files/src/main/resources/resources/internal/configuration/fix_client_visual.yml +++ b/common-files/src/main/resources/resources/internal/configuration/fix_client_visual.yml @@ -3,22 +3,40 @@ items: minecraft:string: client-bound-data: - components: - minecraft:block_state: - attached: 'false' - disarmed: 'false' - east: 'true' - north: 'true' - powered: 'true' - south: 'true' - west: 'true' + $$>=1.20.5: + components: + minecraft:block_state: + attached: 'false' + disarmed: 'false' + east: 'true' + north: 'true' + powered: 'true' + south: 'true' + west: 'true' + $$fallback: + nbt: + BlockStateTag: + attached: 'false' + disarmed: 'false' + east: 'true' + north: 'true' + powered: 'true' + south: 'true' + west: 'true' minecraft:note_block: client-bound-data: - components: - minecraft:block_state: - instrument: harp - powered: 'false' - note: '0' + $$>=1.20.5: + components: + minecraft:block_state: + instrument: harp + powered: 'false' + note: '0' + $$fallback: + nbt: + BlockStateTag: + instrument: harp + powered: 'false' + note: '0' # For the client to determine if a beacon can activate, it needs the beacon_base_blocks tag to render the beam. # This allows custom blocks (like note blocks) to work as beacon bases. # However, whether the beacon actually grants potion effects depends on the block's real tag (server-side check). From 4ad4d8c3ebb65c59a710318f046d0723838a701e Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 23:01:18 +0800 Subject: [PATCH 103/226] =?UTF-8?q?fix(block):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=8B=E8=90=BD=E7=9A=84=E6=96=B9=E5=9D=97=E5=88=B7=E7=89=A9?= =?UTF-8?q?=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 上个pr忘记删除历史遗留问题写的代码导致掉落了2次 - 修复低版本的屏障不能含水导致的问题 - 修复低版本下界合金砧如果可以摔死没声音的问题 --- .../block/behavior/FallingBlockBehavior.java | 20 +++++----------- .../default/configuration/blocks/sofa.yml | 24 +++++++++---------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index fa353e850..10911e79c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -77,23 +77,15 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { public void onBrokenAfterFall(Object thisBlock, Object[] args) throws Exception { Object level = args[0]; Object fallingBlockEntity = args[2]; - boolean cancelDrop = (boolean) CoreReflections.field$FallingBlockEntity$cancelDrop.get(fallingBlockEntity); - if (cancelDrop) return; - Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); - Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); - if (optionalCustomState.isEmpty()) return; - ImmutableBlockState customState = optionalCustomState.get(); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.FALLING_BLOCK, true) - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : customState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); - } Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); if (!isSilent) { + Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCustomState.isEmpty()) return; + ImmutableBlockState customState = optionalCustomState.get(); + net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); world.playBlockSound(position, customState.settings().sounds().destroySound()); } } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index d13094095..5a94e2c20 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -90,59 +90,59 @@ items: type: sofa_shape appearances: facing=east,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa yaw: 90 facing=north,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa facing=south,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa yaw: 180 facing=west,shape=straight: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa yaw: 270 facing=east,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner facing=north,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 270 facing=south,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 90 facing=west,shape=inner_left: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 180 facing=east,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 90 facing=north,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner facing=south,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 180 facing=west,shape=inner_right: - state: barrier[waterlogged=false] + state: barrier entity-renderer: item: default:sofa_inner yaw: 270 From 31bfdd0bf0b3df069b6a2f93c388e80b3c574e91 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 23:29:04 +0800 Subject: [PATCH 104/226] =?UTF-8?q?fix(block):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=8B=E8=90=BD=E7=9A=84=E6=96=B9=E5=9D=97=E4=B8=8D=E8=B5=B0?= =?UTF-8?q?ce=E6=88=98=E5=88=A9=E5=93=81=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a73151edb..4770bc750 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.84 +nms_helper_version=1.0.85 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 384162cc1e5e0781aac78235aceddafd2d1c2f82 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 13 Sep 2025 05:27:44 +0800 Subject: [PATCH 105/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0bm=E5=92=8Cmeg?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=93=E6=B8=B2=E6=9F=93=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BukkitCompatibilityManager.java | 9 +++ .../BetterModelBlockEntityElement.java | 62 +++++++++++++++++ .../BetterModelBlockEntityElementConfig.java | 61 ++++++++++++++++ .../model/bettermodel/BetterModelUtils.java | 6 ++ .../ModelEngineBlockEntityElement.java | 69 +++++++++++++++++++ .../ModelEngineBlockEntityElementConfig.java | 61 ++++++++++++++++ .../model/modelengine/ModelEngineUtils.java | 6 ++ .../block/behavior/FallingBlockBehavior.java | 3 - ...=> BukkitConstantBlockEntityRenderer.java} | 58 +++++++++------- .../ItemDisplayBlockEntityElement.java | 4 -- .../ItemDisplayBlockEntityElementConfig.java | 3 +- .../TextDisplayBlockEntityElement.java | 4 -- .../TextDisplayBlockEntityElementConfig.java | 3 +- .../bukkit/plugin/BukkitCraftEngine.java | 3 + .../plugin/injector/WorldStorageInjector.java | 6 +- .../bukkit/world/BukkitCEWorld.java | 12 ++-- .../src/main/resources/translations/en.yml | 3 + .../core/block/AbstractCustomBlock.java | 2 +- .../core/block/ImmutableBlockState.java | 10 +-- .../core/block/entity/BlockEntity.java | 7 ++ ....java => ConstantBlockEntityRenderer.java} | 10 +-- .../render/element/BlockEntityElement.java | 6 +- .../element/BlockEntityElementConfig.java | 3 +- .../craftengine/core/plugin/CraftEngine.java | 1 + .../core/plugin/config/Config.java | 2 +- .../craftengine/core/world/CEWorld.java | 6 +- .../craftengine/core/world/chunk/CEChunk.java | 21 +++--- gradle.properties | 2 +- 28 files changed, 368 insertions(+), 75 deletions(-) create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/{BukkitBlockEntityRenderer.java => BukkitConstantBlockEntityRenderer.java} (63%) rename core/src/main/java/net/momirealms/craftengine/core/block/entity/render/{BlockEntityRenderer.java => ConstantBlockEntityRenderer.java} (60%) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index b57cd1405..c4ca0328e 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -5,6 +5,7 @@ import net.momirealms.craftengine.bukkit.compatibility.item.*; import net.momirealms.craftengine.bukkit.compatibility.legacy.slimeworld.LegacySlimeFormatStorageAdaptor; import net.momirealms.craftengine.bukkit.compatibility.leveler.*; import net.momirealms.craftengine.bukkit.compatibility.model.bettermodel.BetterModelModel; +import net.momirealms.craftengine.bukkit.compatibility.model.bettermodel.BetterModelUtils; import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineModel; import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineUtils; import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicItemDropListener; @@ -130,6 +131,14 @@ public class BukkitCompatibilityManager implements CompatibilityManager { EventConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>()); LootConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>()); } + if (this.isPluginEnabled("BetterModel")) { + BetterModelUtils.registerConstantBlockEntityRender(); + logHook("BetterModel"); + } + if (this.isPluginEnabled("ModelEngine")) { + ModelEngineUtils.registerConstantBlockEntityRender(); + logHook("ModelEngine"); + } } @Override diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java new file mode 100644 index 000000000..bf8e180ba --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java @@ -0,0 +1,62 @@ +package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel; + +import kr.toxicity.model.api.BetterModel; +import kr.toxicity.model.api.data.renderer.ModelRenderer; +import kr.toxicity.model.api.tracker.DummyTracker; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import org.bukkit.Location; +import org.joml.Vector3f; + +public class BetterModelBlockEntityElement implements BlockEntityElement { + private DummyTracker dummyTracker; + private final Location location; + private final BetterModelBlockEntityElementConfig config; + + public BetterModelBlockEntityElement(World world, BlockPos pos, BetterModelBlockEntityElementConfig config) { + this.config = config; + Vector3f position = config.position(); + this.location = new Location((org.bukkit.World) world.platformWorld(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, config.yaw(), config.pitch()); + this.dummyTracker = createDummyTracker(); + } + + private DummyTracker createDummyTracker() { + ModelRenderer modelRenderer = BetterModel.plugin().modelManager().renderer(this.config.model()); + if (modelRenderer == null) { + return null; + } else { + return modelRenderer.create(this.location); + } + } + + @Override + public void despawn(Player player) { + if (this.dummyTracker != null) { + this.dummyTracker.remove((org.bukkit.entity.Player) player.platformPlayer()); + } + } + + @Override + public void spawn(Player player) { + if (this.dummyTracker != null) { + this.dummyTracker.spawn((org.bukkit.entity.Player) player.platformPlayer()); + } + } + + @Override + public void deactivate() { + if (this.dummyTracker != null) { + this.dummyTracker.close(); + this.dummyTracker = null; + } + } + + @Override + public void activate() { + if (this.dummyTracker == null) { + this.dummyTracker = createDummyTracker(); + } + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java new file mode 100644 index 000000000..0b31cf939 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java @@ -0,0 +1,61 @@ +package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel; + +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import org.joml.Vector3f; + +import java.util.Map; + +public class BetterModelBlockEntityElementConfig implements BlockEntityElementConfig { + private final Vector3f position; + private final float yaw; + private final float pitch; + private final String model; + + public BetterModelBlockEntityElementConfig(String model, Vector3f position, float yaw, float pitch) { + this.pitch = pitch; + this.position = position; + this.yaw = yaw; + this.model = model; + } + + public String model() { + return model; + } + + public float pitch() { + return pitch; + } + + public Vector3f position() { + return position; + } + + public float yaw() { + return yaw; + } + + @Override + public BetterModelBlockEntityElement create(World world, BlockPos pos) { + return new BetterModelBlockEntityElement(world, pos, this); + } + + public static class Factory implements BlockEntityElementConfigFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockEntityElementConfig create(Map arguments) { + String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.better_model.missing_model"); + return (BlockEntityElementConfig) new BetterModelBlockEntityElementConfig( + model, + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch") + ); + } + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelUtils.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelUtils.java index 73e568774..507901517 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelUtils.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelUtils.java @@ -2,6 +2,8 @@ package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel; import kr.toxicity.model.api.BetterModel; import kr.toxicity.model.api.data.renderer.ModelRenderer; +import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs; +import net.momirealms.craftengine.core.util.Key; import org.bukkit.entity.Entity; public class BetterModelUtils { @@ -13,4 +15,8 @@ public class BetterModelUtils { } renderer.create(base); } + + public static void registerConstantBlockEntityRender() { + BukkitBlockEntityElementConfigs.register(Key.of("craftengine:better_model"), new BetterModelBlockEntityElementConfig.Factory()); + } } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java new file mode 100644 index 000000000..5e3660563 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java @@ -0,0 +1,69 @@ +package net.momirealms.craftengine.bukkit.compatibility.model.modelengine; + +import com.ticxo.modelengine.api.ModelEngineAPI; +import com.ticxo.modelengine.api.entity.Dummy; +import com.ticxo.modelengine.api.model.ActiveModel; +import com.ticxo.modelengine.api.model.ModeledEntity; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import org.bukkit.Location; +import org.joml.Vector3f; + +// TODO not tested yet +public class ModelEngineBlockEntityElement implements BlockEntityElement { + private Dummy dummy; + private final Location location; + private final ModelEngineBlockEntityElementConfig config; + + public ModelEngineBlockEntityElement(World world, BlockPos pos, ModelEngineBlockEntityElementConfig config) { + this.config = config; + Vector3f position = config.position(); + this.location = new Location((org.bukkit.World) world.platformWorld(), pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, config.yaw(), config.pitch()); + this.dummy = createDummy(); + } + + private Dummy createDummy() { + ActiveModel activeModel = ModelEngineAPI.createActiveModel(config.model()); + if (activeModel == null) { + return null; + } else { + Dummy dummy = new Dummy<>(); + dummy.setLocation(this.location); + dummy.setDetectingPlayers(false); + ModeledEntity modeledEntity = ModelEngineAPI.createModeledEntity(dummy); + modeledEntity.addModel(activeModel, false); + return dummy; + } + } + + @Override + public void despawn(Player player) { + if (this.dummy != null) { + this.dummy.setForceViewing((org.bukkit.entity.Player) player.platformPlayer(), true); + } + } + + @Override + public void spawn(Player player) { + if (this.dummy != null) { + this.dummy.setForceHidden((org.bukkit.entity.Player) player.platformPlayer(), true); + } + } + + @Override + public void deactivate() { + if (this.dummy != null) { + this.dummy.setRemoved(true); + this.dummy = null; + } + } + + @Override + public void activate() { + if (this.dummy == null) { + this.dummy = createDummy(); + } + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java new file mode 100644 index 000000000..a8dbd2643 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java @@ -0,0 +1,61 @@ +package net.momirealms.craftengine.bukkit.compatibility.model.modelengine; + +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import org.joml.Vector3f; + +import java.util.Map; + +public class ModelEngineBlockEntityElementConfig implements BlockEntityElementConfig { + private final Vector3f position; + private final float yaw; + private final float pitch; + private final String model; + + public ModelEngineBlockEntityElementConfig(String model, Vector3f position, float yaw, float pitch) { + this.pitch = pitch; + this.position = position; + this.yaw = yaw; + this.model = model; + } + + public String model() { + return model; + } + + public float pitch() { + return pitch; + } + + public Vector3f position() { + return position; + } + + public float yaw() { + return yaw; + } + + @Override + public ModelEngineBlockEntityElement create(World world, BlockPos pos) { + return new ModelEngineBlockEntityElement(world, pos, this); + } + + public static class Factory implements BlockEntityElementConfigFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockEntityElementConfig create(Map arguments) { + String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.model_engine.missing_model"); + return (BlockEntityElementConfig) new ModelEngineBlockEntityElementConfig( + model, + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch") + ); + } + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineUtils.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineUtils.java index b32074691..156537804 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineUtils.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineUtils.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.compatibility.model.modelengine; import com.ticxo.modelengine.api.ModelEngineAPI; import com.ticxo.modelengine.api.model.ActiveModel; import com.ticxo.modelengine.api.model.ModeledEntity; +import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs; +import net.momirealms.craftengine.core.util.Key; import org.bukkit.entity.Entity; public class ModelEngineUtils { @@ -24,4 +26,8 @@ public class ModelEngineUtils { } return entityId; } + + public static void registerConstantBlockEntityRender() { + BukkitBlockEntityElementConfigs.register(Key.of("craftengine:model_engine"), new ModelEngineBlockEntityElementConfig.Factory()); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index 10911e79c..e41956aec 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -10,9 +10,6 @@ import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; -import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldPosition; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java similarity index 63% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java index 6c7e14267..9ca1bc4f1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitBlockEntityRenderer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java @@ -3,25 +3,36 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.ChunkPos; +import net.momirealms.craftengine.core.world.World; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; -public class BukkitBlockEntityRenderer extends BlockEntityRenderer { +public class BukkitConstantBlockEntityRenderer extends ConstantBlockEntityRenderer { private final BlockEntityElement[] elements; - private final WeakReference chunkHolder; + private final World world; + private final ChunkPos chunkPos; - public BukkitBlockEntityRenderer(WeakReference chunkHolder, BlockEntityElement[] elements) { - this.chunkHolder = chunkHolder; + public BukkitConstantBlockEntityRenderer(World world, ChunkPos pos, BlockEntityElement[] elements) { + this.world = world; + this.chunkPos = pos; this.elements = elements; } + private Object getChunkHolder() { + Object serverLevel = this.world.serverWorld(); + Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); + return FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, this.chunkPos.longKey); + } + @Override public void despawn() { - List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); + List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.getChunkHolder()); if (players.isEmpty()) return; for (Object player : players) { org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); @@ -33,7 +44,7 @@ public class BukkitBlockEntityRenderer extends BlockEntityRenderer { @Override public void spawn() { - List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); + List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.getChunkHolder()); if (players.isEmpty()) return; for (Object player : players) { org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); @@ -43,25 +54,6 @@ public class BukkitBlockEntityRenderer extends BlockEntityRenderer { } } - @Override - public void update() { - List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.chunkHolder.get()); - if (players.isEmpty()) return; - for (Object player : players) { - org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); - BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(bkPlayer); - if (serverPlayer == null) continue; - update(serverPlayer); - } - } - - @Override - public void update(Player player) { - for (BlockEntityElement element : this.elements) { - element.update(player); - } - } - @Override public void spawn(Player player) { for (BlockEntityElement element : this.elements) { @@ -75,4 +67,18 @@ public class BukkitBlockEntityRenderer extends BlockEntityRenderer { element.despawn(player); } } + + @Override + public void deactivate() { + for (BlockEntityElement element : this.elements) { + element.deactivate(); + } + } + + @Override + public void activate() { + for (BlockEntityElement element : this.elements) { + element.activate(); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java index f60666842..ae5b42482 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java @@ -39,8 +39,4 @@ public class ItemDisplayBlockEntityElement implements BlockEntityElement { public void spawn(Player player) { player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), true); } - - @Override - public void update(Player player) { - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java index b70d1136a..a42838b72 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -65,7 +66,7 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo } @Override - public ItemDisplayBlockEntityElement create(BlockPos pos) { + public ItemDisplayBlockEntityElement create(World world, BlockPos pos) { return new ItemDisplayBlockEntityElement(this, pos); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java index f3bd9ce31..d3a8e8485 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java @@ -39,8 +39,4 @@ public class TextDisplayBlockEntityElement implements BlockEntityElement { public void spawn(Player player) { player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), true); } - - @Override - public void update(Player player) { - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java index 88fee6a01..e707023ee 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -61,7 +62,7 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo } @Override - public TextDisplayBlockEntityElement create(BlockPos pos) { + public TextDisplayBlockEntityElement create(World world, BlockPos pos) { return new TextDisplayBlockEntityElement(this, pos); } 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 6633b1fa3..3f98c3a30 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 @@ -40,11 +40,14 @@ import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImp import net.momirealms.craftengine.core.plugin.locale.TranslationManagerImpl; import net.momirealms.craftengine.core.plugin.logger.JavaPluginLogger; import net.momirealms.craftengine.core.plugin.logger.PluginLogger; +import net.momirealms.craftengine.core.plugin.logger.filter.DisconnectLogFilter; +import net.momirealms.craftengine.core.plugin.logger.filter.LogFilter; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; +import org.apache.logging.log4j.LogManager; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.World; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 2b299b534..e4d1bb197 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -241,7 +241,7 @@ public final class WorldStorageInjector { chunk.removeBlockEntity(pos); } } - if (previousImmutableBlockState.hasBlockEntityRenderer()) { + if (previousImmutableBlockState.hasConstantBlockEntityRenderer()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); chunk.removeBlockEntityRenderer(pos); } @@ -264,7 +264,7 @@ public final class WorldStorageInjector { chunk.replaceOrCreateTickingBlockEntity(blockEntity); } } - if (newImmutableBlockState.hasBlockEntityRenderer()) { + if (newImmutableBlockState.hasConstantBlockEntityRenderer()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); chunk.addBlockEntityRenderer(pos, newImmutableBlockState); } @@ -294,7 +294,7 @@ public final class WorldStorageInjector { chunk.removeBlockEntity(pos); } } - if (previous.hasBlockEntityRenderer()) { + if (previous.hasConstantBlockEntityRenderer()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); chunk.removeBlockEntityRenderer(pos); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index e9a079384..6e21a56f5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -1,9 +1,9 @@ package net.momirealms.craftengine.bukkit.world; -import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitBlockEntityRenderer; +import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitConstantBlockEntityRenderer; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.LightUtils; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; @@ -45,11 +45,7 @@ public class BukkitCEWorld extends CEWorld { } @Override - public BlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, BlockPos pos) { - Object serverLevel = this.world.serverWorld(); - Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); - long chunkKey = ChunkPos.asLong(pos.x() >> 4, pos.z() >> 4); - Object chunkHolder = FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, chunkKey); - return new BukkitBlockEntityRenderer(new WeakReference<>(chunkHolder), elements); + public ConstantBlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, World world, BlockPos pos) { + return new BukkitConstantBlockEntityRenderer(world, new ChunkPos(pos), elements); } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 42e20d0f1..c9603912e 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -263,6 +263,9 @@ warning.config.block.state.missing_appearances: "Issue found in file Issue found in file - The block '' is missing the required 'variants' section for 'states'." warning.config.block.state.entity_renderer.invalid_type: "Issue found in file - The block '' is using an invalid entity renderer type ''." warning.config.block.state.entity_renderer.item_display.missing_item: "Issue found in file - The block '' is missing the required 'item' argument for 'item_display' entity renderer." +warning.config.block.state.entity_renderer.text_display.missing_text: "Issue found in file - The block '' is missing the required 'text' argument for 'text_display' entity renderer." +warning.config.block.state.entity_renderer.better_model.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'better_model' entity renderer." +warning.config.block.state.entity_renderer.model_engine.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'model_engine' entity renderer." warning.config.block.state.variant.missing_appearance: "Issue found in file - The block '' is missing the required 'appearance' argument for variant ''." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index 52a246e06..92593999c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -86,7 +86,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { state.setSettings(blockStateVariant.settings()); state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(stateId)); state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); - blockStateAppearance.blockEntityRenderer().ifPresent(state::setRenderers); + blockStateAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } // double check if there's any invalid state diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 8eb70038f..8bdeb7115 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -69,11 +69,11 @@ public final class ImmutableBlockState extends BlockStateHolder { return this == EmptyBlock.STATE; } - public BlockEntityElementConfig[] renderers() { - return renderers; + public BlockEntityElementConfig[] constantRenderers() { + return this.renderers; } - public void setRenderers(BlockEntityElementConfig[] renderers) { + public void setConstantRenderers(BlockEntityElementConfig[] renderers) { this.renderers = renderers; } @@ -81,8 +81,8 @@ public final class ImmutableBlockState extends BlockStateHolder { return this.blockEntityType != null; } - public boolean hasBlockEntityRenderer() { - return this.renderers != null ; + public boolean hasConstantBlockEntityRenderer() { + return this.renderers != null; } public BlockStateWrapper customBlockState() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java index 566a75f08..73f715209 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -1,11 +1,13 @@ package net.momirealms.craftengine.core.block.entity; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.SectionPos; import net.momirealms.sparrow.nbt.CompoundTag; +import org.jetbrains.annotations.Nullable; public abstract class BlockEntity { protected final BlockPos pos; @@ -96,6 +98,11 @@ public abstract class BlockEntity { return pos; } + @Nullable + public ConstantBlockEntityRenderer[] getBlockEntityRenderers() { + return null; + } + public boolean isValidBlockState(ImmutableBlockState blockState) { return this.type == blockState.blockEntityType(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java similarity index 60% rename from core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java rename to core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index 4ed0d9ab8..b6e45acf8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/BlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -1,18 +1,20 @@ package net.momirealms.craftengine.core.block.entity.render; import net.momirealms.craftengine.core.entity.player.Player; +import org.jetbrains.annotations.ApiStatus; -public abstract class BlockEntityRenderer { +@ApiStatus.Experimental +public abstract class ConstantBlockEntityRenderer { public abstract void spawn(); public abstract void despawn(); - public abstract void update(); - public abstract void spawn(Player player); public abstract void despawn(Player player); - public abstract void update(Player player); + public abstract void deactivate(); + + public abstract void activate(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java index 7ce7e22de..d098200bd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java @@ -1,12 +1,16 @@ package net.momirealms.craftengine.core.block.entity.render.element; import net.momirealms.craftengine.core.entity.player.Player; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Experimental public interface BlockEntityElement { void spawn(Player player); void despawn(Player player); - void update(Player player); + default void deactivate() {} + + default void activate() {} } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java index 32d7bf4ae..a61f0b030 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java @@ -1,8 +1,9 @@ package net.momirealms.craftengine.core.block.entity.render.element; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; public interface BlockEntityElementConfig { - E create(BlockPos pos); + E create(World world, BlockPos pos); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 276dc3ee8..25bb1acf6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -107,6 +107,7 @@ public abstract class CraftEngine implements Plugin { LegacyRecipeTypes.init(); ((Logger) LogManager.getRootLogger()).addFilter(new LogFilter()); ((Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter()); + this.config.load(); } public record ReloadResult(boolean success, long asyncTime, long syncTime) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index f87c745e8..922cd4286 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -104,7 +104,7 @@ public class Config { protected boolean chunk_system$restore_vanilla_blocks_on_chunk_unload; protected boolean chunk_system$restore_custom_blocks_on_chunk_load; protected boolean chunk_system$sync_custom_blocks_on_chunk_load; - protected boolean chunk_system$cache_system; + protected boolean chunk_system$cache_system = true; protected boolean chunk_system$injection$use_fast_method; protected boolean chunk_system$injection$target; protected boolean chunk_system$process_invalid_furniture$enable; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 998af6ac1..21ec02ff0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -4,7 +4,7 @@ import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTabl import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -89,7 +89,7 @@ public abstract class CEWorld { } public World world() { - return world; + return this.world; } public boolean isChunkLoaded(final long chunkPos) { @@ -220,5 +220,5 @@ public abstract class CEWorld { this.isTickingBlockEntities = false; } - public abstract BlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, BlockPos pos); + public abstract ConstantBlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, World world, BlockPos pos); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index beab7ec93..55706f1b4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.entity.render.BlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.tick.*; @@ -26,7 +26,7 @@ public class CEChunk { public final CESection[] sections; public final WorldHeight worldHeightAccessor; public final Map blockEntities; - public final Map blockEntityRenderers; + public final Map blockEntityRenderers; private final ReentrantReadWriteLock renderLock = new ReentrantReadWriteLock(); private volatile boolean dirty; private volatile boolean loaded; @@ -80,7 +80,7 @@ public class CEChunk { public void spawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (BlockEntityRenderer renderer : this.blockEntityRenderers.values()) { + for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) { renderer.spawn(player); } } finally { @@ -91,7 +91,7 @@ public class CEChunk { public void despawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (BlockEntityRenderer renderer : this.blockEntityRenderers.values()) { + for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) { renderer.despawn(player); } } finally { @@ -104,13 +104,14 @@ public class CEChunk { } public void addBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) { - BlockEntityElementConfig[] renderers = state.renderers(); + BlockEntityElementConfig[] renderers = state.constantRenderers(); if (renderers != null && renderers.length > 0) { BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; + World wrappedWorld = this.world.world(); for (int i = 0; i < elements.length; i++) { - elements[i] = renderers[i].create(pos); + elements[i] = renderers[i].create(wrappedWorld, pos); } - BlockEntityRenderer renderer = this.world.createBlockEntityRenderer(elements, pos); + ConstantBlockEntityRenderer renderer = this.world.createBlockEntityRenderer(elements, wrappedWorld, pos); renderer.spawn(); try { this.renderLock.writeLock().lock(); @@ -124,7 +125,7 @@ public class CEChunk { public void removeBlockEntityRenderer(BlockPos pos) { try { this.renderLock.writeLock().lock(); - BlockEntityRenderer removed = this.blockEntityRenderers.remove(pos); + ConstantBlockEntityRenderer removed = this.blockEntityRenderers.remove(pos); if (removed != null) { removed.despawn(); } @@ -151,10 +152,14 @@ public class CEChunk { blockEntity.setValid(true); replaceOrCreateTickingBlockEntity(blockEntity); } + for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) { + renderer.activate(); + } } public void deactivateAllBlockEntities() { this.blockEntities.values().forEach(e -> e.setValid(false)); + this.blockEntityRenderers.values().forEach(ConstantBlockEntityRenderer::deactivate); this.tickingBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); this.tickingBlockEntitiesByPos.clear(); } diff --git a/gradle.properties b/gradle.properties index 4770bc750..79f5433c6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.18 +project_version=0.0.62.19 config_version=45 lang_version=27 project_group=net.momirealms From 7d567dca54e145de4b7505503cc8a7c75cab6e3a Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 13 Sep 2025 05:33:07 +0800 Subject: [PATCH 106/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/bukkit/block/BukkitBlockManager.java | 2 +- common-files/src/main/resources/translations/en.yml | 2 +- common-files/src/main/resources/translations/zh_cn.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 3530fa199..a63b539be 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 @@ -214,7 +214,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { for (ImmutableBlockState state : customBlock.variantProvider().states()) { ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()]; if (previous != null && !previous.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.block.state.bind_failed", state.toString(), previous.toString()); + throw new LocalizedResourceConfigException("warning.config.block.state.bind_failed", state.toString(), previous.toString(), BlockStateUtils.getBlockOwnerIdFromState(previous.customBlockState().literalObject()).toString()); } this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state; this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId()); diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index c9603912e..bde6e388d 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -272,7 +272,7 @@ warning.config.block.state.invalid_vanilla: "Issue found in file warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in mappings.yml." warning.config.block.state.invalid_vanilla_id: "Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." warning.config.block.state.conflict: "Issue found in file - The block '' is using a vanilla block state '' that has been occupied by ''." -warning.config.block.state.bind_failed: "Issue found in file - The block '' failed to bind real block state for '' as the state has been occupied by ''." +warning.config.block.state.bind_failed: "Issue found in file - The block '' failed to bind real block state '' for '' as the state has been occupied by ''." warning.config.block.state.invalid_real_id: "Issue found in file - The block '' is using a real block state '' that exceeds the available slot range '0~'. Consider adding more real states in 'additional-real-blocks.yml' if the slots are used up." warning.config.block.state.model.missing_path: "Issue found in file - The block '' is missing the required 'path' option for 'model'." warning.config.block.state.model.invalid_path: "Issue found in file - The block '' has a 'path' argument '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index d78d52562..b1f2fb30d 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -267,7 +267,7 @@ warning.config.block.state.invalid_vanilla: "在文件 发现问 warning.config.block.state.unavailable_vanilla: "在文件 发现问题 - 方块 '' 使用了不可用的原版方块状态 '' 请在 mappings.yml 中释放该状态" warning.config.block.state.invalid_vanilla_id: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 超出可用槽位范围 '0~'" warning.config.block.state.conflict: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 已被 '' 占用" -warning.config.block.state.bind_failed: "在文件 发现问题 - 方块 '' 无法为 '' 绑定真实方块状态 因该状态已被 '' 占用" +warning.config.block.state.bind_failed: "在文件 发现问题 - 方块 '' 无法为 '' 绑定真实方块状态 '' 因该状态已被 '' 占用" warning.config.block.state.invalid_real_id: "在文件 发现问题 - 方块 '' 使用的真实方块状态 '' 超出可用槽位范围 '0~' 如果槽位已用尽 请在 additional-real-blocks.yml 中添加更多真实状态" warning.config.block.state.model.missing_path: "在文件 发现问题 - 方块 '' 的 'model' 缺少必需的 'path' 选项" warning.config.block.state.model.invalid_path: "在文件 发现问题 - 方块 '' 的 'path' 参数 '' 包含非法字符 请参考 https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" From 5eb899f8d3854d12456db2cd9e5dce40dbd2ee26 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 13 Sep 2025 11:47:11 +0800 Subject: [PATCH 107/226] =?UTF-8?q?feat(block):=20=E8=AE=A9=E7=AE=80?= =?UTF-8?q?=E5=8D=95=E5=AD=98=E5=82=A8=E6=96=B9=E5=9D=97=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A2=AB=E6=BC=8F=E6=96=97=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/SimpleStorageBlockBehavior.java | 34 ++++++++++++++++++- .../entity/SimpleStorageBlockEntity.java | 4 +-- .../bukkit/plugin/gui/BukkitGuiManager.java | 16 ++++++--- .../plugin/user/BukkitServerPlayer.java | 4 ++- .../configuration/blocks/safe_block.yml | 3 ++ .../craftengine/core/block/BlockBehavior.java | 2 +- gradle.properties | 2 +- 7 files changed, 55 insertions(+), 10 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java index c745de2d1..9da43059e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -42,6 +42,8 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E private final SoundData openSound; private final SoundData closeSound; private final boolean hasAnalogOutputSignal; + private final boolean canPlaceItem; + private final boolean canTakeItem; @Nullable private final Property openProperty; @@ -51,6 +53,8 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E SoundData openSound, SoundData closeSound, boolean hasAnalogOutputSignal, + boolean canPlaceItem, + boolean canTakeItem, @Nullable Property openProperty) { super(customBlock); this.containerTitle = containerTitle; @@ -58,6 +62,8 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E this.openSound = openSound; this.closeSound = closeSound; this.hasAnalogOutputSignal = hasAnalogOutputSignal; + this.canPlaceItem = canPlaceItem; + this.canTakeItem = canTakeItem; this.openProperty = openProperty; } @@ -129,6 +135,14 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E return this.rows; } + public boolean canPlaceItem() { + return this.canPlaceItem; + } + + public boolean canTakeItem() { + return this.canTakeItem; + } + public @Nullable Property openProperty() { return openProperty; } @@ -164,6 +178,17 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E return this.hasAnalogOutputSignal; } + @Override + public Object getContainer(Object thisBlock, Object[] args) { + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(args[1])); + BlockPos blockPos = LocationUtils.fromBlockPos(args[2]); + BlockEntity blockEntity = ceWorld.getBlockEntityAtIfLoaded(blockPos); + if (blockEntity instanceof SimpleStorageBlockEntity entity) { + return FastNMS.INSTANCE.method$CraftInventory$getInventory(entity.inventory()); + } + return null; + } + public static class Factory implements BlockBehaviorFactory { @SuppressWarnings("unchecked") @@ -179,8 +204,15 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E openSound = Optional.ofNullable(sounds.get("open")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); closeSound = Optional.ofNullable(sounds.get("close")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); } + Map hopperBehavior = (Map) arguments.get("hopper-behavior"); + boolean canPlaceItem = true; + boolean canTakeItem = true; + if (hopperBehavior != null) { + canPlaceItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("can-place-item", true), "can-place-item"); + canTakeItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("can-take-item", true), "can-take-item"); + } Property property = (Property) block.getProperty("open"); - return new SimpleStorageBlockBehavior(block, title, rows, openSound, closeSound, hasAnalogOutputSignal, property); + return new SimpleStorageBlockBehavior(block, title, rows, openSound, closeSound, hasAnalogOutputSignal, canPlaceItem, canTakeItem, property); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index c15a2d687..60bfd9399 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.ListTag; -import org.bukkit.Bukkit; import org.bukkit.GameEvent; import org.bukkit.GameMode; import org.bukkit.entity.HumanEntity; @@ -42,7 +41,8 @@ public class SimpleStorageBlockEntity extends BlockEntity { super(BukkitBlockEntityTypes.SIMPLE_STORAGE, pos, blockState); this.behavior = super.blockState.behavior().getAs(SimpleStorageBlockBehavior.class).orElseThrow(); BlockEntityHolder holder = new BlockEntityHolder(this); - this.inventory = Bukkit.createInventory(holder, this.behavior.rows() * 9); + this.inventory = FastNMS.INSTANCE.createCraftEngineWorldlyContainer(holder, this.behavior.rows() * 9, this.behavior.canPlaceItem(), this.behavior.canTakeItem()); + holder.setInventory(this.inventory); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index e01f4d056..eb1fcd7ec 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -98,7 +98,9 @@ public class BukkitGuiManager implements GuiManager, Listener { @EventHandler(ignoreCancelled = true) public void onInventoryClick(InventoryClickEvent event) { org.bukkit.inventory.Inventory inventory = event.getInventory(); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { + Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) + || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { return; } if (!(inventory.getHolder() instanceof CraftEngineGUIHolder craftEngineGUIHolder)) { @@ -116,7 +118,9 @@ public class BukkitGuiManager implements GuiManager, Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) public void onInventoryDrag(InventoryDragEvent event) { org.bukkit.inventory.Inventory inventory = event.getInventory(); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { + Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) + || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { return; } if (!(inventory.getHolder() instanceof CraftEngineGUIHolder)) { @@ -134,7 +138,9 @@ public class BukkitGuiManager implements GuiManager, Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) public void onInventoryClose(InventoryCloseEvent event) { org.bukkit.inventory.Inventory inventory = event.getInventory(); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { + Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) + || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { return; } if (!(inventory.getHolder() instanceof BlockEntityHolder holder)) { @@ -149,7 +155,9 @@ public class BukkitGuiManager implements GuiManager, Listener { public void onInventoryClose(PlayerQuitEvent event) { Player player = event.getPlayer(); org.bukkit.inventory.Inventory inventory = player.getInventory(); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory))) { + Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) + || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { return; } if (!(inventory.getHolder() instanceof BlockEntityHolder holder)) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index c7367b2cf..d203ce92c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -497,7 +497,9 @@ public class BukkitServerPlayer extends Player { private void updateGUI() { org.bukkit.inventory.Inventory top = !VersionHelper.isOrAbove1_21() ? LegacyInventoryUtils.getTopInventory(platformPlayer()) : platformPlayer().getOpenInventory().getTopInventory(); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(FastNMS.INSTANCE.method$CraftInventory$getInventory(top))) { + Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(top); + if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) + || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { return; } if (top.getHolder() instanceof CraftEngineGUIHolder holder) { diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index 5f008583e..19cc9dcd2 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -35,6 +35,9 @@ items: sounds: open: minecraft:block.iron_trapdoor.open close: minecraft:block.iron_trapdoor.close + hopper-behavior: + can-place-item: true + can-take-item: false states: properties: facing: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 5536e4322..4f20228e3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -105,7 +105,7 @@ public abstract class BlockBehavior { return 0; } - // BlockState state, LevelReader world, BlockPos pos + // BlockState state, LevelAccessor level, BlockPos pos public Object getContainer(Object thisBlock, Object[] args) throws Exception { return null; } diff --git a/gradle.properties b/gradle.properties index 79f5433c6..025ef70a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.85 +nms_helper_version=1.0.86 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 6151c0712a6c335295ea2ec9a7825bbefbada8ca Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 13 Sep 2025 21:00:19 +0800 Subject: [PATCH 108/226] =?UTF-8?q?fix(block):=20=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/SimpleStorageBlockBehavior.java | 4 +-- .../bukkit/plugin/gui/BukkitGuiManager.java | 25 ++++--------------- .../plugin/user/BukkitServerPlayer.java | 6 +---- .../bukkit/util/InventoryUtils.java | 11 ++++++++ .../configuration/blocks/safe_block.yml | 4 +-- 5 files changed, 21 insertions(+), 29 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java index 9da43059e..49e61329d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -208,8 +208,8 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E boolean canPlaceItem = true; boolean canTakeItem = true; if (hopperBehavior != null) { - canPlaceItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("can-place-item", true), "can-place-item"); - canTakeItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("can-take-item", true), "can-take-item"); + canPlaceItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("allow-input", true), "allow-input"); + canTakeItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("allow-output", true), "allow-output"); } Property property = (Property) block.getProperty("open"); return new SimpleStorageBlockBehavior(block, title, rows, openSound, closeSound, hasAnalogOutputSignal, canPlaceItem, canTakeItem, property); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index eb1fcd7ec..331cf7a71 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitRef import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.bukkit.util.InventoryUtils; import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.gui.*; @@ -98,11 +99,7 @@ public class BukkitGuiManager implements GuiManager, Listener { @EventHandler(ignoreCancelled = true) public void onInventoryClick(InventoryClickEvent event) { org.bukkit.inventory.Inventory inventory = event.getInventory(); - Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) - || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { - return; - } + if (!InventoryUtils.isCustomContainer(inventory)) return; if (!(inventory.getHolder() instanceof CraftEngineGUIHolder craftEngineGUIHolder)) { return; } @@ -118,11 +115,7 @@ public class BukkitGuiManager implements GuiManager, Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) public void onInventoryDrag(InventoryDragEvent event) { org.bukkit.inventory.Inventory inventory = event.getInventory(); - Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) - || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { - return; - } + if (!InventoryUtils.isCustomContainer(inventory)) return; if (!(inventory.getHolder() instanceof CraftEngineGUIHolder)) { return; } @@ -138,11 +131,7 @@ public class BukkitGuiManager implements GuiManager, Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) public void onInventoryClose(InventoryCloseEvent event) { org.bukkit.inventory.Inventory inventory = event.getInventory(); - Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) - || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { - return; - } + if (!InventoryUtils.isCustomContainer(inventory)) return; if (!(inventory.getHolder() instanceof BlockEntityHolder holder)) { return; } @@ -155,11 +144,7 @@ public class BukkitGuiManager implements GuiManager, Listener { public void onInventoryClose(PlayerQuitEvent event) { Player player = event.getPlayer(); org.bukkit.inventory.Inventory inventory = player.getInventory(); - Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) - || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { - return; - } + if (!InventoryUtils.isCustomContainer(inventory)) return; if (!(inventory.getHolder() instanceof BlockEntityHolder holder)) { return; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index d203ce92c..55bcea67a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -497,11 +497,7 @@ public class BukkitServerPlayer extends Player { private void updateGUI() { org.bukkit.inventory.Inventory top = !VersionHelper.isOrAbove1_21() ? LegacyInventoryUtils.getTopInventory(platformPlayer()) : platformPlayer().getOpenInventory().getTopInventory(); - Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(top); - if (!CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) - || !container.getClass().getSimpleName().equals("CraftEngineWorldlyContainer")) { - return; - } + if (!InventoryUtils.isCustomContainer(top)) return; if (top.getHolder() instanceof CraftEngineGUIHolder holder) { holder.gui().onTimer(); } else if (top.getHolder() instanceof BlockEntityHolder holder) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java index 089f0b315..e92f1636c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java @@ -1,8 +1,12 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.nms.StorageContainer; +import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -49,4 +53,11 @@ public final class InventoryUtils { } return -1; } + + public static boolean isCustomContainer(Inventory inventory) { + if (inventory == null) return false; + Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); + if (container == null) return false; + return CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) || StorageContainer.isInstance(container); + } } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index 19cc9dcd2..e312d27cc 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -36,8 +36,8 @@ items: open: minecraft:block.iron_trapdoor.open close: minecraft:block.iron_trapdoor.close hopper-behavior: - can-place-item: true - can-take-item: false + allow-input: true + allow-output: false states: properties: facing: From f98dc4821b557a709014352479098b406f2dd0bc Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 13 Sep 2025 21:41:38 +0800 Subject: [PATCH 109/226] =?UTF-8?q?fix(block):=20=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/momirealms/craftengine/bukkit/util/InventoryUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java index e92f1636c..c5df6f6f7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java @@ -58,6 +58,6 @@ public final class InventoryUtils { if (inventory == null) return false; Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); if (container == null) return false; - return CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) || StorageContainer.isInstance(container); + return CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) || container instanceof StorageContainer; } } From 3b3baf0ac782ce2934fc1bccd675b0ca5290671a Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 13 Sep 2025 23:01:09 +0800 Subject: [PATCH 110/226] =?UTF-8?q?feat(block):=20=E5=AE=9E=E7=8E=B0=20wal?= =?UTF-8?q?l=5Fattached=5Fblock=20=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../behavior/WallAttachedBlockBehavior.java | 83 +++++++++++++++++++ .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + .../core/item/context/BlockPlaceContext.java | 19 +++++ gradle.properties | 2 +- 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 34435ad14..abec0ded2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -32,6 +32,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); + public static final Key WALL_ATTACHED_BLOCK = Key.from("craftengine:wall_attached_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -62,5 +63,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); + register(WALL_ATTACHED_BLOCK, WallAttachedBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java new file mode 100644 index 000000000..2abf17eaf --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java @@ -0,0 +1,83 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class WallAttachedBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property facingProperty; + + public WallAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty) { + super(customBlock); + this.facingProperty = facingProperty; + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (state == null) return args[0]; + WallAttachedBlockBehavior behavior = state.behavior().getAs(WallAttachedBlockBehavior.class).orElse(null); + if (behavior == null) return state; + HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite().toHorizontalDirection(); + return direction == state.get(behavior.facingProperty) && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos]) ? MBlocks.AIR$defaultState : args[0]; + } + + @Override + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (state == null) return false; + WallAttachedBlockBehavior behavior = state.behavior().getAs(WallAttachedBlockBehavior.class).orElse(null); + if (behavior == null) return false; + HorizontalDirection direction = state.get(behavior.facingProperty); + BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction.opposite().toDirection()); + Object nmsPos = LocationUtils.toBlockPos(blockPos); + Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos); + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction.opposite().toDirection()), CoreReflections.instance$SupportType$FULL); + } + + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + WallAttachedBlockBehavior behavior = state.behavior().getAs(WallAttachedBlockBehavior.class).orElse(null); + if (behavior == null) return null; + World level = context.getLevel(); + BlockPos clickedPos = context.getClickedPos(); + Direction[] nearestLookingDirections = context.getNearestLookingDirections(); + for (Direction direction : nearestLookingDirections) { + if (direction.axis().isHorizontal()) { + state = state.with(behavior.facingProperty, direction.opposite().toHorizontalDirection()); + if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), level.serverWorld(), LocationUtils.toBlockPos(clickedPos))) { + return state; + } + } + } + return null; + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.wall_attached.missing_facing"); + return new WallAttachedBlockBehavior(block, facing); + } + } +} diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index bde6e388d..6a03f9f8e 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -316,6 +316,7 @@ warning.config.block.behavior.pressure_plate.missing_powered: "Issue fou warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' property for 'change_over_time_block' behavior." +warning.config.block.behavior.wall_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'wall_attached_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index b1f2fb30d..6ccce87d5 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -311,6 +311,7 @@ warning.config.block.behavior.pressure_plate.missing_powered: "在文件 warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.change_over_time.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 配置项" +warning.config.block.behavior.wall_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'wall_attached_block' 行为缺少必需的 'facing' 属性" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/context/BlockPlaceContext.java b/core/src/main/java/net/momirealms/craftengine/core/item/context/BlockPlaceContext.java index cdc2ddd82..2cb96abb7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/context/BlockPlaceContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/context/BlockPlaceContext.java @@ -48,4 +48,23 @@ public class BlockPlaceContext extends UseOnContext { public Direction getNearestLookingDirection() { return Direction.orderedByNearest(this.getPlayer())[0]; } + + public Direction[] getNearestLookingDirections() { + Direction[] directions = Direction.orderedByNearest(this.getPlayer()); + if (!this.replaceClicked) { + Direction clickedFace = this.getClickedFace(); + int i = 0; + + while (i < directions.length && directions[i] != clickedFace.opposite()) { + i++; + } + + if (i > 0) { + System.arraycopy(directions, 0, directions, 1, i); + directions[0] = clickedFace.opposite(); + } + + } + return directions; + } } diff --git a/gradle.properties b/gradle.properties index 025ef70a7..6cacafa74 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Rule: [major update].[feature update].[bug fix] project_version=0.0.62.19 config_version=45 -lang_version=27 +lang_version=28 project_group=net.momirealms latest_supported_version=1.21.8 From 1ca16091bb30b77c93aa6621235bf37313354bca Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 00:38:31 +0800 Subject: [PATCH 111/226] =?UTF-8?q?=E5=8A=A8=E6=80=81=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BetterModelBlockEntityElement.java | 4 +- .../ModelEngineBlockEntityElement.java | 4 +- .../BukkitConstantBlockEntityRenderer.java | 84 ------------- .../ItemDisplayBlockEntityElement.java | 4 +- .../TextDisplayBlockEntityElement.java | 4 +- .../bukkit/plugin/BukkitCraftEngine.java | 3 - .../plugin/injector/WorldStorageInjector.java | 7 +- .../bukkit/world/BukkitCEWorld.java | 12 +- .../craftengine/bukkit/world/BukkitWorld.java | 20 +++ .../core/block/entity/BlockEntity.java | 17 +-- .../render/ConstantBlockEntityRenderer.java | 34 +++-- .../render/DynamicBlockEntityRenderer.java | 14 +++ .../render/element/BlockEntityElement.java | 4 +- .../craftengine/core/world/CEWorld.java | 6 +- .../craftengine/core/world/World.java | 4 + .../craftengine/core/world/chunk/CEChunk.java | 116 ++++++++++++++---- .../serialization/DefaultChunkSerializer.java | 2 +- 17 files changed, 183 insertions(+), 156 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/entity/render/DynamicBlockEntityRenderer.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java index bf8e180ba..1c9a0e5d3 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElement.java @@ -32,14 +32,14 @@ public class BetterModelBlockEntityElement implements BlockEntityElement { } @Override - public void despawn(Player player) { + public void hide(Player player) { if (this.dummyTracker != null) { this.dummyTracker.remove((org.bukkit.entity.Player) player.platformPlayer()); } } @Override - public void spawn(Player player) { + public void show(Player player) { if (this.dummyTracker != null) { this.dummyTracker.spawn((org.bukkit.entity.Player) player.platformPlayer()); } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java index 5e3660563..0b49726b7 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElement.java @@ -39,14 +39,14 @@ public class ModelEngineBlockEntityElement implements BlockEntityElement { } @Override - public void despawn(Player player) { + public void hide(Player player) { if (this.dummy != null) { this.dummy.setForceViewing((org.bukkit.entity.Player) player.platformPlayer(), true); } } @Override - public void spawn(Player player) { + public void show(Player player) { if (this.dummy != null) { this.dummy.setForceHidden((org.bukkit.entity.Player) player.platformPlayer(), true); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java deleted file mode 100644 index 9ca1bc4f1..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/BukkitConstantBlockEntityRenderer.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.entity.renderer; - -import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; -import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.world.ChunkPos; -import net.momirealms.craftengine.core.world.World; - -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.Objects; - -public class BukkitConstantBlockEntityRenderer extends ConstantBlockEntityRenderer { - private final BlockEntityElement[] elements; - private final World world; - private final ChunkPos chunkPos; - - public BukkitConstantBlockEntityRenderer(World world, ChunkPos pos, BlockEntityElement[] elements) { - this.world = world; - this.chunkPos = pos; - this.elements = elements; - } - - private Object getChunkHolder() { - Object serverLevel = this.world.serverWorld(); - Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); - return FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, this.chunkPos.longKey); - } - - @Override - public void despawn() { - List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.getChunkHolder()); - if (players.isEmpty()) return; - for (Object player : players) { - org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); - BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(bkPlayer); - if (serverPlayer == null) continue; - despawn(serverPlayer); - } - } - - @Override - public void spawn() { - List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(this.getChunkHolder()); - if (players.isEmpty()) return; - for (Object player : players) { - org.bukkit.entity.Player bkPlayer = FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player); - BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(bkPlayer); - if (serverPlayer == null) continue; - spawn(serverPlayer); - } - } - - @Override - public void spawn(Player player) { - for (BlockEntityElement element : this.elements) { - element.spawn(player); - } - } - - @Override - public void despawn(Player player) { - for (BlockEntityElement element : this.elements) { - element.despawn(player); - } - } - - @Override - public void deactivate() { - for (BlockEntityElement element : this.elements) { - element.deactivate(); - } - } - - @Override - public void activate() { - for (BlockEntityElement element : this.elements) { - element.activate(); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java index ae5b42482..074bc87c5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElement.java @@ -31,12 +31,12 @@ public class ItemDisplayBlockEntityElement implements BlockEntityElement { } @Override - public void despawn(Player player) { + public void hide(Player player) { player.sendPacket(this.cachedDespawnPacket, false); } @Override - public void spawn(Player player) { + public void show(Player player) { player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), true); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java index d3a8e8485..45a1c6c9d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElement.java @@ -31,12 +31,12 @@ public class TextDisplayBlockEntityElement implements BlockEntityElement { } @Override - public void despawn(Player player) { + public void hide(Player player) { player.sendPacket(this.cachedDespawnPacket, false); } @Override - public void spawn(Player player) { + public void show(Player player) { player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), true); } } 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 3f98c3a30..6633b1fa3 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 @@ -40,14 +40,11 @@ import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImp import net.momirealms.craftengine.core.plugin.locale.TranslationManagerImpl; import net.momirealms.craftengine.core.plugin.logger.JavaPluginLogger; import net.momirealms.craftengine.core.plugin.logger.PluginLogger; -import net.momirealms.craftengine.core.plugin.logger.filter.DisconnectLogFilter; -import net.momirealms.craftengine.core.plugin.logger.filter.LogFilter; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; -import org.apache.logging.log4j.LogManager; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.World; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index e4d1bb197..6a79e1032 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -243,7 +243,7 @@ public final class WorldStorageInjector { } if (previousImmutableBlockState.hasConstantBlockEntityRenderer()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); - chunk.removeBlockEntityRenderer(pos); + chunk.removeConstantBlockEntityRenderer(pos); } } if (newImmutableBlockState.hasBlockEntity()) { @@ -262,11 +262,12 @@ public final class WorldStorageInjector { blockEntity.setBlockState(newImmutableBlockState); // 方块类型未变,仅更新状态,选择性更新ticker chunk.replaceOrCreateTickingBlockEntity(blockEntity); + chunk.createDynamicBlockEntityRenderer(blockEntity); } } if (newImmutableBlockState.hasConstantBlockEntityRenderer()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); - chunk.addBlockEntityRenderer(pos, newImmutableBlockState); + chunk.addConstantBlockEntityRenderer(pos, newImmutableBlockState); } // 如果新方块的光照属性和客户端认为的不同 if (Config.enableLightSystem()) { @@ -296,7 +297,7 @@ public final class WorldStorageInjector { } if (previous.hasConstantBlockEntityRenderer()) { BlockPos pos = new BlockPos(chunk.chunkPos.x * 16 + x, section.sectionY * 16 + y, chunk.chunkPos.z * 16 + z); - chunk.removeBlockEntityRenderer(pos); + chunk.removeConstantBlockEntityRenderer(pos); } if (Config.enableLightSystem()) { // 自定义块到原版块,只需要判断旧块是否和客户端一直 diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 6e21a56f5..012529a87 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -1,18 +1,17 @@ package net.momirealms.craftengine.bukkit.world; -import net.momirealms.craftengine.bukkit.block.entity.renderer.BukkitConstantBlockEntityRenderer; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.LightUtils; -import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; -import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class BukkitCEWorld extends CEWorld { @@ -43,9 +42,4 @@ public class BukkitCEWorld extends CEWorld { super.lightSections.addAll(pendingLightSections); } } - - @Override - public ConstantBlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, World world, BlockPos pos) { - return new BukkitConstantBlockEntityRenderer(world, new ChunkPos(pos), elements); - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index fc162525c..2624bc78d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -1,8 +1,10 @@ package net.momirealms.craftengine.bukkit.world; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.sound.SoundSource; @@ -21,6 +23,9 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.UUID; public class BukkitWorld implements World { @@ -129,4 +134,19 @@ public class BukkitWorld implements World { public CEWorld storageWorld() { return BukkitWorldManager.instance().getWorld(uuid()); } + + @Override + public List getTrackedBy(ChunkPos pos) { + Object serverLevel = serverWorld(); + Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(serverLevel); + Object chunkHolder = FastNMS.INSTANCE.method$ServerChunkCache$getVisibleChunkIfPresent(chunkSource, pos.longKey); + if (chunkHolder == null) return Collections.emptyList(); + List players = FastNMS.INSTANCE.method$ChunkHolder$getPlayers(chunkHolder); + if (players.isEmpty()) return Collections.emptyList(); + List tracked = new ArrayList<>(players.size()); + for (Object player : players) { + tracked.add(BukkitAdaptors.adapt(FastNMS.INSTANCE.method$ServerPlayer$getBukkitEntity(player))); + } + return tracked; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java index 73f715209..fd582ffe5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.block.entity; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.DynamicBlockEntityRenderer; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.ChunkPos; @@ -15,6 +15,8 @@ public abstract class BlockEntity { protected BlockEntityType type; protected CEWorld world; protected boolean valid; + @Nullable + protected DynamicBlockEntityRenderer blockEntityRenderer; protected BlockEntity(BlockEntityType type, BlockPos pos, ImmutableBlockState blockState) { this.pos = pos; @@ -78,7 +80,11 @@ public abstract class BlockEntity { } public BlockEntityType type() { - return type; + return this.type; + } + + public @Nullable DynamicBlockEntityRenderer blockEntityRenderer() { + return blockEntityRenderer; } public static BlockPos readPosAndVerify(CompoundTag tag, ChunkPos chunkPos) { @@ -95,12 +101,7 @@ public abstract class BlockEntity { } public BlockPos pos() { - return pos; - } - - @Nullable - public ConstantBlockEntityRenderer[] getBlockEntityRenderers() { - return null; + return this.pos; } public boolean isValidBlockState(ImmutableBlockState blockState) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index b6e45acf8..f28ab579f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -1,20 +1,38 @@ package net.momirealms.craftengine.core.block.entity.render; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental -public abstract class ConstantBlockEntityRenderer { +public class ConstantBlockEntityRenderer { + private final BlockEntityElement[] elements; - public abstract void spawn(); + public ConstantBlockEntityRenderer(BlockEntityElement[] elements) { + this.elements = elements; + } - public abstract void despawn(); + public void show(Player player) { + for (BlockEntityElement element : this.elements) { + element.show(player); + } + } - public abstract void spawn(Player player); + public void hide(Player player) { + for (BlockEntityElement element : this.elements) { + element.hide(player); + } + } - public abstract void despawn(Player player); + public void deactivate() { + for (BlockEntityElement element : this.elements) { + element.deactivate(); + } + } - public abstract void deactivate(); - - public abstract void activate(); + public void activate() { + for (BlockEntityElement element : this.elements) { + element.activate(); + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/DynamicBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/DynamicBlockEntityRenderer.java new file mode 100644 index 000000000..bd7fa3c88 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/DynamicBlockEntityRenderer.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.core.block.entity.render; + +import net.momirealms.craftengine.core.entity.player.Player; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface DynamicBlockEntityRenderer { + + void show(Player player); + + void hide(Player player); + + void update(Player player); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java index d098200bd..eaf5d605c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElement.java @@ -6,9 +6,9 @@ import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental public interface BlockEntityElement { - void spawn(Player player); + void show(Player player); - void despawn(Player player); + void hide(Player player); default void deactivate() {} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 21ec02ff0..38b3985ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -4,8 +4,6 @@ import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTabl import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; -import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -21,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap; public abstract class CEWorld { public static final String REGION_DIRECTORY = "craftengine"; - protected final World world; + public final World world; protected final ConcurrentLong2ReferenceChainedHashTable loadedChunkMap; protected final WorldDataStorage worldDataStorage; protected final WorldHeight worldHeightAccessor; @@ -219,6 +217,4 @@ public abstract class CEWorld { } this.isTickingBlockEntities = false; } - - public abstract ConstantBlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, World world, BlockPos pos); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/World.java b/core/src/main/java/net/momirealms/craftengine/core/world/World.java index 287429f5a..bc6181c8b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/World.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/World.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.sound.SoundData; @@ -12,6 +13,7 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.nio.file.Path; +import java.util.List; import java.util.UUID; public interface World { @@ -59,4 +61,6 @@ public interface World { void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context); long time(); + + List getTrackedBy(ChunkPos pos); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 55706f1b4..442ac8999 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; +import net.momirealms.craftengine.core.block.entity.render.DynamicBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.tick.*; @@ -25,12 +26,13 @@ public class CEChunk { public final ChunkPos chunkPos; public final CESection[] sections; public final WorldHeight worldHeightAccessor; - public final Map blockEntities; - public final Map blockEntityRenderers; + public final Map blockEntities; // 从区域线程上访问,安全 + public final Map tickingBlockEntitiesByPos; // 从区域线程上访问,安全 + public final Map constantBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 + public final Map dynamicBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 private final ReentrantReadWriteLock renderLock = new ReentrantReadWriteLock(); private volatile boolean dirty; private volatile boolean loaded; - protected final Map tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; @@ -38,7 +40,9 @@ public class CEChunk { this.worldHeightAccessor = world.worldHeight(); this.sections = new CESection[this.worldHeightAccessor.getSectionsCount()]; this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); this.fillEmptySection(); } @@ -46,6 +50,8 @@ public class CEChunk { this.world = world; this.chunkPos = chunkPos; this.worldHeightAccessor = world.worldHeight(); + this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); int sectionCount = this.worldHeightAccessor.getSectionsCount(); this.sections = new CESection[sectionCount]; if (sections != null) { @@ -67,21 +73,24 @@ public class CEChunk { this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); } if (itemDisplayBlockRenders != null) { - this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(itemDisplayBlockRenders.size(), 10), 0.5f); + this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(itemDisplayBlockRenders.size(), 10), 0.5f); List blockEntityRendererPoses = DefaultBlockEntityRendererSerializer.deserialize(this.chunkPos, itemDisplayBlockRenders); for (BlockPos pos : blockEntityRendererPoses) { - this.addBlockEntityRenderer(pos); + this.addConstantBlockEntityRenderer(pos); } } else { - this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); } } public void spawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) { - renderer.spawn(player); + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.show(player); + } + for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { + renderer.show(player); } } finally { this.renderLock.readLock().unlock(); @@ -91,19 +100,22 @@ public class CEChunk { public void despawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) { - renderer.despawn(player); + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.hide(player); + } + for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { + renderer.hide(player); } } finally { this.renderLock.readLock().unlock(); } } - public void addBlockEntityRenderer(BlockPos pos) { - this.addBlockEntityRenderer(pos, this.getBlockState(pos)); + public void addConstantBlockEntityRenderer(BlockPos pos) { + this.addConstantBlockEntityRenderer(pos, this.getBlockState(pos)); } - public void addBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) { + public void addConstantBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) { BlockEntityElementConfig[] renderers = state.constantRenderers(); if (renderers != null && renderers.length > 0) { BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; @@ -111,23 +123,41 @@ public class CEChunk { for (int i = 0; i < elements.length; i++) { elements[i] = renderers[i].create(wrappedWorld, pos); } - ConstantBlockEntityRenderer renderer = this.world.createBlockEntityRenderer(elements, wrappedWorld, pos); - renderer.spawn(); + ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements); + for (Player player : getTrackedBy()) { + renderer.show(player); + } try { this.renderLock.writeLock().lock(); - this.blockEntityRenderers.put(pos, renderer); + this.constantBlockEntityRenderers.put(pos, renderer); } finally { this.renderLock.writeLock().unlock(); } } } - public void removeBlockEntityRenderer(BlockPos pos) { + public void removeConstantBlockEntityRenderer(BlockPos pos) { try { this.renderLock.writeLock().lock(); - ConstantBlockEntityRenderer removed = this.blockEntityRenderers.remove(pos); + ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos); if (removed != null) { - removed.despawn(); + for (Player player : getTrackedBy()) { + removed.hide(player); + } + } + } finally { + this.renderLock.writeLock().unlock(); + } + } + + private void removeDynamicBlockEntityRenderer(BlockPos pos) { + try { + this.renderLock.writeLock().lock(); + DynamicBlockEntityRenderer renderer = this.dynamicBlockEntityRenderers.remove(pos); + if (renderer != null) { + for (Player player : getTrackedBy()) { + renderer.hide(player); + } } } finally { this.renderLock.writeLock().unlock(); @@ -137,6 +167,7 @@ public class CEChunk { public void addBlockEntity(BlockEntity blockEntity) { this.setBlockEntity(blockEntity); this.replaceOrCreateTickingBlockEntity(blockEntity); + this.createDynamicBlockEntityRenderer(blockEntity); } public void removeBlockEntity(BlockPos blockPos) { @@ -145,21 +176,28 @@ public class CEChunk { removedBlockEntity.setValid(false); } this.removeBlockEntityTicker(blockPos); + this.removeDynamicBlockEntityRenderer(blockPos); } public void activateAllBlockEntities() { for (BlockEntity blockEntity : this.blockEntities.values()) { blockEntity.setValid(true); - replaceOrCreateTickingBlockEntity(blockEntity); + this.replaceOrCreateTickingBlockEntity(blockEntity); + this.createDynamicBlockEntityRenderer(blockEntity); } - for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { renderer.activate(); } } + public List getTrackedBy() { + return this.world.world.getTrackedBy(this.chunkPos); + } + public void deactivateAllBlockEntities() { this.blockEntities.values().forEach(e -> e.setValid(false)); - this.blockEntityRenderers.values().forEach(ConstantBlockEntityRenderer::deactivate); + this.constantBlockEntityRenderers.values().forEach(ConstantBlockEntityRenderer::deactivate); + this.dynamicBlockEntityRenderers.clear(); this.tickingBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); this.tickingBlockEntitiesByPos.clear(); } @@ -184,6 +222,34 @@ public class CEChunk { } } + public void createDynamicBlockEntityRenderer(T blockEntity) { + DynamicBlockEntityRenderer renderer = blockEntity.blockEntityRenderer(); + if (renderer != null) { + DynamicBlockEntityRenderer previous; + try { + this.renderLock.writeLock().lock(); + previous = this.dynamicBlockEntityRenderers.put(blockEntity.pos(), renderer); + } finally { + this.renderLock.writeLock().unlock(); + } + if (previous != null) { + if (previous == renderer) { + return; + } + for (Player player : getTrackedBy()) { + previous.hide(player); + renderer.show(player); + } + } else { + for (Player player : getTrackedBy()) { + renderer.show(player); + } + } + } else { + this.removeDynamicBlockEntityRenderer(blockEntity.pos()); + } + } + private void removeBlockEntityTicker(BlockPos pos) { ReplaceableTickingBlockEntity blockEntity = this.tickingBlockEntitiesByPos.remove(pos); if (blockEntity != null) { @@ -239,10 +305,10 @@ public class CEChunk { return Collections.unmodifiableCollection(this.blockEntities.values()); } - public List blockEntityRenderers() { + public List constantBlockEntityRenderers() { try { this.renderLock.readLock().lock(); - return new ArrayList<>(this.blockEntityRenderers.keySet()); + return new ArrayList<>(this.constantBlockEntityRenderers.keySet()); } finally { this.renderLock.readLock().unlock(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index ff195c4d3..5c4395082 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -30,7 +30,7 @@ public final class DefaultChunkSerializer { if (!blockEntities.isEmpty()) { chunkNbt.put("block_entities", blockEntities); } - ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.blockEntityRenderers()); + ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers()); if (!blockEntityRenders.isEmpty()) { chunkNbt.put("block_entity_renderers", blockEntityRenders); } From d0a91744aefbc68b7f6fc7d3fa66f2a1e1599e96 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 00:39:00 +0800 Subject: [PATCH 112/226] Update BukkitCEWorld.java --- .../craftengine/bukkit/world/BukkitCEWorld.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java index 012529a87..988f8e787 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitCEWorld.java @@ -1,17 +1,15 @@ package net.momirealms.craftengine.bukkit.world; -import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; -import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.LightUtils; -import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.SectionPosUtils; -import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.SectionPos; +import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class BukkitCEWorld extends CEWorld { From 08200e5f753b2a9643fa2a477f50b152c477e20b Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 00:48:22 +0800 Subject: [PATCH 113/226] =?UTF-8?q?feat(network):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=A5=E6=A0=BC=E6=A0=A1=E9=AA=8C=E7=94=A8=E6=88=B7=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E8=B5=84=E6=BA=90=E5=8C=85=E4=BD=BF=E7=94=A8=E7=9A=84?= =?UTF-8?q?UUID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/PacketConsumers.java | 25 +++++++++---- .../plugin/user/BukkitServerPlayer.java | 36 ++++++++++++++++--- common-files/src/main/resources/config.yml | 2 ++ .../src/main/resources/translations/en.yml | 3 +- .../src/main/resources/translations/zh_cn.yml | 3 +- .../core/plugin/config/Config.java | 5 +++ .../core/plugin/network/NetWorkUser.java | 12 +++++-- 7 files changed, 72 insertions(+), 14 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 87d00a5c0..7349c027d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -60,6 +60,7 @@ import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; @@ -1239,17 +1240,17 @@ public class PacketConsumers { try { BukkitServerPlayer player = (BukkitServerPlayer) user; String name = (String) NetworkReflections.methodHandle$ServerboundHelloPacket$nameGetter.invokeExact(packet); - player.setName(name); + player.setUnverifiedName(name); if (VersionHelper.isOrAbove1_20_2()) { UUID uuid = (UUID) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); - player.setUUID(uuid); + player.setUnverifiedUUID(uuid); } else { @SuppressWarnings("unchecked") Optional uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); if (uuid.isPresent()) { - player.setUUID(uuid.get()); + player.setUnverifiedUUID(uuid.get()); } else { - player.setUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); + player.setUnverifiedUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); } } } catch (Throwable e) { @@ -2499,6 +2500,18 @@ public class PacketConsumers { // 防止后续加入的JoinWorldTask再次处理 user.setShouldProcessFinishConfiguration(false); + // 检查用户UUID是否已经校验 + if (!user.isVerifiedUUID()) { + if (Config.strictPlayerUuidValidation()) { + TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); + user.kick(Component.translatable("disconnect.loginFailed")); + return; + } + if (Config.debugResourcePack()) { + TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); + } + } + // 取消 ClientboundFinishConfigurationPacket,让客户端发呆,并结束掉当前的进入世界任务 event.setCancelled(true); try { @@ -2548,8 +2561,8 @@ public class PacketConsumers { public static final TriConsumer LOGIN_FINISHED = (user, event, packet) -> { try { GameProfile gameProfile = FastNMS.INSTANCE.field$ClientboundLoginFinishedPacket$gameProfile(packet); - user.setName(gameProfile.getName()); - user.setUUID(gameProfile.getId()); + user.setVerifiedName(gameProfile.getName()); + user.setVerifiedUUID(gameProfile.getId()); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginFinishedPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index c7367b2cf..ddae03bbd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -72,6 +72,8 @@ public class BukkitServerPlayer extends Player { private ChannelHandler connection; private String name; private UUID uuid; + private boolean isVerifiedName; + private boolean isVerifiedUUID; private ConnectionState decoderState; private ConnectionState encoderState; private boolean shouldProcessFinishConfiguration = true; @@ -140,7 +142,9 @@ public class BukkitServerPlayer extends Player { this.playerRef = new WeakReference<>(player); this.serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); this.uuid = player.getUniqueId(); + this.isVerifiedUUID = true; this.name = player.getName(); + this.isVerifiedName = true; byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY); this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(768, 0.5f); this.entityTypeView = new ConcurrentHashMap<>(256); @@ -321,22 +325,46 @@ public class BukkitServerPlayer extends Player { } @Override - public void setName(String name) { - if (this.name != null) return; + public boolean isVerifiedName() { + return this.isVerifiedName; + } + + @Override + public void setUnverifiedName(String name) { + if (this.isVerifiedName) return; this.name = name; } + @Override + public void setVerifiedName(String name) { + if (this.isVerifiedName) return; + this.name = name; + this.isVerifiedName = true; + } + @Override public UUID uuid() { return this.uuid; } @Override - public void setUUID(UUID uuid) { - if (this.uuid != null) return; + public boolean isVerifiedUUID() { + return this.isVerifiedUUID; + } + + @Override + public void setUnverifiedUUID(UUID uuid) { + if (this.isVerifiedUUID) return; this.uuid = uuid; } + @Override + public void setVerifiedUUID(UUID uuid) { + if (this.isVerifiedUUID) return; + this.uuid = uuid; + this.isVerifiedUUID = true; + } + @Override public void playSound(Key sound, SoundSource source, float volume, float pitch) { platformPlayer().playSound(platformPlayer(), sound.toString(), SoundUtils.toBukkit(source), volume, pitch); diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 2c0c66aad..cb1226ba1 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -98,6 +98,8 @@ resource-pack: file-to-upload: "./generated/resource_pack.zip" # Resend the resource pack to players upon successful upload resend-on-upload: true + # Whether a verified player UUID is required to get the resource pack + strict-player-uuid-validation: true duplicated-files-handler: - term: type: any_of diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index bde6e388d..885cb3fbc 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -63,7 +63,8 @@ command.upload.failure.not_supported: "Current hosting method '' doe command.upload.on_progress: "Started uploading progress. Check the console for more information." command.send_resource_pack.success.single: "Sent resource pack to ." command.send_resource_pack.success.multiple: "Send resource packs to players." -warning.config.pack.duplicated_files: "Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section." +warning.network.resource_pack.unverified_uuid: "Player attempts to request a resource package using a UUID () that is not authenticated by the server." +warning.config.pack.duplicated_files: "Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section." warning.config.yaml.duplicated_key: "Issue found in file - Found duplicated key '' at line , this might cause unexpected results." warning.config.yaml.inconsistent_value_type: "Issue found in file - Found duplicated key '' at line with different value types, this might cause unexpected results." warning.config.type.int: "Issue found in file - Failed to load '': Cannot cast '' to integer type for option ''." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index b1f2fb30d..72fb2c989 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -63,7 +63,8 @@ command.upload.failure.not_supported: "当前托管模式 '' 不支 command.upload.on_progress: "已开始上传进程. 检查控制台以获取详细信息" command.send_resource_pack.success.single: "发送资源包给 " command.send_resource_pack.success.multiple: "发送资源包给 个玩家" -warning.config.pack.duplicated_files: "发现重复文件 请通过 config.yml 的 'resource-pack.duplicated-files-handler' 部分解决" +warning.network.resource_pack.unverified_uuid: "玩家 使用未经服务器验证的 UUID () 尝试请求获取资源包" +warning.config.pack.duplicated_files: "发现重复文件 请通过 config.yml 的 'resource-pack.duplicated-files-handler' 部分解决" warning.config.yaml.duplicated_key: "在文件 发现问题 - 在第行发现重复的键 '', 这可能会导致一些意料之外的问题" warning.config.yaml.inconsistent_value_type: "在文件 发现问题 - 在第行发现重复且值类型不同的键 '', 这可能会导致一些意料之外的问题" warning.config.type.int: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为整数类型 (选项 '')" diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 922cd4286..ffdc1cacf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -93,6 +93,7 @@ public class Config { protected boolean resource_pack$delivery$send_on_join; protected boolean resource_pack$delivery$resend_on_upload; protected boolean resource_pack$delivery$auto_upload; + protected boolean resource_pack$delivery$strict_player_uuid_validation; protected Path resource_pack$delivery$file_to_upload; protected Component resource_pack$send$prompt; @@ -271,6 +272,7 @@ public class Config { resource_pack$delivery$kick_if_declined = config.getBoolean("resource-pack.delivery.kick-if-declined", true); resource_pack$delivery$kick_if_failed_to_apply = config.getBoolean("resource-pack.delivery.kick-if-failed-to-apply", true); resource_pack$delivery$auto_upload = config.getBoolean("resource-pack.delivery.auto-upload", true); + resource_pack$delivery$strict_player_uuid_validation = config.getBoolean("resource-pack.delivery.strict-player-uuid-validation", true); resource_pack$delivery$file_to_upload = resolvePath(config.getString("resource-pack.delivery.file-to-upload", "./generated/resource_pack.zip")); resource_pack$send$prompt = AdventureHelper.miniMessage().deserialize(config.getString("resource-pack.delivery.prompt", "To fully experience our server, please accept our custom resource pack.")); resource_pack$protection$crash_tools$method_1 = config.getBoolean("resource-pack.protection.crash-tools.method-1", false); @@ -590,6 +592,9 @@ public class Config { public static boolean autoUpload() { return instance.resource_pack$delivery$auto_upload; } + public static boolean strictPlayerUuidValidation() { + return instance.resource_pack$delivery$strict_player_uuid_validation; + } public static Path fileToUpload() { return instance.resource_pack$delivery$file_to_upload; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index eaf72bf24..00d359c5f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -31,11 +31,19 @@ public interface NetWorkUser { String name(); - void setName(String name); + boolean isVerifiedName(); + + void setUnverifiedName(String name); + + void setVerifiedName(String name); UUID uuid(); - void setUUID(UUID uuid); + boolean isVerifiedUUID(); + + void setUnverifiedUUID(UUID uuid); + + void setVerifiedUUID(UUID uuid); void sendPacket(Object packet, boolean immediately); From 24938359f8059dfba8d79ab33109cbb14993b32e Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 01:23:26 +0800 Subject: [PATCH 114/226] =?UTF-8?q?refactor(user):=20=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E6=96=B9=E6=B3=95=E5=92=8C=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/PacketConsumers.java | 14 ++++---- .../plugin/user/BukkitServerPlayer.java | 36 +++++++++---------- .../core/plugin/network/NetWorkUser.java | 12 +++---- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 7349c027d..c0b9d54a6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1240,17 +1240,17 @@ public class PacketConsumers { try { BukkitServerPlayer player = (BukkitServerPlayer) user; String name = (String) NetworkReflections.methodHandle$ServerboundHelloPacket$nameGetter.invokeExact(packet); - player.setUnverifiedName(name); + player.setNameUnverified(name); if (VersionHelper.isOrAbove1_20_2()) { UUID uuid = (UUID) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); - player.setUnverifiedUUID(uuid); + player.setUUIDUnverified(uuid); } else { @SuppressWarnings("unchecked") Optional uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); if (uuid.isPresent()) { - player.setUnverifiedUUID(uuid.get()); + player.setUUIDUnverified(uuid.get()); } else { - player.setUnverifiedUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); + player.setUUIDUnverified(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); } } } catch (Throwable e) { @@ -2501,7 +2501,7 @@ public class PacketConsumers { user.setShouldProcessFinishConfiguration(false); // 检查用户UUID是否已经校验 - if (!user.isVerifiedUUID()) { + if (!user.isUUIDVerified()) { if (Config.strictPlayerUuidValidation()) { TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); user.kick(Component.translatable("disconnect.loginFailed")); @@ -2561,8 +2561,8 @@ public class PacketConsumers { public static final TriConsumer LOGIN_FINISHED = (user, event, packet) -> { try { GameProfile gameProfile = FastNMS.INSTANCE.field$ClientboundLoginFinishedPacket$gameProfile(packet); - user.setVerifiedName(gameProfile.getName()); - user.setVerifiedUUID(gameProfile.getId()); + user.setNameVerified(gameProfile.getName()); + user.setUUIDVerified(gameProfile.getId()); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginFinishedPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index ddae03bbd..195f65514 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -72,8 +72,8 @@ public class BukkitServerPlayer extends Player { private ChannelHandler connection; private String name; private UUID uuid; - private boolean isVerifiedName; - private boolean isVerifiedUUID; + private boolean isNameVerified; + private boolean isUUIDVerified; private ConnectionState decoderState; private ConnectionState encoderState; private boolean shouldProcessFinishConfiguration = true; @@ -142,9 +142,9 @@ public class BukkitServerPlayer extends Player { this.playerRef = new WeakReference<>(player); this.serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); this.uuid = player.getUniqueId(); - this.isVerifiedUUID = true; + this.isUUIDVerified = true; this.name = player.getName(); - this.isVerifiedName = true; + this.isNameVerified = true; byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY); this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(768, 0.5f); this.entityTypeView = new ConcurrentHashMap<>(256); @@ -325,21 +325,21 @@ public class BukkitServerPlayer extends Player { } @Override - public boolean isVerifiedName() { - return this.isVerifiedName; + public boolean isNameVerified() { + return this.isNameVerified; } @Override - public void setUnverifiedName(String name) { - if (this.isVerifiedName) return; + public void setNameUnverified(String name) { + if (this.isNameVerified) return; this.name = name; } @Override - public void setVerifiedName(String name) { - if (this.isVerifiedName) return; + public void setNameVerified(String name) { + if (this.isNameVerified) return; this.name = name; - this.isVerifiedName = true; + this.isNameVerified = true; } @Override @@ -348,21 +348,21 @@ public class BukkitServerPlayer extends Player { } @Override - public boolean isVerifiedUUID() { - return this.isVerifiedUUID; + public boolean isUUIDVerified() { + return this.isUUIDVerified; } @Override - public void setUnverifiedUUID(UUID uuid) { - if (this.isVerifiedUUID) return; + public void setUUIDUnverified(UUID uuid) { + if (this.isUUIDVerified) return; this.uuid = uuid; } @Override - public void setVerifiedUUID(UUID uuid) { - if (this.isVerifiedUUID) return; + public void setUUIDVerified(UUID uuid) { + if (this.isUUIDVerified) return; this.uuid = uuid; - this.isVerifiedUUID = true; + this.isUUIDVerified = true; } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 00d359c5f..051744d66 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -31,19 +31,19 @@ public interface NetWorkUser { String name(); - boolean isVerifiedName(); + boolean isNameVerified(); - void setUnverifiedName(String name); + void setNameUnverified(String name); - void setVerifiedName(String name); + void setNameVerified(String name); UUID uuid(); - boolean isVerifiedUUID(); + boolean isUUIDVerified(); - void setUnverifiedUUID(UUID uuid); + void setUUIDUnverified(UUID uuid); - void setVerifiedUUID(UUID uuid); + void setUUIDVerified(UUID uuid); void sendPacket(Object packet, boolean immediately); From d53246d5472598599f0ecb8e032fa42e5a8d6acf Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 01:33:54 +0800 Subject: [PATCH 115/226] =?UTF-8?q?refactor(user):=20=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E6=96=B9=E6=B3=95=E5=92=8C=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/network/PacketConsumers.java | 12 ++++++------ .../bukkit/plugin/user/BukkitServerPlayer.java | 8 ++++---- .../craftengine/core/plugin/network/NetWorkUser.java | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index c0b9d54a6..b8671f959 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1240,17 +1240,17 @@ public class PacketConsumers { try { BukkitServerPlayer player = (BukkitServerPlayer) user; String name = (String) NetworkReflections.methodHandle$ServerboundHelloPacket$nameGetter.invokeExact(packet); - player.setNameUnverified(name); + player.setUnverifiedName(name); if (VersionHelper.isOrAbove1_20_2()) { UUID uuid = (UUID) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); - player.setUUIDUnverified(uuid); + player.setUnverifiedUUID(uuid); } else { @SuppressWarnings("unchecked") Optional uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); if (uuid.isPresent()) { - player.setUUIDUnverified(uuid.get()); + player.setUnverifiedUUID(uuid.get()); } else { - player.setUUIDUnverified(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); + player.setUnverifiedUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); } } } catch (Throwable e) { @@ -2561,8 +2561,8 @@ public class PacketConsumers { public static final TriConsumer LOGIN_FINISHED = (user, event, packet) -> { try { GameProfile gameProfile = FastNMS.INSTANCE.field$ClientboundLoginFinishedPacket$gameProfile(packet); - user.setNameVerified(gameProfile.getName()); - user.setUUIDVerified(gameProfile.getId()); + user.setVerifiedName(gameProfile.getName()); + user.setVerifiedUUID(gameProfile.getId()); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginFinishedPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 195f65514..33f60a832 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -330,13 +330,13 @@ public class BukkitServerPlayer extends Player { } @Override - public void setNameUnverified(String name) { + public void setUnverifiedName(String name) { if (this.isNameVerified) return; this.name = name; } @Override - public void setNameVerified(String name) { + public void setVerifiedName(String name) { if (this.isNameVerified) return; this.name = name; this.isNameVerified = true; @@ -353,13 +353,13 @@ public class BukkitServerPlayer extends Player { } @Override - public void setUUIDUnverified(UUID uuid) { + public void setUnverifiedUUID(UUID uuid) { if (this.isUUIDVerified) return; this.uuid = uuid; } @Override - public void setUUIDVerified(UUID uuid) { + public void setVerifiedUUID(UUID uuid) { if (this.isUUIDVerified) return; this.uuid = uuid; this.isUUIDVerified = true; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 051744d66..baa955b1a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -33,17 +33,17 @@ public interface NetWorkUser { boolean isNameVerified(); - void setNameUnverified(String name); + void setUnverifiedName(String name); - void setNameVerified(String name); + void setVerifiedName(String name); UUID uuid(); boolean isUUIDVerified(); - void setUUIDUnverified(UUID uuid); + void setUnverifiedUUID(UUID uuid); - void setUUIDVerified(UUID uuid); + void setVerifiedUUID(UUID uuid); void sendPacket(Object packet, boolean immediately); From ed154361ebaddf7c11fae6661b8db71e338aae68 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 01:46:15 +0800 Subject: [PATCH 116/226] =?UTF-8?q?feat(block):=20=E5=85=81=E8=AE=B86/4?= =?UTF-8?q?=E5=90=91=E9=99=84=E7=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 4 +- ...java => SurfaceAttachedBlockBehavior.java} | 61 +++++++++++++------ .../bukkit/plugin/gui/BukkitGuiManager.java | 3 +- .../bukkit/util/InventoryUtils.java | 3 +- .../src/main/resources/translations/en.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 2 +- 6 files changed, 49 insertions(+), 26 deletions(-) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/{WallAttachedBlockBehavior.java => SurfaceAttachedBlockBehavior.java} (50%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index abec0ded2..a110edeef 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -32,7 +32,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); - public static final Key WALL_ATTACHED_BLOCK = Key.from("craftengine:wall_attached_block"); + public static final Key SURFACE_ATTACHED_BLOCK = Key.from("craftengine:surface_attached_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -63,6 +63,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); - register(WALL_ATTACHED_BLOCK, WallAttachedBlockBehavior.FACTORY); + register(SURFACE_ATTACHED_BLOCK, SurfaceAttachedBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SurfaceAttachedBlockBehavior.java similarity index 50% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SurfaceAttachedBlockBehavior.java index 2abf17eaf..c7c529617 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SurfaceAttachedBlockBehavior.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -21,48 +22,68 @@ import net.momirealms.craftengine.core.world.World; import java.util.Map; import java.util.concurrent.Callable; -public class WallAttachedBlockBehavior extends BukkitBlockBehavior { +public class SurfaceAttachedBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final Property facingProperty; + private final Property facingProperty; + private final boolean isDirection; - public WallAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty) { + public SurfaceAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty, boolean isDirection) { super(customBlock); this.facingProperty = facingProperty; + this.isDirection = isDirection; } @Override public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (state == null) return args[0]; - WallAttachedBlockBehavior behavior = state.behavior().getAs(WallAttachedBlockBehavior.class).orElse(null); + SurfaceAttachedBlockBehavior behavior = state.behavior().getAs(SurfaceAttachedBlockBehavior.class).orElse(null); if (behavior == null) return state; - HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite().toHorizontalDirection(); - return direction == state.get(behavior.facingProperty) && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos]) ? MBlocks.AIR$defaultState : args[0]; + boolean flag; + if (isDirection) { + Direction direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite(); + flag = direction == state.get(behavior.facingProperty); + } else { + HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite().toHorizontalDirection(); + flag = direction == state.get(behavior.facingProperty); + } + return flag && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos]) + ? MBlocks.AIR$defaultState : args[0]; } @Override public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (state == null) return false; - WallAttachedBlockBehavior behavior = state.behavior().getAs(WallAttachedBlockBehavior.class).orElse(null); + SurfaceAttachedBlockBehavior behavior = state.behavior().getAs(SurfaceAttachedBlockBehavior.class).orElse(null); if (behavior == null) return false; - HorizontalDirection direction = state.get(behavior.facingProperty); - BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction.opposite().toDirection()); + Direction direction; + if (isDirection) { + direction = ((Direction) state.get(behavior.facingProperty)).opposite(); + } else { + direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection(); + } + BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction); Object nmsPos = LocationUtils.toBlockPos(blockPos); Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos); - return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction.opposite().toDirection()), CoreReflections.instance$SupportType$FULL); + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL); } + @SuppressWarnings("unchecked") @Override public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { - WallAttachedBlockBehavior behavior = state.behavior().getAs(WallAttachedBlockBehavior.class).orElse(null); + SurfaceAttachedBlockBehavior behavior = state.behavior().getAs(SurfaceAttachedBlockBehavior.class).orElse(null); if (behavior == null) return null; World level = context.getLevel(); BlockPos clickedPos = context.getClickedPos(); - Direction[] nearestLookingDirections = context.getNearestLookingDirections(); - for (Direction direction : nearestLookingDirections) { - if (direction.axis().isHorizontal()) { - state = state.with(behavior.facingProperty, direction.opposite().toHorizontalDirection()); + for (Direction direction : context.getNearestLookingDirections()) { + if (isDirection) { + state = state.with((Property) behavior.facingProperty, direction.opposite()); + if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), level.serverWorld(), LocationUtils.toBlockPos(clickedPos))) { + return state; + } + } else if (direction.axis().isHorizontal()) { + state = state.with((Property) behavior.facingProperty, direction.opposite().toHorizontalDirection()); if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), level.serverWorld(), LocationUtils.toBlockPos(clickedPos))) { return state; } @@ -73,11 +94,15 @@ public class WallAttachedBlockBehavior extends BukkitBlockBehavior { public static class Factory implements BlockBehaviorFactory { - @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.wall_attached.missing_facing"); - return new WallAttachedBlockBehavior(block, facing); + Property facing = ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.surface_attached.missing_facing"); + boolean isHorizontalDirection = facing.valueClass() == HorizontalDirection.class; + boolean isDirection = facing.valueClass() == Direction.class; + if (!(isHorizontalDirection || isDirection)) { + throw new LocalizedResourceConfigException("warning.config.block.behavior.surface_attached.missing_facing"); + } + return new SurfaceAttachedBlockBehavior(block, facing, isDirection); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index 331cf7a71..c30af575b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -5,7 +5,6 @@ import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder; import net.momirealms.craftengine.bukkit.block.entity.SimpleStorageBlockEntity; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.util.ComponentUtils; @@ -91,7 +90,7 @@ public class BukkitGuiManager implements GuiManager, Listener { @Override public Inventory createInventory(Gui gui, int size) { CraftEngineGUIHolder holder = new CraftEngineGUIHolder(gui); - org.bukkit.inventory.Inventory inventory = Bukkit.createInventory(holder, size); + org.bukkit.inventory.Inventory inventory = FastNMS.INSTANCE.createCraftEngineWorldlyContainer(holder, size, false, false); holder.holder().bindValue(inventory); return new BukkitInventory(inventory); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java index c5df6f6f7..7b1fed616 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InventoryUtils.java @@ -2,7 +2,6 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.nms.StorageContainer; -import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryEvent; @@ -58,6 +57,6 @@ public final class InventoryUtils { if (inventory == null) return false; Object container = FastNMS.INSTANCE.method$CraftInventory$getInventory(inventory); if (container == null) return false; - return CraftBukkitReflections.clazz$MinecraftInventory.isInstance(container) || container instanceof StorageContainer; + return container instanceof StorageContainer; } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 6a03f9f8e..06c11317a 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -316,7 +316,7 @@ warning.config.block.behavior.pressure_plate.missing_powered: "Issue fou warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' property for 'change_over_time_block' behavior." -warning.config.block.behavior.wall_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'wall_attached_block' behavior." +warning.config.block.behavior.surface_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'surface_attached_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 6ccce87d5..7fa6de8ea 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -311,7 +311,7 @@ warning.config.block.behavior.pressure_plate.missing_powered: "在文件 warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.change_over_time.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 配置项" -warning.config.block.behavior.wall_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'wall_attached_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.surface_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'surface_attached_block' 行为缺少必需的 'facing' 属性" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" From acb2c14012091836c312dc2a4c0730c775e7103f Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 01:57:06 +0800 Subject: [PATCH 117/226] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20en.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/translations/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 885cb3fbc..62e3377c8 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -63,7 +63,7 @@ command.upload.failure.not_supported: "Current hosting method '' doe command.upload.on_progress: "Started uploading progress. Check the console for more information." command.send_resource_pack.success.single: "Sent resource pack to ." command.send_resource_pack.success.multiple: "Send resource packs to players." -warning.network.resource_pack.unverified_uuid: "Player attempts to request a resource package using a UUID () that is not authenticated by the server." +warning.network.resource_pack.unverified_uuid: "Player attempts to request a resource pack using a UUID () that is not authenticated by the server." warning.config.pack.duplicated_files: "Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section." warning.config.yaml.duplicated_key: "Issue found in file - Found duplicated key '' at line , this might cause unexpected results." warning.config.yaml.inconsistent_value_type: "Issue found in file - Found duplicated key '' at line with different value types, this might cause unexpected results." From 6468ddf64d832fb005d0d37fa5c159e68f99d26e Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 01:59:00 +0800 Subject: [PATCH 118/226] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20en.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/translations/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 62e3377c8..97dafb01d 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -63,7 +63,7 @@ command.upload.failure.not_supported: "Current hosting method '' doe command.upload.on_progress: "Started uploading progress. Check the console for more information." command.send_resource_pack.success.single: "Sent resource pack to ." command.send_resource_pack.success.multiple: "Send resource packs to players." -warning.network.resource_pack.unverified_uuid: "Player attempts to request a resource pack using a UUID () that is not authenticated by the server." +warning.network.resource_pack.unverified_uuid: "Player is attempting to request a resource pack using a UUID () that is not authenticated by the server." warning.config.pack.duplicated_files: "Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section." warning.config.yaml.duplicated_key: "Issue found in file - Found duplicated key '' at line , this might cause unexpected results." warning.config.yaml.inconsistent_value_type: "Issue found in file - Found duplicated key '' at line with different value types, this might cause unexpected results." From c273a758716d9ec856d5572a3ccb07fcd8ea546b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 02:39:21 +0800 Subject: [PATCH 119/226] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 4 +-- ... => DirectionalAttachedBlockBehavior.java} | 25 +++++++++---------- .../behavior/SimpleStorageBlockBehavior.java | 9 ++----- .../configuration/blocks/chessboard_block.yml | 4 +-- .../configuration/blocks/chinese_lantern.yml | 4 +-- .../configuration/blocks/copper_coil.yml | 2 +- .../blocks/ender_pearl_flower.yml | 2 +- .../configuration/blocks/fairy_flower.yml | 2 +- .../configuration/blocks/flame_cane.yml | 2 +- .../configuration/blocks/gunpowder_block.yml | 4 +-- .../configuration/blocks/netherite_anvil.yml | 2 +- .../configuration/blocks/palm_tree.yml | 4 +-- .../default/configuration/blocks/pebble.yml | 2 +- .../default/configuration/blocks/reed.yml | 2 +- .../configuration/blocks/safe_block.yml | 9 +++---- .../default/configuration/blocks/sofa.yml | 8 +++--- .../default/configuration/templates.yml | 22 ++++++++-------- 17 files changed, 50 insertions(+), 57 deletions(-) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/{SurfaceAttachedBlockBehavior.java => DirectionalAttachedBlockBehavior.java} (85%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index a110edeef..b6497732a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -32,7 +32,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); - public static final Key SURFACE_ATTACHED_BLOCK = Key.from("craftengine:surface_attached_block"); + public static final Key DIRECTIONAL_ATTACHED_BLOCK = Key.from("craftengine:directional_attached_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -63,6 +63,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); - register(SURFACE_ATTACHED_BLOCK, SurfaceAttachedBlockBehavior.FACTORY); + register(DIRECTIONAL_ATTACHED_BLOCK, DirectionalAttachedBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SurfaceAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java similarity index 85% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SurfaceAttachedBlockBehavior.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index c7c529617..228bab889 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SurfaceAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -22,43 +22,42 @@ import net.momirealms.craftengine.core.world.World; import java.util.Map; import java.util.concurrent.Callable; -public class SurfaceAttachedBlockBehavior extends BukkitBlockBehavior { +public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; - private final boolean isDirection; + private final boolean isSixDirection; - public SurfaceAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty, boolean isDirection) { + public DirectionalAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty, boolean isSixDirection) { super(customBlock); this.facingProperty = facingProperty; - this.isDirection = isDirection; + this.isSixDirection = isSixDirection; } @Override public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (state == null) return args[0]; - SurfaceAttachedBlockBehavior behavior = state.behavior().getAs(SurfaceAttachedBlockBehavior.class).orElse(null); + DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null); if (behavior == null) return state; boolean flag; - if (isDirection) { + if (isSixDirection) { Direction direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite(); flag = direction == state.get(behavior.facingProperty); } else { HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).opposite().toHorizontalDirection(); flag = direction == state.get(behavior.facingProperty); } - return flag && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos]) - ? MBlocks.AIR$defaultState : args[0]; + return flag && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos]) ? MBlocks.AIR$defaultState : args[0]; } @Override public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (state == null) return false; - SurfaceAttachedBlockBehavior behavior = state.behavior().getAs(SurfaceAttachedBlockBehavior.class).orElse(null); + DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null); if (behavior == null) return false; Direction direction; - if (isDirection) { + if (isSixDirection) { direction = ((Direction) state.get(behavior.facingProperty)).opposite(); } else { direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection(); @@ -72,12 +71,12 @@ public class SurfaceAttachedBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("unchecked") @Override public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { - SurfaceAttachedBlockBehavior behavior = state.behavior().getAs(SurfaceAttachedBlockBehavior.class).orElse(null); + DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null); if (behavior == null) return null; World level = context.getLevel(); BlockPos clickedPos = context.getClickedPos(); for (Direction direction : context.getNearestLookingDirections()) { - if (isDirection) { + if (isSixDirection) { state = state.with((Property) behavior.facingProperty, direction.opposite()); if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), level.serverWorld(), LocationUtils.toBlockPos(clickedPos))) { return state; @@ -102,7 +101,7 @@ public class SurfaceAttachedBlockBehavior extends BukkitBlockBehavior { if (!(isHorizontalDirection || isDirection)) { throw new LocalizedResourceConfigException("warning.config.block.behavior.surface_attached.missing_facing"); } - return new SurfaceAttachedBlockBehavior(block, facing, isDirection); + return new DirectionalAttachedBlockBehavior(block, facing, isDirection); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java index 49e61329d..9ec94e21b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -204,13 +204,8 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E openSound = Optional.ofNullable(sounds.get("open")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); closeSound = Optional.ofNullable(sounds.get("close")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); } - Map hopperBehavior = (Map) arguments.get("hopper-behavior"); - boolean canPlaceItem = true; - boolean canTakeItem = true; - if (hopperBehavior != null) { - canPlaceItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("allow-input", true), "allow-input"); - canTakeItem = ResourceConfigUtils.getAsBoolean(hopperBehavior.getOrDefault("allow-output", true), "allow-output"); - } + boolean canPlaceItem = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("allow-input", true), "allow-input"); + boolean canTakeItem = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("allow-output", true), "allow-output"); Property property = (Property) block.getProperty("open"); return new SimpleStorageBlockBehavior(block, title, rows, openSound, closeSound, hasAnalogOutputSignal, canPlaceItem, canTakeItem, property); } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml index 90f89d869..2ba57608c 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml @@ -19,8 +19,8 @@ items: resistance: 1.4 is-suffocating: true is-redstone-conductor: true - push-reaction: PUSH_ONLY - instrument: BASEDRUM + push-reaction: push_only + instrument: basedrum map-color: 36 sounds: break: minecraft:block.stone.break diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml index dbfe8ea9c..9a22722b8 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml @@ -21,8 +21,8 @@ items: - default:sound/wood - default:settings/solid_1x1x1 overrides: - push-reaction: NORMAL - instrument: HARP + push-reaction: normal + instrument: harp luminance: 15 map-color: 36 state: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml index 6bc0d8394..c0fe30805 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml @@ -24,7 +24,7 @@ items: replaceable: false is-redstone-conductor: true is-suffocating: true - instrument: BASEDRUM + instrument: basedrum map-color: 15 behavior: type: lamp_block diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml index 5e346254b..6ccd4a673 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml @@ -19,7 +19,7 @@ blocks: - default:sound/grass overrides: item: default:ender_pearl_flower_seeds - push-reaction: DESTROY + push-reaction: destroy map-color: 24 is-randomly-ticking: true behaviors: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml index 188ae9b80..4777b653c 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml @@ -17,7 +17,7 @@ items: - default:sound/grass overrides: item: default:fairy_flower - push-reaction: DESTROY + push-reaction: destroy map-color: 19 behavior: type: bush_block diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml index b5544c681..6a43fc1cf 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml @@ -16,7 +16,7 @@ items: - default:hardness/none - default:sound/grass overrides: - push-reaction: DESTROY + push-reaction: destroy map-color: 15 is-randomly-ticking: true behaviors: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml index 3cb8fb486..1bab9bb39 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml @@ -25,7 +25,7 @@ items: overrides: hardness: 0.5 resistance: 0.5 - instrument: SNARE + instrument: snare map-color: 45 state: id: 16 @@ -59,7 +59,7 @@ items: overrides: hardness: 1.8 resistance: 1.8 - instrument: BASEDRUM + instrument: basedrum map-color: 45 state: id: 17 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml index 7b000d9ae..add02ff3b 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml @@ -44,7 +44,7 @@ items: map-color: 29 hardness: 10.0 resistance: 1200 - push-reaction: BLOCK + push-reaction: block states: properties: facing_clockwise: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 901dbbb00..fe8e46bd6 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -363,7 +363,7 @@ items: template: - default:sound/wood overrides: - push-reaction: DESTROY + push-reaction: destroy map-color: 2 instrument: bass hardness: 3.0 @@ -588,7 +588,7 @@ items: - default:hardness/planks overrides: burnable: true - push-reaction: DESTROY + push-reaction: destroy map-color: 2 instrument: bass tags: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml index c275b52b8..9d5b8a502 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml @@ -17,7 +17,7 @@ items: - default:hardness/none overrides: map-color: 11 - push-reaction: DESTROY + push-reaction: destroy behaviors: - type: sturdy_base_block direction: down diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml index 3ecbd29a2..f444576b6 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml @@ -16,7 +16,7 @@ items: - default:hardness/none - default:sound/grass overrides: - push-reaction: DESTROY + push-reaction: destroy map-color: 60 behavior: type: on_liquid_block diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index e312d27cc..99d1a28e3 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -19,8 +19,8 @@ items: resistance: 1200 is-suffocating: true is-redstone-conductor: true - push-reaction: BLOCK - instrument: BASEDRUM + push-reaction: block + instrument: basedrum map-color: 6 sounds: break: minecraft:block.stone.break @@ -35,9 +35,8 @@ items: sounds: open: minecraft:block.iron_trapdoor.open close: minecraft:block.iron_trapdoor.close - hopper-behavior: - allow-input: true - allow-output: false + allow-input: true + allow-output: false states: properties: facing: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index 5a94e2c20..1f4a86917 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -21,8 +21,8 @@ items: burnable: true is-suffocating: false is-redstone-conductor: false - push-reaction: BLOCK - instrument: BASS + push-reaction: block + instrument: bass sounds: break: minecraft:block.wood.break fall: minecraft:block.wood.fall @@ -68,8 +68,8 @@ items: burnable: true is-suffocating: false is-redstone-conductor: false - push-reaction: BLOCK - instrument: BASS + push-reaction: block + instrument: bass sounds: break: minecraft:block.wood.break fall: minecraft:block.wood.fall diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 8e6a62609..e2c881a6c 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -747,7 +747,7 @@ templates#settings#blocks: - default:hardness/none - default:sound/grass overrides: - push-reaction: DESTROY + push-reaction: destroy is-randomly-ticking: true map-color: 7 tags: @@ -763,11 +763,11 @@ templates#settings#blocks: overrides: hardness: 0.2 resistance: 0.2 - push-reaction: DESTROY + push-reaction: destroy replaceable: false is-redstone-conductor: false is-suffocating: false - instrument: HARP + instrument: harp tags: - minecraft:mineable/hoe - minecraft:sword_efficient @@ -780,11 +780,11 @@ templates#settings#blocks: - default:burn_data/wood - default:hardness/wood overrides: - push-reaction: NORMAL + push-reaction: normal replaceable: false is-redstone-conductor: true is-suffocating: true - instrument: BASS + instrument: bass can-occlude: true tags: - minecraft:mineable/axe @@ -798,11 +798,11 @@ templates#settings#blocks: - default:burn_data/planks - default:hardness/planks overrides: - push-reaction: NORMAL + push-reaction: normal replaceable: false is-redstone-conductor: true is-suffocating: true - instrument: BASS + instrument: bass can-occlude: true tags: - minecraft:mineable/axe @@ -814,10 +814,10 @@ templates#settings#blocks: overrides: hardness: 3.0 resistance: 3.0 - push-reaction: NORMAL + push-reaction: normal is-redstone-conductor: true is-suffocating: true - instrument: BASEDRUM + instrument: basedrum can-occlude: true map-color: 11 tags: @@ -830,10 +830,10 @@ templates#settings#blocks: overrides: hardness: 4.5 resistance: 3.0 - push-reaction: NORMAL + push-reaction: normal is-redstone-conductor: true is-suffocating: true - instrument: BASEDRUM + instrument: basedrum can-occlude: true map-color: 59 tags: From 94c02f6b7b306cb664c8a8836fff6fb80d82924c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 03:32:13 +0800 Subject: [PATCH 120/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=B4=AB=E6=B0=B4?= =?UTF-8?q?=E6=99=B6=E7=81=AB=E6=8A=8A=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitCustomBlock.java | 4 +- .../plugin/user/BukkitServerPlayer.java | 1 - .../main/resources/additional-real-blocks.yml | 4 +- .../configuration/blocks/amethyst_torch.yml | 143 ++++++++++++++++++ .../default/configuration/categories.yml | 3 +- .../resources/default/configuration/i18n.yml | 8 +- .../textures/block/custom/amethyst_torch.png | Bin 0 -> 220 bytes .../core/pack/AbstractPackManager.java | 2 + 8 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/amethyst_torch.png diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java index c70387f2e..ae91184a2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java @@ -76,10 +76,10 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { try { for (ImmutableBlockState immutableBlockState : variantProvider().states()) { if (immutableBlockState.vanillaBlockState() == null) { - CraftEngine.instance().logger().warn("Could not find vanilla block immutableBlockState for " + immutableBlockState + ". This might cause errors!"); + CraftEngine.instance().logger().warn("Could not find vanilla visual block state for " + immutableBlockState + ". This might cause errors!"); continue; } else if (immutableBlockState.customBlockState() == null) { - CraftEngine.instance().logger().warn("Could not find custom block immutableBlockState for " + immutableBlockState + ". This might cause errors!"); + CraftEngine.instance().logger().warn("Could not find real block state for " + immutableBlockState + ". This might cause errors!"); continue; } DelegatingBlockState nmsState = (DelegatingBlockState) immutableBlockState.customBlockState().literalObject(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index f4a4ed340..c08d6af2d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -13,7 +13,6 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.gui.CraftEngineGUIHolder; import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; -import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MMobEffects; diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 8de88617e..2c6f2c87d 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -82,4 +82,6 @@ minecraft:bamboo_fence_gate: 16 minecraft:crimson_fence_gate: 16 minecraft:warped_fence_gate: 16 minecraft:barrier: 128 -minecraft:white_bed: 1 \ No newline at end of file +minecraft:white_bed: 1 +minecraft:redstone_torch: 1 +minecraft:redstone_wall_torch: 4 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml new file mode 100644 index 000000000..b99aeadfe --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -0,0 +1,143 @@ +items: + default:amethyst_torch: + material: nether_brick + custom-model-data: 3019 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/amethyst_torch + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:block/custom/amethyst_torch + behavior: + - type: block_item + block: default:amethyst_wall_torch + - type: block_item + block: default:amethyst_torch + default:amethyst_standing_torch: + material: nether_brick + custom-model-data: 3020 + data: + item-name: + model: + type: minecraft:model + path: minecraft:block/custom/amethyst_torch + generation: + parent: minecraft:block/template_torch + textures: + torch: minecraft:block/custom/amethyst_torch + default:amethyst_wall_torch: + material: nether_brick + custom-model-data: 3021 + data: + item-name: + model: + type: minecraft:model + path: minecraft:block/custom/amethyst_wall_torch + generation: + parent: minecraft:block/template_torch_wall + textures: + torch: minecraft:block/custom/amethyst_torch +blocks: + default:amethyst_torch: + loot: + template: default:loot_table/basic + arguments: + item: default:amethyst_torch + settings: + template: + - default:sound/wood + - default:hardness/none + overrides: + push-reaction: destroy + replaceable: false + map-color: 24 + luminance: 15 + item: default:amethyst_torch + state: + id: 0 + state: redstone_torch[lit=false] + entity-renderer: + item: default:amethyst_standing_torch + scale: 1.01 + behavior: + type: sturdy_base_block + direction: down + support-types: + - center + default:amethyst_wall_torch: + loot: + template: default:loot_table/basic + arguments: + item: default:amethyst_torch + settings: + template: + - default:sound/wood + - default:hardness/none + overrides: + push-reaction: destroy + replaceable: false + map-color: 24 + luminance: 15 + item: default:amethyst_torch + behavior: + type: directional_attached_block + states: + properties: + facing: + type: horizontal_direction + appearances: + north: + state: redstone_wall_torch[facing=north,lit=false] + entity-renderer: + item: default:amethyst_wall_torch + scale: 1.04 + yaw: 90 + translation: -0.02,0.01,0 + east: + state: redstone_wall_torch[facing=east,lit=false] + entity-renderer: + item: default:amethyst_wall_torch + scale: 1.04 + yaw: 180 + translation: -0.02,0.01,0 + west: + state: redstone_wall_torch[facing=west,lit=false] + entity-renderer: + item: default:amethyst_wall_torch + scale: 1.04 + translation: -0.02,0.01,0 + south: + state: redstone_wall_torch[facing=south,lit=false] + entity-renderer: + item: default:amethyst_wall_torch + scale: 1.04 + yaw: -90 + translation: -0.02,0.01,0 + variants: + facing=north: + appearance: north + id: 0 + facing=east: + appearance: east + id: 1 + facing=west: + appearance: west + id: 2 + facing=south: + appearance: south + id: 3 +recipes: + default:amethyst_torch: + type: shaped + pattern: + - 'A' + - 'B' + ingredients: + A: minecraft:amethyst_shard + B: minecraft:stick + result: + id: default:amethyst_torch + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 7514c7909..55610a412 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -77,4 +77,5 @@ categories: - default:table_lamp - default:bench - default:wooden_chair - - default:flower_basket \ No newline at end of file + - default:flower_basket + - default:amethyst_torch \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index 465a6f86e..fa91ebbb0 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -48,6 +48,7 @@ i18n: item.chessboard_block: Chessboard Block item.safe_block: Safe Block item.sofa: Sofa + item.amethyst_torch: Amethyst Torch category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -105,6 +106,7 @@ i18n: item.chessboard_block: 棋盘方块 item.safe_block: 保险柜 item.sofa: 沙发 + item.amethyst_torch: 紫水晶火把 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -145,6 +147,8 @@ lang: block_name:default:safe_block: Safe Block block_name:default:sleeper_sofa: Sofa block_name:default:sofa: Sofa + block_name:default:amethyst_torch: Amethyst Torch + block_name:default:amethyst_wall_torch: Amethyst Torch zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -172,4 +176,6 @@ lang: block_name:default:chessboard_block: 棋盘方块 block_name:default:safe_block: 保险柜 block_name:default:sleeper_sofa: 沙发 - block_name:default:sofa: 沙发 \ No newline at end of file + block_name:default:sofa: 沙发 + block_name:default:amethyst_torch: 紫水晶火把 + block_name:default:amethyst_wall_torch: 紫水晶火把 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/amethyst_torch.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/amethyst_torch.png new file mode 100644 index 0000000000000000000000000000000000000000..a190ec9d5f8c01a3a7611a44c6b7d07627b2a29e GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!fo^#Gp`*ZK7Ux#4D^u6k2T-Ts|^c7DNUCsQSBJ?Y-8Gqwh@ z=33(a|NjT76`G Date: Sun, 14 Sep 2025 04:34:04 +0800 Subject: [PATCH 121/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E6=B6=B2=E4=BD=93=E6=B5=81=E8=BF=87=E7=9A=84=E6=96=B9=E5=9D=97?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BouncingBlockBehavior.java | 4 +- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../behavior/LiquidFlowableBlockBehavior.java | 49 +++++++++++++++++++ .../UnsafeCompositeBlockBehavior.java | 34 ++++++++++--- .../entity/SimpleStorageBlockEntity.java | 2 +- .../bukkit/plugin/gui/BukkitGuiManager.java | 2 +- .../plugin/injector/BlockGenerator.java | 6 +-- .../configuration/blocks/amethyst_torch.yml | 12 +++-- ...Behavior.java => FallOnBlockBehavior.java} | 2 +- .../special/PlaceLiquidBlockBehavior.java | 10 ++++ gradle.properties | 2 +- 11 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LiquidFlowableBlockBehavior.java rename core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/{TriggerOnceBlockBehavior.java => FallOnBlockBehavior.java} (93%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/PlaceLiquidBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java index f7895e804..87d709036 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BouncingBlockBehavior.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; -import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.special.FallOnBlockBehavior; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; @@ -17,7 +17,7 @@ import org.bukkit.entity.Entity; import java.util.Map; import java.util.concurrent.Callable; -public class BouncingBlockBehavior extends BukkitBlockBehavior implements TriggerOnceBlockBehavior { +public class BouncingBlockBehavior extends BukkitBlockBehavior implements FallOnBlockBehavior { public static final Factory FACTORY = new Factory(); private final double bounceHeight; private final boolean syncPlayerPosition; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index b6497732a..948c8a44e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -33,6 +33,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); public static final Key DIRECTIONAL_ATTACHED_BLOCK = Key.from("craftengine:directional_attached_block"); + public static final Key LIQUID_FLOWABLE_BLOCK = Key.from("craftengine:liquid_flowable_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -64,5 +65,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); register(DIRECTIONAL_ATTACHED_BLOCK, DirectionalAttachedBlockBehavior.FACTORY); + register(LIQUID_FLOWABLE_BLOCK, LiquidFlowableBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LiquidFlowableBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LiquidFlowableBlockBehavior.java new file mode 100644 index 000000000..65edd4bdc --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LiquidFlowableBlockBehavior.java @@ -0,0 +1,49 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.special.PlaceLiquidBlockBehavior; +import net.momirealms.craftengine.core.world.WorldEvents; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class LiquidFlowableBlockBehavior extends BukkitBlockBehavior implements PlaceLiquidBlockBehavior { + public static final Factory FACTORY = new Factory(); + + public LiquidFlowableBlockBehavior(CustomBlock customBlock) { + super(customBlock); + } + + @Override + public boolean canPlaceLiquid(Object thisBlock, Object[] args, Callable superMethod) { + return true; + } + + @Override + public boolean placeLiquid(Object thisBlock, Object[] args, Callable superMethod) { + Object level = args[0]; + Object pos = args[1]; + Object blockState = args[2]; + Object fluidState = args[3]; + Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(fluidState); + if (fluidType == MFluids.LAVA || fluidType == MFluids.FLOWING_LAVA) { + FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.LAVA_CONVERTS_BLOCK, pos, 0); + } else { + FastNMS.INSTANCE.method$Block$dropResources(blockState, level, pos); + } + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, FastNMS.INSTANCE.method$FluidState$createLegacyBlock(fluidState), UpdateOption.UPDATE_ALL.flags()); + return true; + } + + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + return new LiquidFlowableBlockBehavior(block); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index d70cf4e47..2ab839d6b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.special.FallOnBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.special.PlaceLiquidBlockBehavior; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.UseOnContext; @@ -15,7 +16,8 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; -public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior implements TriggerOnceBlockBehavior { +public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior + implements FallOnBlockBehavior, PlaceLiquidBlockBehavior { private final AbstractBlockBehavior[] behaviors; public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List behaviors) { @@ -23,6 +25,26 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior implements this.behaviors = behaviors.toArray(new AbstractBlockBehavior[0]); } + @Override + public boolean canPlaceLiquid(Object thisBlock, Object[] args, Callable superMethod) { + for (AbstractBlockBehavior behavior : behaviors) { + if (behavior instanceof PlaceLiquidBlockBehavior) { + return behavior.canPlaceLiquid(thisBlock, args, superMethod); + } + } + return super.canPlaceLiquid(thisBlock, args, superMethod); + } + + @Override + public boolean placeLiquid(Object thisBlock, Object[] args, Callable superMethod) { + for (AbstractBlockBehavior behavior : behaviors) { + if (behavior instanceof PlaceLiquidBlockBehavior) { + return behavior.placeLiquid(thisBlock, args, superMethod); + } + } + return super.placeLiquid(thisBlock, args, superMethod); + } + @SuppressWarnings("unchecked") @Override public Optional getAs(Class tClass) { @@ -341,22 +363,22 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior implements @Override public void fallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { for (AbstractBlockBehavior behavior : this.behaviors) { - if (behavior instanceof TriggerOnceBlockBehavior f) { + if (behavior instanceof FallOnBlockBehavior f) { f.fallOn(thisBlock, args, superMethod); return; } } - TriggerOnceBlockBehavior.super.fallOn(thisBlock, args, superMethod); + FallOnBlockBehavior.super.fallOn(thisBlock, args, superMethod); } @Override public void updateEntityMovementAfterFallOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { for (AbstractBlockBehavior behavior : this.behaviors) { - if (behavior instanceof TriggerOnceBlockBehavior f) { + if (behavior instanceof FallOnBlockBehavior f) { f.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); return; } } - TriggerOnceBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); + FallOnBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index 60bfd9399..fcb654c71 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -41,7 +41,7 @@ public class SimpleStorageBlockEntity extends BlockEntity { super(BukkitBlockEntityTypes.SIMPLE_STORAGE, pos, blockState); this.behavior = super.blockState.behavior().getAs(SimpleStorageBlockBehavior.class).orElseThrow(); BlockEntityHolder holder = new BlockEntityHolder(this); - this.inventory = FastNMS.INSTANCE.createCraftEngineWorldlyContainer(holder, this.behavior.rows() * 9, this.behavior.canPlaceItem(), this.behavior.canTakeItem()); + this.inventory = FastNMS.INSTANCE.createSimpleStorageContainer(holder, this.behavior.rows() * 9, this.behavior.canPlaceItem(), this.behavior.canTakeItem()); holder.setInventory(this.inventory); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index c30af575b..fb01ccc33 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -90,7 +90,7 @@ public class BukkitGuiManager implements GuiManager, Listener { @Override public Inventory createInventory(Gui gui, int size) { CraftEngineGUIHolder holder = new CraftEngineGUIHolder(gui); - org.bukkit.inventory.Inventory inventory = FastNMS.INSTANCE.createCraftEngineWorldlyContainer(holder, size, false, false); + org.bukkit.inventory.Inventory inventory = FastNMS.INSTANCE.createSimpleStorageContainer(holder, size, false, false); holder.holder().bindValue(inventory); return new BukkitInventory(inventory); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index fb1cd5739..f7105c15e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -23,7 +23,7 @@ import net.momirealms.craftengine.core.block.BlockKeys; import net.momirealms.craftengine.core.block.BlockShape; import net.momirealms.craftengine.core.block.DelegatingBlock; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.special.TriggerOnceBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.special.FallOnBlockBehavior; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.Key; @@ -715,7 +715,7 @@ public final class BlockGenerator { public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); try { - if (holder.value() instanceof TriggerOnceBlockBehavior behavior) { + if (holder.value() instanceof FallOnBlockBehavior behavior) { behavior.fallOn(thisObj, args, superMethod); } else { superMethod.call(); @@ -733,7 +733,7 @@ public final class BlockGenerator { public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); try { - if (holder.value() instanceof TriggerOnceBlockBehavior behavior) { + if (holder.value() instanceof FallOnBlockBehavior behavior) { behavior.updateEntityMovementAfterFallOn(thisObj, args, superMethod); } else { superMethod.call(); diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml index b99aeadfe..e4fe38ab3 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -63,10 +63,11 @@ blocks: item: default:amethyst_standing_torch scale: 1.01 behavior: - type: sturdy_base_block - direction: down - support-types: - - center + - type: sturdy_base_block + direction: down + support-types: + - center + - type: liquid_flowable_block default:amethyst_wall_torch: loot: template: default:loot_table/basic @@ -83,7 +84,8 @@ blocks: luminance: 15 item: default:amethyst_torch behavior: - type: directional_attached_block + - type: directional_attached_block + - type: liquid_flowable_block states: properties: facing: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/FallOnBlockBehavior.java similarity index 93% rename from core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java rename to core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/FallOnBlockBehavior.java index 4cad47085..5376477a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/TriggerOnceBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/FallOnBlockBehavior.java @@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.block.behavior.special; import java.util.concurrent.Callable; -public interface TriggerOnceBlockBehavior { +public interface FallOnBlockBehavior { // 1.20.1~1.21.4 Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance // 1.21.5+ Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/PlaceLiquidBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/PlaceLiquidBlockBehavior.java new file mode 100644 index 000000000..ddab31b4f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/special/PlaceLiquidBlockBehavior.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.block.behavior.special; + +import java.util.concurrent.Callable; + +public interface PlaceLiquidBlockBehavior { + + boolean placeLiquid(Object thisBlock, Object[] args, Callable superMethod); + + boolean canPlaceLiquid(Object thisBlock, Object[] args, Callable superMethod); +} diff --git a/gradle.properties b/gradle.properties index 6cacafa74..d8657261e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.86 +nms_helper_version=1.0.87 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 8032d15470ded5a35be5d4cf7e71ab7ff80ba4a4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 04:42:56 +0800 Subject: [PATCH 122/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A2=99=E9=9D=A2?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=89=A9=E5=93=81=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/behavior/BukkitItemBehaviors.java | 2 + .../item/behavior/WallBlockItemBehavior.java | 56 +++++++++++++++++++ .../configuration/blocks/amethyst_torch.yml | 4 +- .../src/main/resources/translations/en.yml | 1 + gradle.properties | 4 +- 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java index 79292cfda..271930a23 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java @@ -12,6 +12,7 @@ public class BukkitItemBehaviors extends ItemBehaviors { public static final Key COMPOSTABLE_ITEM = Key.from("craftengine:compostable_item"); public static final Key AXE_ITEM = Key.from("craftengine:axe_item"); public static final Key DOUBLE_HIGH_BLOCK_ITEM = Key.from("craftengine:double_high_block_item"); + public static final Key WALL_BLOCK_ITEM = Key.from("craftengine:wall_block_item"); public static void init() { register(EMPTY, EmptyItemBehavior.FACTORY); @@ -22,5 +23,6 @@ public class BukkitItemBehaviors extends ItemBehaviors { register(COMPOSTABLE_ITEM, CompostableItemBehavior.FACTORY); register(AXE_ITEM, AxeItemBehavior.FACTORY); register(DOUBLE_HIGH_BLOCK_ITEM, DoubleHighBlockItemBehavior.FACTORY); + register(WALL_BLOCK_ITEM, WallBlockItemBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java new file mode 100644 index 000000000..53767eeaf --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java @@ -0,0 +1,56 @@ +package net.momirealms.craftengine.bukkit.item.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; + +import java.nio.file.Path; +import java.util.Map; + +public class WallBlockItemBehavior extends BlockItemBehavior { + public static final Factory FACTORY = new Factory(); + + public WallBlockItemBehavior(Key wallBlockId) { + super(wallBlockId); + } + + @Override + public InteractionResult useOnBlock(UseOnContext context) { + return this.place(new BlockPlaceContext(context)); + } + + public InteractionResult place(BlockPlaceContext context) { + if (context.getClickedFace().stepY() != 0) { + return InteractionResult.PASS; + } + return super.place(context); + } + + public static class Factory implements ItemBehaviorFactory { + @Override + public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + Object id = arguments.get("block"); + if (id == null) { + throw new LocalizedResourceConfigException("warning.config.item.behavior.wall_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for wall_block_item behavior")); + } + if (id instanceof Map map) { + if (map.containsKey(key.toString())) { + // 防呆 + BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + } else { + BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + } + return new WallBlockItemBehavior(key); + } else { + return new WallBlockItemBehavior(Key.of(id.toString())); + } + } + } +} diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml index e4fe38ab3..2963b0af2 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -12,10 +12,12 @@ items: textures: layer0: minecraft:block/custom/amethyst_torch behavior: - - type: block_item + - type: wall_block_item block: default:amethyst_wall_torch - type: block_item block: default:amethyst_torch + - type: block_item + block: default:amethyst_wall_torch default:amethyst_standing_torch: material: nether_brick custom-model-data: 3020 diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index e469cdf50..824db15c7 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -196,6 +196,7 @@ warning.config.item.missing_model: "Issue found in file - The it warning.config.item.behavior.missing_type: "Issue found in file - The item '' is missing the required 'type' argument for its item behavior." warning.config.item.behavior.invalid_type: "Issue found in file - The item '' is using an invalid item behavior type ''." warning.config.item.behavior.block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'block_item' behavior." +warning.config.item.behavior.wall_block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'wall_block_item' behavior." warning.config.item.behavior.furniture.missing_furniture: "Issue found in file - The item '' is missing the required 'furniture' argument for 'furniture_item' behavior." warning.config.item.behavior.liquid_collision.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'liquid_collision_block_item' behavior." warning.config.item.behavior.double_high.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'double_high_block_item' behavior." diff --git a/gradle.properties b/gradle.properties index d8657261e..d93169b9c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.19 +project_version=0.0.62.20 config_version=45 -lang_version=28 +lang_version=29 project_group=net.momirealms latest_supported_version=1.21.8 From e28c29aef97b21413b91e684f86d67db1af73f3d Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 14:26:38 +0800 Subject: [PATCH 123/226] =?UTF-8?q?feat(block):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=B2=92=E5=AD=90=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../block/behavior/ParticleBlockBehavior.java | 75 +++++++++++++++++++ .../behavior/SimpleStorageBlockBehavior.java | 3 +- .../block/entity/BaseParticleBlockEntity.java | 27 +++++++ .../block/entity/BukkitBlockEntityTypes.java | 2 + .../block/entity/ParticleBlockEntity.java | 31 ++++++++ .../block/entity/WallParticleBlockEntity.java | 51 +++++++++++++ .../craftengine/bukkit/world/BukkitWorld.java | 4 +- .../configuration/blocks/amethyst_torch.yml | 13 ++++ .../src/main/resources/translations/en.yml | 12 +-- .../src/main/resources/translations/zh_cn.yml | 4 +- .../block/behavior/EntityBlockBehavior.java | 7 +- .../block/entity/BlockEntityTypeKeys.java | 3 +- .../core/util/ResourceConfigUtils.java | 19 +++++ .../craftengine/core/world/World.java | 2 +- 15 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 948c8a44e..0ccb79370 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -34,6 +34,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); public static final Key DIRECTIONAL_ATTACHED_BLOCK = Key.from("craftengine:directional_attached_block"); public static final Key LIQUID_FLOWABLE_BLOCK = Key.from("craftengine:liquid_flowable_block"); + public static final Key PARTICLE_BLOCK = Key.from("craftengine:particle_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -66,5 +67,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); register(DIRECTIONAL_ATTACHED_BLOCK, DirectionalAttachedBlockBehavior.FACTORY); register(LIQUID_FLOWABLE_BLOCK, LiquidFlowableBlockBehavior.FACTORY); + register(PARTICLE_BLOCK, ParticleBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java new file mode 100644 index 000000000..548b159c9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java @@ -0,0 +1,75 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; +import net.momirealms.craftengine.bukkit.block.entity.BaseParticleBlockEntity; +import net.momirealms.craftengine.bukkit.block.entity.ParticleBlockEntity; +import net.momirealms.craftengine.bukkit.block.entity.WallParticleBlockEntity; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.Vec3d; + +import java.util.List; +import java.util.Map; + +public class ParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final List particles; + private final boolean inWall; + + public ParticleBlockBehavior(CustomBlock customBlock, List particles, boolean inWall) { + super(customBlock); + this.particles = particles; + this.inWall = inWall; + } + + public List particles() { + return particles; + } + + @Override + public BlockEntityType blockEntityType() { + return EntityBlockBehavior.blockEntityTypeHelper(this.inWall ? BukkitBlockEntityTypes.WALL_PARTICLE : BukkitBlockEntityTypes.PARTICLE); + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { + return this.inWall ? new WallParticleBlockEntity(pos, state) : new ParticleBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + if (this.particles().isEmpty()) return null; + return EntityBlockBehavior.createTickerHelper(BaseParticleBlockEntity::tick); + } + + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + List particles = ResourceConfigUtils.parseConfigAsList(arguments.getOrDefault("particles", List.of()), ParticleData::fromMap); + boolean inWall = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("in-wall", false), "in-wall"); + return new ParticleBlockBehavior(block, particles, inWall); + } + } + + public record ParticleData(Key particle, Vec3d locationOffset, Vec3d offset, int count, double speed) { + + public static ParticleData fromMap(Map arguments) { + Key particle = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("type"), "warning.config.block.behavior.particle.missing_type")); + Vec3d locationOffset = ResourceConfigUtils.getAsVec3d(arguments.get("location-offset"), "location-offset"); + Vec3d offset = ResourceConfigUtils.getAsVec3d(arguments.get("offset"), "offset"); + int count = ResourceConfigUtils.getAsInt(arguments.getOrDefault("count", 1), "count"); + double speed = ResourceConfigUtils.getAsDouble(arguments.get("speed"), "speed"); + return new ParticleData(particle, locationOffset, offset, count, speed); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java index 9ec94e21b..0a7933cad 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -105,10 +105,9 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E } } - @SuppressWarnings("unchecked") @Override public BlockEntityType blockEntityType() { - return (BlockEntityType) BukkitBlockEntityTypes.SIMPLE_STORAGE; + return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.SIMPLE_STORAGE); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java new file mode 100644 index 000000000..fb8d63c83 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.World; + +public abstract class BaseParticleBlockEntity extends BlockEntity { + protected final ParticleBlockBehavior behavior; + protected int tickCount; + + public BaseParticleBlockEntity(BlockEntityType type, BlockPos pos, ImmutableBlockState blockState) { + super(type, pos, blockState); + this.behavior = super.blockState.behavior().getAs(ParticleBlockBehavior.class).orElseThrow(); + } + + public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) { + particle.tickCount++; + if (particle.tickCount % 10 != 0) return; + particle.animateTick(state, ceWorld.world(), blockPos); + } + + public abstract void animateTick(ImmutableBlockState state, World level, BlockPos pos); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java index 3e778bd32..75c449fa9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -6,4 +6,6 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; public class BukkitBlockEntityTypes extends BlockEntityTypes { public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); + public static final BlockEntityType PARTICLE = register(BlockEntityTypeKeys.PARTICLE, ParticleBlockEntity::new); + public static final BlockEntityType WALL_PARTICLE = register(BlockEntityTypeKeys.WALL_PARTICLE, WallParticleBlockEntity::new); } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java new file mode 100644 index 000000000..485eb0c85 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java @@ -0,0 +1,31 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; + +public class ParticleBlockEntity extends BaseParticleBlockEntity { + + public ParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.PARTICLE, pos, blockState); + } + + @Override + public void animateTick(ImmutableBlockState state, World level, BlockPos pos) { + for (ParticleBlockBehavior.ParticleData particle : behavior.particles()) { + Vec3d location = particle.locationOffset().add(pos.x(), pos.y(), pos.z()); + level.spawnParticle( + location, + particle.particle(), + particle.count(), + particle.offset().x(), + particle.offset().y(), + particle.offset().z(), + particle.speed(), + null, null + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java new file mode 100644 index 000000000..7de011fb9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; + +public class WallParticleBlockEntity extends BaseParticleBlockEntity { + + public WallParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.WALL_PARTICLE, pos, blockState); + } + + @Override + public void animateTick(ImmutableBlockState state, World level, BlockPos pos) { + Direction direction = null; + for (Property property : state.getProperties()) { + if (!property.name().equals("facing")) continue; + if (property.valueClass() == Direction.class) { + direction = (Direction) state.get(property); + break; + } else if (property.valueClass() == HorizontalDirection.class) { + direction = ((HorizontalDirection) state.get(property)).toDirection(); + break; + } + } + if (direction != null) { + direction = direction.opposite(); + } + for (ParticleBlockBehavior.ParticleData particle : behavior.particles()) { + Vec3d location = particle.locationOffset().add(pos.x(), pos.y(), pos.z()); + if (direction != null) { + location = location.add(0.27 * direction.stepX(), 0.22, 0.27 * direction.stepZ()); + } + level.spawnParticle( + location, + particle.particle(), + particle.count(), + particle.offset().x(), + particle.offset().y(), + particle.offset().z(), + particle.speed(), + null, null + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 2624bc78d..28b39712d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -106,11 +106,11 @@ public class BukkitWorld implements World { } @Override - public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context) { + public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @Nullable Context context) { Particle particleType = ParticleUtils.getParticle(particle); if (particleType == null) return; org.bukkit.World platformWorld = platformWorld(); - platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z())); + platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null || context == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z())); } @Override diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml index 2963b0af2..1ee3349c5 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -70,6 +70,12 @@ blocks: support-types: - center - type: liquid_flowable_block + - type: particle_block + particles: + - type: smoke + location-offset: 0.5,0.7,0.5 + - type: flame + location-offset: 0.5,0.7,0.5 default:amethyst_wall_torch: loot: template: default:loot_table/basic @@ -88,6 +94,13 @@ blocks: behavior: - type: directional_attached_block - type: liquid_flowable_block + - type: particle_block + in-wall: true + particles: + - type: smoke + location-offset: 0.5,0.7,0.5 + - type: flame + location-offset: 0.5,0.7,0.5 states: properties: facing: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 824db15c7..d57a7cc15 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -73,6 +73,7 @@ warning.config.type.float: "Issue found in file - Failed to load warning.config.type.double: "Issue found in file - Failed to load '': Cannot cast '' to double type for option ''." warning.config.type.quaternionf: "Issue found in file - Failed to load '': Cannot cast '' to Quaternionf type for option ''." warning.config.type.vector3f: "Issue found in file - Failed to load '': Cannot cast '' to Vector3f type for option ''." +warning.config.type.vec3d: "Issue found in file - Failed to load '': Cannot cast '' to Vec3d type for option ''." warning.config.type.map: "Issue found in file - Failed to load '': Cannot cast '' to Map type for option ''." warning.config.type.snbt.invalid_syntax: "Issue found in file - Failed to load '': Invalid snbt syntax ''." warning.config.number.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for number argument." @@ -304,10 +305,10 @@ warning.config.block.behavior.trapdoor.missing_open: "Issue found in fil warning.config.block.behavior.trapdoor.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'trapdoor_block' behavior." warning.config.block.behavior.stackable.missing_property: "Issue found in file - The block '' is missing the required '' property for 'stackable_block' behavior." warning.config.block.behavior.stackable.missing_items: "Issue found in file - The block '' is missing the required 'items' argument for 'stackable_block' behavior." -warning.config.block.behavior.fence_gate.missing_facing: "Issue found in file - The block '' is missing the required 'facing' argument for 'fence_gate_block' behavior." -warning.config.block.behavior.fence_gate.missing_in_wall: "Issue found in file - The block '' is missing the required 'in_wall' argument for 'fence_gate_block' behavior." -warning.config.block.behavior.fence_gate.missing_open: "Issue found in file - The block '' is missing the required 'powered' argument for 'fence_gate_block' behavior." -warning.config.block.behavior.fence_gate.missing_powered: "Issue found in file - The block '' is missing the required 'open' argument for 'fence_gate_block' behavior." +warning.config.block.behavior.fence_gate.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'fence_gate_block' behavior." +warning.config.block.behavior.fence_gate.missing_in_wall: "Issue found in file - The block '' is missing the required 'in_wall' property for 'fence_gate_block' behavior." +warning.config.block.behavior.fence_gate.missing_open: "Issue found in file - The block '' is missing the required 'powered' property for 'fence_gate_block' behavior." +warning.config.block.behavior.fence_gate.missing_powered: "Issue found in file - The block '' is missing the required 'open' property for 'fence_gate_block' behavior." warning.config.block.behavior.slab.missing_type: "Issue found in file - The block '' is missing the required 'type' property for 'slab_block' behavior." warning.config.block.behavior.stairs.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'stairs_block' behavior." warning.config.block.behavior.stairs.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'stairs_block' behavior." @@ -317,8 +318,9 @@ warning.config.block.behavior.sofa.missing_shape: "Issue found in file < warning.config.block.behavior.pressure_plate.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'pressure_plate_block' behavior." warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." -warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' property for 'change_over_time_block' behavior." +warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' argument for 'change_over_time_block' behavior." warning.config.block.behavior.surface_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'surface_attached_block' behavior." +warning.config.block.behavior.particle.missing_type: "Issue found in file - The block '' is missing the required 'type' argument for 'particle_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index f66a18c69..902056cd9 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -73,6 +73,7 @@ warning.config.type.float: "在文件 发现问题 - 无法加 warning.config.type.double: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度类型 (选项 '')" warning.config.type.quaternionf: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为四元数类型 (选项 '')" warning.config.type.vector3f: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为三维向量类型 (选项 '')" +warning.config.type.vec3d: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度浮点数三维向量类型 (选项 '')" warning.config.type.map: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为映射类型 (选项 '')" warning.config.type.snbt.invalid_syntax: "在文件 发现问题 - 无法加载 '': 无效的 SNBT 语法 ''" warning.config.number.missing_type: "在文件 发现问题 - 配置项 '' 缺少数字类型所需的 'type' 参数" @@ -311,8 +312,9 @@ warning.config.block.behavior.sofa.missing_shape: "在文件 发 warning.config.block.behavior.pressure_plate.missing_powered: "在文件 发现问题 - 方块 '' 的 'pressure_plate_block' 行为缺少必需的 'powered' 属性" warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" -warning.config.block.behavior.change_over_time.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 配置项" +warning.config.block.behavior.change_over_time.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 参数" warning.config.block.behavior.surface_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'surface_attached_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.particle.missing_type: "在文件 发现问题 - 配置项 '' 的 'particle' 段落缺少必需的 'type' 参数" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java index b5df01017..f92b0e59a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java @@ -20,7 +20,12 @@ public interface EntityBlockBehavior { } @SuppressWarnings("unchecked") - static BlockEntityTicker createTickerHelper(BlockEntityTicker ticker) { + static BlockEntityTicker createTickerHelper(BlockEntityTicker ticker) { return (BlockEntityTicker) ticker; } + + @SuppressWarnings("unchecked") + static BlockEntityType blockEntityTypeHelper(BlockEntityType type) { + return (BlockEntityType) type; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java index b801d9156..6bc44620d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -7,5 +7,6 @@ public final class BlockEntityTypeKeys { public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite"); public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); - public static final Key SEAT = Key.of("craftengine:seat"); + public static final Key PARTICLE = Key.of("craftengine:particle"); + public static final Key WALL_PARTICLE = Key.of("craftengine:wall_particle"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index b62402aa4..58c09669e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.util; import com.mojang.datafixers.util.Either; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.world.Vec3d; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -261,4 +262,22 @@ public final class ResourceConfigUtils { } } } + + public static Vec3d getAsVec3d(Object o, String option) { + if (o == null) return new Vec3d(0, 0, 0); + if (o instanceof List list && list.size() == 3) { + return new Vec3d(Double.parseDouble(list.get(0).toString()), Double.parseDouble(list.get(1).toString()), Double.parseDouble(list.get(2).toString())); + } else { + String stringFormat = o.toString(); + String[] split = stringFormat.split(","); + if (split.length == 3) { + return new Vec3d(Double.parseDouble(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2])); + } else if (split.length == 1) { + double d = Double.parseDouble(split[0]); + return new Vec3d(d, d, d); + } else { + throw new LocalizedResourceConfigException("warning.config.type.vec3d", stringFormat, option); + } + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/World.java b/core/src/main/java/net/momirealms/craftengine/core/world/World.java index bc6181c8b..40dd01f58 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/World.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/World.java @@ -58,7 +58,7 @@ public interface World { void levelEvent(int id, BlockPos pos, int data); - void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context); + void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @Nullable Context context); long time(); From 708aa394a5d4efc99328644d1e2c3a9285fd1f11 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 20:49:53 +0800 Subject: [PATCH 124/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=B2=92=E5=AD=90?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 6 +- .../block/behavior/ParticleBlockBehavior.java | 75 ------------ .../behavior/SimpleParticleBlockBehavior.java | 64 ++++++++++ .../WallTorchParticleBlockBehavior.java | 78 ++++++++++++ .../AbstractAnimateTickBlockEntity.java | 14 +++ .../block/entity/BaseParticleBlockEntity.java | 27 ----- .../block/entity/BukkitBlockEntityTypes.java | 4 +- .../block/entity/ParticleBlockEntity.java | 31 ----- .../entity/SimpleParticleBlockEntity.java | 45 +++++++ .../block/entity/WallParticleBlockEntity.java | 51 -------- .../entity/WallTorchParticleBlockEntity.java | 55 +++++++++ .../craftengine/bukkit/world/BukkitWorld.java | 4 +- .../configuration/blocks/amethyst_torch.yml | 32 +++-- .../src/main/resources/translations/en.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 2 +- .../block/entity/BlockEntityTypeKeys.java | 4 +- .../context/function/ParticleFunction.java | 114 ++---------------- .../core/util/HorizontalDirection.java | 24 +++- .../craftengine/core/world/World.java | 2 +- .../core/world/particle/ParticleConfig.java | 101 ++++++++++++++++ .../world/particle/ParticleDataTypes.java | 77 ++++++++++++ 21 files changed, 495 insertions(+), 317 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/AbstractAnimateTickBlockEntity.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleParticleBlockEntity.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleDataTypes.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 0ccb79370..f6cc1c58e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -34,7 +34,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); public static final Key DIRECTIONAL_ATTACHED_BLOCK = Key.from("craftengine:directional_attached_block"); public static final Key LIQUID_FLOWABLE_BLOCK = Key.from("craftengine:liquid_flowable_block"); - public static final Key PARTICLE_BLOCK = Key.from("craftengine:particle_block"); + public static final Key SIMPLE_PARTICLE_BLOCK = Key.from("craftengine:simple_particle_block"); + public static final Key WALL_TORCH_PARTICLE_BLOCK = Key.from("craftengine:wall_torch_particle_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -67,6 +68,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); register(DIRECTIONAL_ATTACHED_BLOCK, DirectionalAttachedBlockBehavior.FACTORY); register(LIQUID_FLOWABLE_BLOCK, LiquidFlowableBlockBehavior.FACTORY); - register(PARTICLE_BLOCK, ParticleBlockBehavior.FACTORY); + register(SIMPLE_PARTICLE_BLOCK, SimpleParticleBlockBehavior.FACTORY); + register(WALL_TORCH_PARTICLE_BLOCK, WallTorchParticleBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java deleted file mode 100644 index 548b159c9..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ParticleBlockBehavior.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.behavior; - -import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; -import net.momirealms.craftengine.bukkit.block.entity.BaseParticleBlockEntity; -import net.momirealms.craftengine.bukkit.block.entity.ParticleBlockEntity; -import net.momirealms.craftengine.bukkit.block.entity.WallParticleBlockEntity; -import net.momirealms.craftengine.core.block.BlockBehavior; -import net.momirealms.craftengine.core.block.CustomBlock; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; -import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; -import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.entity.BlockEntityType; -import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.CEWorld; -import net.momirealms.craftengine.core.world.Vec3d; - -import java.util.List; -import java.util.Map; - -public class ParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { - public static final Factory FACTORY = new Factory(); - private final List particles; - private final boolean inWall; - - public ParticleBlockBehavior(CustomBlock customBlock, List particles, boolean inWall) { - super(customBlock); - this.particles = particles; - this.inWall = inWall; - } - - public List particles() { - return particles; - } - - @Override - public BlockEntityType blockEntityType() { - return EntityBlockBehavior.blockEntityTypeHelper(this.inWall ? BukkitBlockEntityTypes.WALL_PARTICLE : BukkitBlockEntityTypes.PARTICLE); - } - - @Override - public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { - return this.inWall ? new WallParticleBlockEntity(pos, state) : new ParticleBlockEntity(pos, state); - } - - @Override - public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { - if (this.particles().isEmpty()) return null; - return EntityBlockBehavior.createTickerHelper(BaseParticleBlockEntity::tick); - } - - public static class Factory implements BlockBehaviorFactory { - @Override - public BlockBehavior create(CustomBlock block, Map arguments) { - List particles = ResourceConfigUtils.parseConfigAsList(arguments.getOrDefault("particles", List.of()), ParticleData::fromMap); - boolean inWall = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("in-wall", false), "in-wall"); - return new ParticleBlockBehavior(block, particles, inWall); - } - } - - public record ParticleData(Key particle, Vec3d locationOffset, Vec3d offset, int count, double speed) { - - public static ParticleData fromMap(Map arguments) { - Key particle = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("type"), "warning.config.block.behavior.particle.missing_type")); - Vec3d locationOffset = ResourceConfigUtils.getAsVec3d(arguments.get("location-offset"), "location-offset"); - Vec3d offset = ResourceConfigUtils.getAsVec3d(arguments.get("offset"), "offset"); - int count = ResourceConfigUtils.getAsInt(arguments.getOrDefault("count", 1), "count"); - double speed = ResourceConfigUtils.getAsDouble(arguments.get("speed"), "speed"); - return new ParticleData(particle, locationOffset, offset, count, speed); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java new file mode 100644 index 000000000..d52bda8a4 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java @@ -0,0 +1,64 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; +import net.momirealms.craftengine.bukkit.block.entity.SimpleParticleBlockEntity; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.particle.ParticleConfig; + +import java.util.List; +import java.util.Map; + +public class SimpleParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { + public static final Factory FACTORY = new Factory(); + public final ParticleConfig[] particles; + public final int tickInterval; + + public SimpleParticleBlockBehavior(CustomBlock customBlock, ParticleConfig[] particles, int tickInterval) { + super(customBlock); + this.particles = particles; + this.tickInterval = tickInterval; + } + + public ParticleConfig[] particles() { + return this.particles; + } + + public int tickInterval() { + return tickInterval; + } + + @Override + public BlockEntityType blockEntityType() { + return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.SIMPLE_PARTICLE); + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { + return new SimpleParticleBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + if (this.particles.length == 0) return null; + return EntityBlockBehavior.createTickerHelper(SimpleParticleBlockEntity::tick); + } + + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + List particles = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "particles", "particle"), ParticleConfig::fromMap$blockEntity); + int tickInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("tick-interval", 10), "tick-interval"); + return new SimpleParticleBlockBehavior(block, particles.toArray(new ParticleConfig[0]), tickInterval); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java new file mode 100644 index 000000000..84a2d19fb --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java @@ -0,0 +1,78 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; +import net.momirealms.craftengine.bukkit.block.entity.WallTorchParticleBlockEntity; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.particle.ParticleConfig; + +import java.util.List; +import java.util.Map; + +public class WallTorchParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { + public static final Factory FACTORY = new Factory(); + public final ParticleConfig[] particles; + public final int tickInterval; + public final Property facingProperty; + + public WallTorchParticleBlockBehavior(CustomBlock customBlock, ParticleConfig[] particles, int tickInterval, Property facingProperty) { + super(customBlock); + this.particles = particles; + this.tickInterval = tickInterval; + this.facingProperty = facingProperty; + } + + public ParticleConfig[] particles() { + return this.particles; + } + + public int tickInterval() { + return tickInterval; + } + + public Property facingProperty() { + return facingProperty; + } + + @Override + public BlockEntityType blockEntityType() { + return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.WALL_TORCH_PARTICLE); + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { + return new WallTorchParticleBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + if (this.particles.length == 0) return null; + return EntityBlockBehavior.createTickerHelper(WallTorchParticleBlockEntity::tick); + } + + public static class Factory implements BlockBehaviorFactory { + @SuppressWarnings("unchecked") + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + List particles = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "particles", "particle"), ParticleConfig::fromMap$blockEntity); + int tickInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("tick-interval", 10), "tick-interval"); + Property directionProperty = (Property) block.getProperty("facing"); + if (directionProperty == null) { + throw new LocalizedResourceConfigException("warning.config.block.behavior.wall_torch_particle.missing_facing"); + } + return new WallTorchParticleBlockBehavior(block, particles.toArray(new ParticleConfig[0]), tickInterval, directionProperty); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/AbstractAnimateTickBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/AbstractAnimateTickBlockEntity.java new file mode 100644 index 000000000..90f863944 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/AbstractAnimateTickBlockEntity.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.world.BlockPos; + +public abstract class AbstractAnimateTickBlockEntity extends BlockEntity { + protected int tickCount; + + public AbstractAnimateTickBlockEntity(BlockEntityType type, BlockPos pos, ImmutableBlockState blockState) { + super(type, pos, blockState); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java deleted file mode 100644 index fb8d63c83..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.entity; - -import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.entity.BlockEntity; -import net.momirealms.craftengine.core.block.entity.BlockEntityType; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.CEWorld; -import net.momirealms.craftengine.core.world.World; - -public abstract class BaseParticleBlockEntity extends BlockEntity { - protected final ParticleBlockBehavior behavior; - protected int tickCount; - - public BaseParticleBlockEntity(BlockEntityType type, BlockPos pos, ImmutableBlockState blockState) { - super(type, pos, blockState); - this.behavior = super.blockState.behavior().getAs(ParticleBlockBehavior.class).orElseThrow(); - } - - public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) { - particle.tickCount++; - if (particle.tickCount % 10 != 0) return; - particle.animateTick(state, ceWorld.world(), blockPos); - } - - public abstract void animateTick(ImmutableBlockState state, World level, BlockPos pos); -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java index 75c449fa9..7c0456145 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -6,6 +6,6 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; public class BukkitBlockEntityTypes extends BlockEntityTypes { public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); - public static final BlockEntityType PARTICLE = register(BlockEntityTypeKeys.PARTICLE, ParticleBlockEntity::new); - public static final BlockEntityType WALL_PARTICLE = register(BlockEntityTypeKeys.WALL_PARTICLE, WallParticleBlockEntity::new); + public static final BlockEntityType SIMPLE_PARTICLE = register(BlockEntityTypeKeys.SIMPLE_PARTICLE, SimpleParticleBlockEntity::new); + public static final BlockEntityType WALL_TORCH_PARTICLE = register(BlockEntityTypeKeys.WALL_TORCH_PARTICLE, WallTorchParticleBlockEntity::new); } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java deleted file mode 100644 index 485eb0c85..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/ParticleBlockEntity.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.entity; - -import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.World; - -public class ParticleBlockEntity extends BaseParticleBlockEntity { - - public ParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) { - super(BukkitBlockEntityTypes.PARTICLE, pos, blockState); - } - - @Override - public void animateTick(ImmutableBlockState state, World level, BlockPos pos) { - for (ParticleBlockBehavior.ParticleData particle : behavior.particles()) { - Vec3d location = particle.locationOffset().add(pos.x(), pos.y(), pos.z()); - level.spawnParticle( - location, - particle.particle(), - particle.count(), - particle.offset().x(), - particle.offset().y(), - particle.offset().z(), - particle.speed(), - null, null - ); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleParticleBlockEntity.java new file mode 100644 index 000000000..30acd0be6 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleParticleBlockEntity.java @@ -0,0 +1,45 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.bukkit.block.behavior.SimpleParticleBlockBehavior; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; +import net.momirealms.craftengine.core.plugin.context.SimpleContext; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.particle.ParticleConfig; + +public class SimpleParticleBlockEntity extends AbstractAnimateTickBlockEntity { + private final SimpleParticleBlockBehavior behavior; + private final Context context = SimpleContext.of(ContextHolder.empty()); + + public SimpleParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.SIMPLE_PARTICLE, pos, blockState); + this.behavior = blockState.behavior().getAs(SimpleParticleBlockBehavior.class).orElseThrow(); + } + + public void animateTick(ImmutableBlockState state, World level, BlockPos pos) { + for (ParticleConfig particle : this.behavior.particles) { + Vec3d location = new Vec3d(super.pos.x() + particle.x.getDouble(context), super.pos.y() + particle.y.getDouble(context), super.pos.z() + particle.z.getDouble(context)); + level.spawnParticle( + location, + particle.particleType, + particle.count.getInt(context), + particle.xOffset.getDouble(context), + particle.yOffset.getDouble(context), + particle.zOffset.getDouble(context), + particle.speed.getDouble(context), + particle.particleData, + context + ); + } + } + + public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, SimpleParticleBlockEntity particle) { + particle.tickCount++; + if (particle.tickCount % particle.behavior.tickInterval != 0) return; + particle.animateTick(state, ceWorld.world(), blockPos); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java deleted file mode 100644 index 7de011fb9..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallParticleBlockEntity.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.entity; - -import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.HorizontalDirection; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.World; - -public class WallParticleBlockEntity extends BaseParticleBlockEntity { - - public WallParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) { - super(BukkitBlockEntityTypes.WALL_PARTICLE, pos, blockState); - } - - @Override - public void animateTick(ImmutableBlockState state, World level, BlockPos pos) { - Direction direction = null; - for (Property property : state.getProperties()) { - if (!property.name().equals("facing")) continue; - if (property.valueClass() == Direction.class) { - direction = (Direction) state.get(property); - break; - } else if (property.valueClass() == HorizontalDirection.class) { - direction = ((HorizontalDirection) state.get(property)).toDirection(); - break; - } - } - if (direction != null) { - direction = direction.opposite(); - } - for (ParticleBlockBehavior.ParticleData particle : behavior.particles()) { - Vec3d location = particle.locationOffset().add(pos.x(), pos.y(), pos.z()); - if (direction != null) { - location = location.add(0.27 * direction.stepX(), 0.22, 0.27 * direction.stepZ()); - } - level.spawnParticle( - location, - particle.particle(), - particle.count(), - particle.offset().x(), - particle.offset().y(), - particle.offset().z(), - particle.speed(), - null, null - ); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java new file mode 100644 index 000000000..f571661e8 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java @@ -0,0 +1,55 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import net.momirealms.craftengine.bukkit.block.behavior.WallTorchParticleBlockBehavior; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; +import net.momirealms.craftengine.core.plugin.context.SimpleContext; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.particle.ParticleConfig; + +public class WallTorchParticleBlockEntity extends AbstractAnimateTickBlockEntity { + private final WallTorchParticleBlockBehavior behavior; + private final Context context = SimpleContext.of(ContextHolder.empty()); + + public WallTorchParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.WALL_TORCH_PARTICLE, pos, blockState); + this.behavior = blockState.behavior().getAs(WallTorchParticleBlockBehavior.class).orElseThrow(); + } + + public void animateTick(ImmutableBlockState state, World level, BlockPos pos) { + HorizontalDirection direction = state.get(this.behavior.facingProperty); + if (direction == null) return; + Vec3d center = Vec3d.atCenterOf(pos); + HorizontalDirection opposite = direction.opposite(); + for (ParticleConfig particle : this.behavior.particles) { + Vec3d location = new Vec3d( + center.x() + particle.x.getDouble(context) * opposite.stepX(), + center.y() + particle.y.getDouble(context), + center.z() + particle.z.getDouble(context) * opposite.stepZ() + ); + level.spawnParticle( + location, + particle.particleType, + particle.count.getInt(context), + particle.xOffset.getDouble(context), + particle.yOffset.getDouble(context), + particle.zOffset.getDouble(context), + particle.speed.getDouble(context), + particle.particleData, + context + ); + } + } + + public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, WallTorchParticleBlockEntity particle) { + particle.tickCount++; + if (particle.tickCount % particle.behavior.tickInterval != 0) return; + particle.animateTick(state, ceWorld.world(), blockPos); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 28b39712d..2624bc78d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -106,11 +106,11 @@ public class BukkitWorld implements World { } @Override - public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @Nullable Context context) { + public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context) { Particle particleType = ParticleUtils.getParticle(particle); if (particleType == null) return; org.bukkit.World platformWorld = platformWorld(); - platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null || context == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z())); + platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z())); } @Override diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml index 1ee3349c5..a83504d42 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -70,12 +70,18 @@ blocks: support-types: - center - type: liquid_flowable_block - - type: particle_block + - type: simple_particle_block + tick-interval: 10 particles: - - type: smoke - location-offset: 0.5,0.7,0.5 - - type: flame - location-offset: 0.5,0.7,0.5 + - particle: smoke + x: 0.5 + y: 0.7 + z: 0.5 + - particle: dust + color: 138,43,226 + x: 0.5 + y: 0.7 + z: 0.5 default:amethyst_wall_torch: loot: template: default:loot_table/basic @@ -94,13 +100,17 @@ blocks: behavior: - type: directional_attached_block - type: liquid_flowable_block - - type: particle_block - in-wall: true + - type: wall_torch_particle_block particles: - - type: smoke - location-offset: 0.5,0.7,0.5 - - type: flame - location-offset: 0.5,0.7,0.5 + - particle: smoke + x: 0.27 + y: 0.42 + z: 0.27 + - particle: dust + color: 138,43,226 + x: 0.27 + y: 0.42 + z: 0.27 states: properties: facing: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index d57a7cc15..4fa46057b 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -320,7 +320,7 @@ warning.config.block.behavior.grass.missing_feature: "Issue found in fil warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' argument for 'change_over_time_block' behavior." warning.config.block.behavior.surface_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'surface_attached_block' behavior." -warning.config.block.behavior.particle.missing_type: "Issue found in file - The block '' is missing the required 'type' argument for 'particle_block' behavior." +warning.config.block.behavior.wall_torch_particle.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'wall_torch_particle_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 902056cd9..8a5413621 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -314,7 +314,7 @@ warning.config.block.behavior.grass.missing_feature: "在文件 warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.change_over_time.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 参数" warning.config.block.behavior.surface_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'surface_attached_block' 行为缺少必需的 'facing' 属性" -warning.config.block.behavior.particle.missing_type: "在文件 发现问题 - 配置项 '' 的 'particle' 段落缺少必需的 'type' 参数" +warning.config.block.behavior.wall_torch_particle.missing_facing: "在文件 发现问题 - 配置项 '' 的 'wall_torch_particle_block' 行为缺少必需的 'facing' 属性" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java index 6bc44620d..720da9e20 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -7,6 +7,6 @@ public final class BlockEntityTypeKeys { public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite"); public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); - public static final Key PARTICLE = Key.of("craftengine:particle"); - public static final Key WALL_PARTICLE = Key.of("craftengine:wall_particle"); + public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle"); + public static final Key WALL_TORCH_PARTICLE = Key.of("craftengine:wall_torch_particle"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java index c97d47bd3..4893d19f8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java @@ -1,115 +1,25 @@ package net.momirealms.craftengine.core.plugin.context.function; -import net.momirealms.craftengine.core.block.BlockStateWrapper; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; -import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; -import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.util.Color; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.LazyReference; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.Position; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; -import net.momirealms.craftengine.core.world.particle.*; +import net.momirealms.craftengine.core.world.particle.ParticleConfig; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Supplier; public class ParticleFunction extends AbstractConditionalFunction { - public static final Map, ParticleData>> DATA_TYPES = new HashMap<>(); + private final ParticleConfig config; - static { - registerParticleData(map -> new BlockStateData( - LazyReference.lazyReference(new Supplier<>() { - final String blockState = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("block-state"), "warning.config.function.particle.missing_block_state"); - @Override - public BlockStateWrapper get() { - return CraftEngine.instance().blockManager().createBlockState(this.blockState); - } - })), - ParticleTypes.BLOCK, ParticleTypes.FALLING_DUST, ParticleTypes.DUST_PILLAR, ParticleTypes.BLOCK_CRUMBLE, ParticleTypes.BLOCK_MARKER); - registerParticleData(map -> new ColorData( - Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(","))), - ParticleTypes.ENTITY_EFFECT, ParticleTypes.TINTED_LEAVES); - registerParticleData(map -> new JavaTypeData( - ResourceConfigUtils.getAsFloat(map.get("charge"), "charge")), - ParticleTypes.SCULK_CHARGE); - registerParticleData(map -> new JavaTypeData( - ResourceConfigUtils.getAsInt(map.get("shriek"), "shriek")), - ParticleTypes.SHRIEK); - registerParticleData(map -> new DustData( - Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")), - ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")), - ParticleTypes.DUST); - registerParticleData(map -> new DustTransitionData( - Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("from"), "warning.config.function.particle.missing_from").split(",")), - Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("to"), "warning.config.function.particle.missing_to").split(",")), - ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")), - ParticleTypes.DUST_COLOR_TRANSITION); - registerParticleData(map -> new ItemStackData( - LazyReference.lazyReference(new Supplier<>() { - final Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("item"), "warning.config.function.particle.missing_item")); - @Override - public Item get() { - return CraftEngine.instance().itemManager().createWrappedItem(this.itemId, null); - } - }) - ), - ParticleTypes.ITEM); - registerParticleData(map -> new VibrationData( - NumberProviders.fromObject(map.getOrDefault("target-x", 0)), - NumberProviders.fromObject(map.getOrDefault("target-y", 0)), - NumberProviders.fromObject(map.getOrDefault("target-z", 0)), - NumberProviders.fromObject(map.getOrDefault("arrival-time", 10))), - ParticleTypes.VIBRATION); - registerParticleData(map -> new TrailData( - NumberProviders.fromObject(map.getOrDefault("target-x", 0)), - NumberProviders.fromObject(map.getOrDefault("target-y", 0)), - NumberProviders.fromObject(map.getOrDefault("target-z", 0)), - Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")), - NumberProviders.fromObject(map.getOrDefault("duration", 10))), - ParticleTypes.TRAIL); - } - - public static void registerParticleData(java.util.function.Function, ParticleData> function, Key... types) { - for (Key type : types) { - DATA_TYPES.put(type, function); - } - } - - private final Key particleType; - private final NumberProvider x; - private final NumberProvider y; - private final NumberProvider z; - private final NumberProvider count; - private final NumberProvider xOffset; - private final NumberProvider yOffset; - private final NumberProvider zOffset; - private final NumberProvider speed; - private final ParticleData particleData; - - public ParticleFunction(Key particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count, - NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData, List> predicates) { + public ParticleFunction(ParticleConfig config, List> predicates) { super(predicates); - this.particleType = particleType; - this.count = count; - this.xOffset = xOffset; - this.yOffset = yOffset; - this.zOffset = zOffset; - this.speed = speed; - this.x = x; - this.y = y; - this.z = z; - this.particleData = particleData; + this.config = config; } @Override @@ -117,8 +27,8 @@ public class ParticleFunction extends AbstractConditionalFu Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - Position position = new Vec3d(this.x.getDouble(ctx), this.y.getDouble(ctx), this.z.getDouble(ctx)); - world.spawnParticle(position, this.particleType, this.count.getInt(ctx), this.xOffset.getDouble(ctx), this.yOffset.getDouble(ctx), this.zOffset.getDouble(ctx), this.speed.getDouble(ctx), this.particleData, ctx); + Position position = new Vec3d(config.x.getDouble(ctx), config.y.getDouble(ctx), config.z.getDouble(ctx)); + world.spawnParticle(position, config.particleType, config.count.getInt(ctx), config.xOffset.getDouble(ctx), config.yOffset.getDouble(ctx), config.zOffset.getDouble(ctx), config.speed.getDouble(ctx), config.particleData, ctx); } } @@ -135,17 +45,7 @@ public class ParticleFunction extends AbstractConditionalFu @Override public Function create(Map arguments) { - Key particleType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("particle"), "warning.config.function.particle.missing_particle")); - NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); - NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); - NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); - NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1)); - NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0)); - NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0)); - NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0)); - NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0)); - return new ParticleFunction<>(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, - Optional.ofNullable(ParticleFunction.DATA_TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null), getPredicates(arguments)); + return new ParticleFunction<>(ParticleConfig.fromMap$function(arguments), getPredicates(arguments)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/HorizontalDirection.java b/core/src/main/java/net/momirealms/craftengine/core/util/HorizontalDirection.java index cc8143e12..d0a6ec27c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/HorizontalDirection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/HorizontalDirection.java @@ -3,10 +3,26 @@ package net.momirealms.craftengine.core.util; import org.jetbrains.annotations.NotNull; public enum HorizontalDirection { - NORTH, - SOUTH, - WEST, - EAST; + NORTH(0, -1), + SOUTH(0, 1), + WEST(-1, 0), + EAST(1, 0); + + private final int adjX; + private final int adjZ; + + HorizontalDirection(int adjX, int adjZ) { + this.adjX = adjX; + this.adjZ = adjZ; + } + + public int stepX() { + return this.adjX; + } + + public int stepZ() { + return this.adjZ; + } public Direction toDirection() { return switch (this) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/World.java b/core/src/main/java/net/momirealms/craftengine/core/world/World.java index 40dd01f58..bc6181c8b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/World.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/World.java @@ -58,7 +58,7 @@ public interface World { void levelEvent(int id, BlockPos pos, int data); - void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @Nullable Context context); + void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context); long time(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java new file mode 100644 index 000000000..b96863555 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java @@ -0,0 +1,101 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; + +public class ParticleConfig { + public final Key particleType; + public final NumberProvider x; + public final NumberProvider y; + public final NumberProvider z; + public final NumberProvider count; + public final NumberProvider xOffset; + public final NumberProvider yOffset; + public final NumberProvider zOffset; + public final NumberProvider speed; + public final ParticleData particleData; + + public ParticleConfig(Key particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count, NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData) { + this.particleType = particleType; + this.x = x; + this.y = y; + this.z = z; + this.count = count; + this.xOffset = xOffset; + this.yOffset = yOffset; + this.zOffset = zOffset; + this.speed = speed; + this.particleData = particleData; + } + + public static ParticleConfig fromMap$function(Map arguments) { + Key particleType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("particle"), "warning.config.function.particle.missing_particle")); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1)); + NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0)); + NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0)); + NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0)); + NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0)); + return new ParticleConfig(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null)); + } + + public static ParticleConfig fromMap$blockEntity(Map arguments) { + Key particleType = Key.of(arguments.getOrDefault("particle", "flame").toString()); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", 0)); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", 0)); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", 0)); + NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1)); + NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0)); + NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0)); + NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0)); + NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0)); + return new ParticleConfig(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null)); + } + + public Key particleType() { + return particleType; + } + + public NumberProvider x() { + return x; + } + + public NumberProvider y() { + return y; + } + + public NumberProvider z() { + return z; + } + + public NumberProvider count() { + return count; + } + + public NumberProvider xOffset() { + return xOffset; + } + + public NumberProvider yOffset() { + return yOffset; + } + + public NumberProvider zOffset() { + return zOffset; + } + + public NumberProvider speed() { + return speed; + } + + public ParticleData particleData() { + return particleData; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleDataTypes.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleDataTypes.java new file mode 100644 index 000000000..094a83daa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleDataTypes.java @@ -0,0 +1,77 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.util.Color; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.LazyReference; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public final class ParticleDataTypes { + public static final Map, ParticleData>> TYPES = new HashMap<>(); + + static { + registerParticleData(map -> new BlockStateData( + LazyReference.lazyReference(new Supplier<>() { + final String blockState = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("block-state"), "warning.config.function.particle.missing_block_state"); + @Override + public BlockStateWrapper get() { + return CraftEngine.instance().blockManager().createBlockState(this.blockState); + } + })), + ParticleTypes.BLOCK, ParticleTypes.FALLING_DUST, ParticleTypes.DUST_PILLAR, ParticleTypes.BLOCK_CRUMBLE, ParticleTypes.BLOCK_MARKER); + registerParticleData(map -> new ColorData( + Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(","))), + ParticleTypes.ENTITY_EFFECT, ParticleTypes.TINTED_LEAVES); + registerParticleData(map -> new JavaTypeData( + ResourceConfigUtils.getAsFloat(map.get("charge"), "charge")), + ParticleTypes.SCULK_CHARGE); + registerParticleData(map -> new JavaTypeData( + ResourceConfigUtils.getAsInt(map.get("shriek"), "shriek")), + ParticleTypes.SHRIEK); + registerParticleData(map -> new DustData( + Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")), + ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")), + ParticleTypes.DUST); + registerParticleData(map -> new DustTransitionData( + Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("from"), "warning.config.function.particle.missing_from").split(",")), + Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("to"), "warning.config.function.particle.missing_to").split(",")), + ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")), + ParticleTypes.DUST_COLOR_TRANSITION); + registerParticleData(map -> new ItemStackData( + LazyReference.lazyReference(new Supplier<>() { + final Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("item"), "warning.config.function.particle.missing_item")); + @Override + public Item get() { + return CraftEngine.instance().itemManager().createWrappedItem(this.itemId, null); + } + }) + ), + ParticleTypes.ITEM); + registerParticleData(map -> new VibrationData( + NumberProviders.fromObject(map.getOrDefault("target-x", 0)), + NumberProviders.fromObject(map.getOrDefault("target-y", 0)), + NumberProviders.fromObject(map.getOrDefault("target-z", 0)), + NumberProviders.fromObject(map.getOrDefault("arrival-time", 10))), + ParticleTypes.VIBRATION); + registerParticleData(map -> new TrailData( + NumberProviders.fromObject(map.getOrDefault("target-x", 0)), + NumberProviders.fromObject(map.getOrDefault("target-y", 0)), + NumberProviders.fromObject(map.getOrDefault("target-z", 0)), + Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")), + NumberProviders.fromObject(map.getOrDefault("duration", 10))), + ParticleTypes.TRAIL); + } + + public static void registerParticleData(java.util.function.Function, ParticleData> function, Key... types) { + for (Key type : types) { + TYPES.put(type, function); + } + } +} From cb5865e7fcad3f50dafcd172630303c31f4c57c6 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 21:14:22 +0800 Subject: [PATCH 125/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=8B=E6=AC=A1?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=89=80=E9=9C=80=E7=9A=84=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/blocks/amethyst_torch.yml | 6 +- .../configuration/blocks/table_lamp.yml | 18 ++- .../configuration/blocks/topaz_ore.yml | 6 +- .../models/block/custom/mushroom_1.json | 74 +++++++++ .../models/block/custom/mushroom_2.json | 151 ++++++++++++++++++ .../models/block/custom/mushroom_3.json | 86 ++++++++++ .../textures/block/custom/mushroom.png | Bin 0 -> 770 bytes .../textures/item/custom/table_lamp_on.png | Bin 0 -> 331 bytes .../core/pack/AbstractPackManager.java | 1 + 9 files changed, 332 insertions(+), 10 deletions(-) create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/mushroom.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/table_lamp_on.png diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml index a83504d42..84b5e29e0 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -1,7 +1,7 @@ items: default:amethyst_torch: material: nether_brick - custom-model-data: 3019 + custom-model-data: 3020 data: item-name: model: @@ -20,7 +20,7 @@ items: block: default:amethyst_wall_torch default:amethyst_standing_torch: material: nether_brick - custom-model-data: 3020 + custom-model-data: 3021 data: item-name: model: @@ -32,7 +32,7 @@ items: torch: minecraft:block/custom/amethyst_torch default:amethyst_wall_torch: material: nether_brick - custom-model-data: 3021 + custom-model-data: 3022 data: item-name: model: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml index 09671ba0e..8802410b6 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml @@ -60,21 +60,21 @@ items: east_on: state: barrier entity-renderer: - item: default:table_lamp + item: default:table_lamp_on north_on: state: barrier entity-renderer: - item: default:table_lamp + item: default:table_lamp_on yaw: -90 south_on: state: barrier entity-renderer: - item: default:table_lamp + item: default:table_lamp_on yaw: 90 west_on: state: barrier entity-renderer: - item: default:table_lamp + item: default:table_lamp_on yaw: 180 variants: facing=east,lit=false: @@ -109,6 +109,16 @@ items: id: 19 settings: luminance: 15 + default:table_lamp_on: + material: nether_brick + custom-model-data: 3016 + model: + type: minecraft:model + path: minecraft:item/custom/table_lamp_on + generation: + parent: minecraft:item/custom/table_lamp + textures: + "0": "minecraft:item/custom/table_lamp_on" recipes: default:table_lamp: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index eda1e9f16..067cc0a09 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -1,7 +1,7 @@ items: default:topaz_ore: material: nether_brick - custom-model-data: 3016 + custom-model-data: 3017 data: item-name: model: @@ -14,7 +14,7 @@ items: block: default:topaz_ore default:deepslate_topaz_ore: material: nether_brick - custom-model-data: 3017 + custom-model-data: 3018 data: item-name: model: @@ -27,7 +27,7 @@ items: block: default:deepslate_topaz_ore default:topaz: material: nether_brick - custom-model-data: 3018 + custom-model-data: 3019 settings: anvil-repair-item: - target: diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json new file mode 100644 index 000000000..f70b470fe --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json @@ -0,0 +1,74 @@ +{ + "texture_size": [32, 32], + "textures": { + "0": "block/custom/mushroom" + }, + "elements": [ + { + "from": [9.5, 3, 11.5], + "to": [14.5, 3, 17.5], + "rotation": {"angle": 0, "axis": "y", "origin": [12, 3, 14.5]}, + "faces": { + "north": {"uv": [2.5, 0, 0, 0], "texture": "#0"}, + "east": {"uv": [3, 0, 0, 0], "texture": "#0"}, + "south": {"uv": [2.5, 0, 0, 0], "texture": "#0"}, + "west": {"uv": [3, 0, 0, 0], "texture": "#0"}, + "up": {"uv": [8, 0, 5.5, 3], "texture": "#0"}, + "down": {"uv": [8, 3, 5.5, 0], "texture": "#0"} + } + }, + { + "from": [2.5, 11, 10.5], + "to": [7.5, 11, 16.5], + "rotation": {"angle": -22.5, "axis": "x", "origin": [5, 11, 16.5]}, + "faces": { + "north": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "east": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "south": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "west": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "up": {"uv": [5.5, 0, 8, 3], "texture": "#0"}, + "down": {"uv": [5.5, 3, 8, 0], "texture": "#0"} + } + }, + { + "from": [3, 4, 14], + "to": [8, 5, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [3, 4, 14]}, + "faces": { + "north": {"uv": [0, 9, 2.5, 9.5], "texture": "#0"}, + "east": {"uv": [2.5, 11.5, 3.5, 12], "texture": "#0"}, + "south": {"uv": [0, 9.5, 2.5, 10], "texture": "#0"}, + "west": {"uv": [2.5, 11, 3.5, 11.5], "texture": "#0"}, + "up": {"uv": [2.5, 11, 0, 10], "texture": "#0"}, + "down": {"uv": [2.5, 11, 0, 12], "texture": "#0"} + } + }, + { + "from": [6, 7, 13], + "to": [13, 9, 17], + "rotation": {"angle": 0, "axis": "y", "origin": [6, 7, 13]}, + "faces": { + "north": {"uv": [0, 5, 3.5, 6], "texture": "#0"}, + "east": {"uv": [3.5, 0, 5.5, 1], "texture": "#0"}, + "south": {"uv": [0, 4, 3.5, 5], "texture": "#0"}, + "west": {"uv": [3.5, 1, 5.5, 2], "texture": "#0"}, + "up": {"uv": [3.5, 2, 0, 0], "texture": "#0"}, + "down": {"uv": [3.5, 2, 0, 4], "texture": "#0"} + } + } + ], + "display": { + "ground": { + "translation": [0, 2.25, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -150, 0], + "translation": [3.75, -2.5, 0] + }, + "fixed": { + "translation": [0, 0, -16.25], + "scale": [2, 2, 2] + } + } +} \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json new file mode 100644 index 000000000..5c75b3c6e --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json @@ -0,0 +1,151 @@ +{ + "textures": { + "0": "block/custom/mushroom" + }, + "elements": [ + { + "from": [8.5, 3, 11.5], + "to": [13.5, 3, 17.5], + "rotation": {"angle": 0, "axis": "y", "origin": [10, 4, 13]}, + "faces": { + "north": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "east": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "south": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "west": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "up": {"uv": [8, 0, 10.5, 3], "texture": "#0"}, + "down": {"uv": [8, 3, 10.5, 0], "texture": "#0"} + } + }, + { + "from": [9.5, 11, 10.5], + "to": [14.5, 11, 16.5], + "rotation": {"angle": -22.5, "axis": "x", "origin": [12, 11, 16.5]}, + "faces": { + "north": {"uv": [2.5, 0, 0, 0], "texture": "#0"}, + "east": {"uv": [3, 0, 0, 0], "texture": "#0"}, + "south": {"uv": [2.5, 0, 0, 0], "texture": "#0"}, + "west": {"uv": [3, 0, 0, 0], "texture": "#0"}, + "up": {"uv": [8, 0, 5.5, 3], "texture": "#0"}, + "down": {"uv": [8, 3, 5.5, 0], "texture": "#0"} + } + }, + { + "from": [1.5, 7, 10.5], + "to": [6.5, 7, 16.5], + "rotation": {"angle": 0, "axis": "y", "origin": [3, 8, 12]}, + "faces": { + "north": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "east": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "south": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "west": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "up": {"uv": [5.5, 0, 8, 3], "texture": "#0"}, + "down": {"uv": [5.5, 3, 8, 0], "texture": "#0"} + } + }, + { + "from": [5.5, 9, 14.5], + "to": [5.5, 11, 16.5], + "rotation": {"angle": 45, "axis": "y", "origin": [5.5, 10, 15.5]}, + "faces": { + "north": {"uv": [0, 0, 0, 1], "texture": "#0"}, + "east": {"uv": [2.5, 10, 3.5, 11], "texture": "#0"}, + "south": {"uv": [0, 0, 0, 1], "texture": "#0"}, + "west": {"uv": [3.5, 10, 2.5, 11], "texture": "#0"}, + "up": {"uv": [0, 0, 0, 1], "texture": "#0"}, + "down": {"uv": [0, 0, 0, 1], "texture": "#0"} + } + }, + { + "from": [4.5, 2, 14.5], + "to": [4.5, 4, 16.5], + "rotation": {"angle": -45, "axis": "y", "origin": [4.5, 3, 15.5]}, + "faces": { + "north": {"uv": [0, 0, 0, 1], "texture": "#0"}, + "east": {"uv": [2.5, 10, 3.5, 11], "texture": "#0"}, + "south": {"uv": [0, 0, 0, 1], "texture": "#0"}, + "west": {"uv": [3.5, 10, 2.5, 11], "texture": "#0"}, + "up": {"uv": [0, 0, 0, 1], "texture": "#0"}, + "down": {"uv": [0, 0, 0, 1], "texture": "#0"} + } + }, + { + "from": [9.5, 6, 14.5], + "to": [11.5, 8, 16.5], + "rotation": {"angle": 0, "axis": "y", "origin": [10, 6, 16]}, + "faces": { + "north": {"uv": [3.5, 2, 4.5, 3], "texture": "#0"}, + "east": {"uv": [5.5, 3, 4.5, 4], "texture": "#0"}, + "south": {"uv": [3.5, 4, 4.5, 5], "texture": "#0"}, + "west": {"uv": [4.5, 3, 5.5, 4], "texture": "#0"}, + "up": {"uv": [3.5, 3, 4.5, 4], "texture": "#0"}, + "down": {"uv": [4.5, 2, 5.5, 3], "texture": "#0"} + } + }, + { + "from": [7, 7, 11], + "to": [14, 9, 15], + "rotation": {"angle": 0, "axis": "y", "origin": [7, 7, 11]}, + "faces": { + "north": {"uv": [0, 5, 3.5, 6], "texture": "#0"}, + "east": {"uv": [3.5, 0, 5.5, 1], "texture": "#0"}, + "south": {"uv": [0, 4, 3.5, 5], "texture": "#0"}, + "west": {"uv": [3.5, 1, 5.5, 2], "texture": "#0"}, + "up": {"uv": [3.5, 2, 0, 0], "texture": "#0"}, + "down": {"uv": [3.5, 2, 0, 4], "texture": "#0"} + } + }, + { + "from": [7, 13, 14], + "to": [12, 14, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [7, 13, 14]}, + "faces": { + "north": {"uv": [0, 9, 2.5, 9.5], "texture": "#0"}, + "east": {"uv": [2.5, 11.5, 3.5, 12], "texture": "#0"}, + "south": {"uv": [0, 9.5, 2.5, 10], "texture": "#0"}, + "west": {"uv": [2.5, 11, 3.5, 11.5], "texture": "#0"}, + "up": {"uv": [2.5, 11, 0, 10], "texture": "#0"}, + "down": {"uv": [2.5, 11, 0, 12], "texture": "#0"} + } + }, + { + "from": [3, 11, 13], + "to": [8, 12, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [3, 11, 13]}, + "faces": { + "north": {"uv": [0, 15.5, 2.5, 16], "texture": "#0"}, + "east": {"uv": [2.5, 15, 4, 15.5], "texture": "#0"}, + "south": {"uv": [0, 15, 2.5, 15.5], "texture": "#0"}, + "west": {"uv": [2.5, 15.5, 4, 16], "texture": "#0"}, + "up": {"uv": [2.5, 13.5, 0, 12], "texture": "#0"}, + "down": {"uv": [2.5, 13.5, 0, 15], "texture": "#0"} + } + }, + { + "from": [2, 4, 13], + "to": [7, 5, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [2, 4, 13]}, + "faces": { + "north": {"uv": [0, 15.5, 2.5, 16], "texture": "#0"}, + "east": {"uv": [2.5, 15, 4, 15.5], "texture": "#0"}, + "south": {"uv": [0, 15, 2.5, 15.5], "texture": "#0"}, + "west": {"uv": [2.5, 15.5, 4, 16], "texture": "#0"}, + "up": {"uv": [2.5, 13.5, 0, 12], "texture": "#0"}, + "down": {"uv": [2.5, 13.5, 0, 15], "texture": "#0"} + } + } + ], + "display": { + "ground": { + "translation": [0, 2.25, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -150, 0], + "translation": [3.75, -2.5, 0] + }, + "fixed": { + "translation": [0, 0, -16], + "scale": [2, 2, 2] + } + } +} \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json new file mode 100644 index 000000000..79459425c --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json @@ -0,0 +1,86 @@ +{ + "textures": { + "0": "block/custom/mushroom" + }, + "elements": [ + { + "from": [8.5, 13, 11.5], + "to": [13.5, 13, 17.5], + "rotation": {"angle": -22.5, "axis": "x", "origin": [11, 13, 14.5]}, + "faces": { + "north": {"uv": [2.5, 0, 0, 0], "texture": "#0"}, + "east": {"uv": [3, 0, 0, 0], "texture": "#0"}, + "south": {"uv": [2.5, 0, 0, 0], "texture": "#0"}, + "west": {"uv": [3, 0, 0, 0], "texture": "#0"}, + "up": {"uv": [8, 0, 5.5, 3], "texture": "#0"}, + "down": {"uv": [8, 3, 5.5, 0], "texture": "#0"} + } + }, + { + "from": [8.5, 3, 12.5], + "to": [13.5, 3, 18.5], + "rotation": {"angle": -22.5, "axis": "x", "origin": [11, 3, 15.5]}, + "faces": { + "north": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "east": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "south": {"uv": [0, 0, 2.5, 0], "texture": "#0"}, + "west": {"uv": [0, 0, 3, 0], "texture": "#0"}, + "up": {"uv": [8, 0, 10.5, 3], "texture": "#0"}, + "down": {"uv": [8, 3, 10.5, 0], "texture": "#0"} + } + }, + { + "from": [6, 7, 12], + "to": [13, 9, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [6, 7, 12]}, + "faces": { + "north": {"uv": [0, 5, 3.5, 6], "texture": "#0"}, + "east": {"uv": [3.5, 0, 5.5, 1], "texture": "#0"}, + "south": {"uv": [0, 4, 3.5, 5], "texture": "#0"}, + "west": {"uv": [3.5, 1, 5.5, 2], "texture": "#0"}, + "up": {"uv": [3.5, 2, 0, 0], "texture": "#0"}, + "down": {"uv": [3.5, 2, 0, 4], "texture": "#0"} + } + }, + { + "from": [3, 11, 14], + "to": [8, 12, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [3, 11, 14]}, + "faces": { + "north": {"uv": [0, 9, 2.5, 9.5], "texture": "#0"}, + "east": {"uv": [2.5, 11.5, 3.5, 12], "texture": "#0"}, + "south": {"uv": [0, 9.5, 2.5, 10], "texture": "#0"}, + "west": {"uv": [2.5, 11, 3.5, 11.5], "texture": "#0"}, + "up": {"uv": [2.5, 11, 0, 10], "texture": "#0"}, + "down": {"uv": [2.5, 11, 0, 12], "texture": "#0"} + } + }, + { + "from": [3, 4, 13], + "to": [8, 5, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [3, 4, 13]}, + "faces": { + "north": {"uv": [0, 15.5, 2.5, 16], "texture": "#0"}, + "east": {"uv": [2.5, 15, 4, 15.5], "texture": "#0"}, + "south": {"uv": [0, 15, 2.5, 15.5], "texture": "#0"}, + "west": {"uv": [2.5, 15.5, 4, 16], "texture": "#0"}, + "up": {"uv": [2.5, 13.5, 0, 12], "texture": "#0"}, + "down": {"uv": [2.5, 13.5, 0, 15], "texture": "#0"} + } + } + ], + "display": { + "ground": { + "translation": [0, 2.25, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -150, 0], + "translation": [3.75, -2.5, 0] + }, + "fixed": { + "translation": [0, 0, -16], + "scale": [2, 2, 2] + } + } +} \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/mushroom.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/mushroom.png new file mode 100644 index 0000000000000000000000000000000000000000..2a5460ef31b050f122facb8754f2d7d80d714d3f GIT binary patch literal 770 zcmV+d1O5DoP)Px%yh%hsR9J=Wm%mFJVHn3h7Y>_pHEEJdPCX%3goLJ1I|LkD#I{&zi%?1_E$FNr zy0k<8fDV~D)Xt`KXtxf6LP{4Mgh~~I(V+xG(HObJrWmj#dL7K2i9brCCX*jHj`!a8 z_&(2b?{m-d3i0rOzM9_I*MLdZ#|Cv{O~X(`(8q`Mw3as`7!MEVS6_VkZzDvl zzMbUSFJg-cf&oYI-iqXqai=n@EGGbnEhfyD46*gGK|QVIc^BOz7-$6*R+Gz>1OQ80 zr_UUeintXS0LfuPa@h6+ie4uREfiEg*^L|xLlNQ1PL{2OS+*8tE8eK0A#4a3nwz?dmP2V+X-=O6 zvAMS&vC+k5D#NbJWlrS&WPnJu_0Kuy9E8$ZPO&5lcivV#{|&^?6Xk!pSgn+cSb`x1 z)oX=fD;5kX08G4kc5IC}OGHJMP-F?GXa5s~AK%j@hpiU+h*LjgW~S!}23mh1*K?)@9Uo_J}4T2%w|?_WrN(*XGGx`gVrQb|*JY6F}eJC|MM z=Yd)x0_M&y?FVXRz}(t?D;|p`4mF_F#$u$K(R$S?{R~96BAZ^f_-pGufDjK4=*dEh z`M@i#K*>3=+I^u2aJ^|0%uUz?#o|NMw#aif*ND`maphCE|PXUk~Jk^mUC!O_=A!s zCdQpNCwCq3bKmf0f^b&dJ|^!3?uPP Date: Sun, 14 Sep 2025 21:41:21 +0800 Subject: [PATCH 126/226] =?UTF-8?q?perf(core):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=93=E7=9A=84=20tick=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/entity/BaseParticleBlockEntity.java | 1 + .../core/util/BlockEntityTickersList.java | 111 ++++++++++++++++++ .../craftengine/core/world/CEWorld.java | 14 ++- 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java index fb8d63c83..9643a2bfd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java @@ -18,6 +18,7 @@ public abstract class BaseParticleBlockEntity extends BlockEntity { } public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) { + if (true) return; particle.tickCount++; if (particle.tickCount % 10 != 0) return; particle.animateTick(state, ceWorld.world(), blockPos); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java b/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java new file mode 100644 index 000000000..124269086 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java @@ -0,0 +1,111 @@ +/** + * This implementation references the BlockEntityTickersList implementation by Winds Studio, + * available at: https://github.com/Winds-Studio/Leaf/blob/b9ebff/leaf-server/src/main/java/org/dreeam/leaf/util/list/BlockEntityTickersList.java + *

+ * This work is licensed under the GNU General Public License v3.0 (GPLv3) + */ +package net.momirealms.craftengine.core.util; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A list for ServerLevel's blockEntityTickers + *

+ * This list behaves identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what + * indexes should be deleted from the list + *

+ * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove, + * since we don't need to resize the array every single remove. + */ +public final class BlockEntityTickersList extends ObjectArrayList { + + private final IntOpenHashSet toRemove = new IntOpenHashSet(); + private int startSearchFromIndex = -1; + + /** + * Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. + */ + public BlockEntityTickersList() { + super(); + } + + /** + * Creates a new array list and fills it with a given collection. + * + * @param c a collection that will be used to fill the array list. + */ + public BlockEntityTickersList(final Collection c) { + super(c); + } + + /** + * Marks an entry as removed + * + * @param index the index of the item on the list to be marked as removed + */ + public void markAsRemoved(final int index) { + // The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it + if (this.startSearchFromIndex == -1) + this.startSearchFromIndex = index; + + this.toRemove.add(index); + } + + /** + * Removes elements that have been marked as removed. + */ + public void removeMarkedEntries() { + if (this.startSearchFromIndex == -1) // No entries in the list, skip + return; + + removeAllByIndex(startSearchFromIndex, toRemove); + toRemove.clear(); + this.startSearchFromIndex = -1; // Reset the start search index + } + + /** + * Removes elements by their index. + */ + private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set because we want to avoid autoboxing when using contains + final int requiredMatches = c.size(); + if (requiredMatches == 0) + return; // exit early, we don't need to do anything + + final Object[] a = this.a; + int writeIndex = startSearchFromIndex; + int lastCopyIndex = startSearchFromIndex; + int matches = 0; + + for (int readIndex = startSearchFromIndex; readIndex < size; readIndex++) { + if (c.contains(readIndex)) { + matches++; + final int blockLength = readIndex - lastCopyIndex; + if (blockLength > 0) { + System.arraycopy(a, lastCopyIndex, a, writeIndex, blockLength); + writeIndex += blockLength; + } + lastCopyIndex = readIndex + 1; + + if (matches == requiredMatches) { + break; + } + } + } + + final int finalBlockLength = size - lastCopyIndex; + if (finalBlockLength > 0) { + System.arraycopy(a, lastCopyIndex, a, writeIndex, finalBlockLength); + writeIndex += finalBlockLength; + } + + if (writeIndex < size) { + Arrays.fill(a, writeIndex, size, null); + } + size = writeIndex; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 38b3985ec..5d35506a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -1,13 +1,13 @@ package net.momirealms.craftengine.core.world; import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; +import net.momirealms.craftengine.core.util.BlockEntityTickersList; import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; @@ -25,12 +25,14 @@ public abstract class CEWorld { protected final WorldHeight worldHeightAccessor; protected List pendingLightSections = new ArrayList<>(); protected final Set lightSections = ConcurrentHashMap.newKeySet(128); - protected final List tickingBlockEntities = new ArrayList<>(); + protected final BlockEntityTickersList tickingBlockEntities = new BlockEntityTickersList(); protected final List pendingTickingBlockEntities = new ArrayList<>(); protected volatile boolean isTickingBlockEntities = false; protected volatile boolean isUpdatingLights = false; protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; + @SuppressWarnings("FieldCanBeLocal") + private int tileTickPosition; public CEWorld(World world, StorageAdaptor adaptor) { this.world = world; @@ -205,15 +207,15 @@ public abstract class CEWorld { this.pendingTickingBlockEntities.clear(); } if (!this.tickingBlockEntities.isEmpty()) { - ReferenceOpenHashSet toRemove = new ReferenceOpenHashSet<>(); - for (TickingBlockEntity blockEntity : this.tickingBlockEntities) { + for (this.tileTickPosition = 0; this.tileTickPosition < this.tickingBlockEntities.size(); this.tileTickPosition++) { + TickingBlockEntity blockEntity = this.tickingBlockEntities.get(this.tileTickPosition); if (blockEntity.isValid()) { blockEntity.tick(); } else { - toRemove.add(blockEntity); + this.tickingBlockEntities.markAsRemoved(this.tileTickPosition); } } - this.tickingBlockEntities.removeAll(toRemove); + this.tickingBlockEntities.removeMarkedEntries(); } this.isTickingBlockEntities = false; } From dec48e9d8e1d338216be1506ffdc5726d73c82aa Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 21:42:48 +0800 Subject: [PATCH 127/226] =?UTF-8?q?perf(core):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=93=E7=9A=84=20tick=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/entity/BaseParticleBlockEntity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java index 9643a2bfd..fb8d63c83 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java @@ -18,7 +18,6 @@ public abstract class BaseParticleBlockEntity extends BlockEntity { } public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) { - if (true) return; particle.tickCount++; if (particle.tickCount % 10 != 0) return; particle.animateTick(state, ceWorld.world(), blockPos); From 3aa5bbe3a898ce3cfd119f30b4ee6365cbf96b01 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 21:58:04 +0800 Subject: [PATCH 128/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/AsyncResourcePackCacheEvent.java | 38 ++++++++++++++++++- .../core/pack/AbstractPackManager.java | 2 +- .../craftengine/core/pack/PackCacheData.java | 6 +-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java index 56776f611..35f1cdd4f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/AsyncResourcePackCacheEvent.java @@ -5,6 +5,22 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * This event is triggered when a user executes the "/ce reload pack" command. + *

+ * The event initiates a process that caches all resource content into a virtual file system + * to ensure optimal build performance. To add your resource pack through this event, + * you must use the {@link #registerExternalResourcePack(Path)} method every time this event is called. + *

+ *

+ * Important: The caching system will not update your resource pack if its file size or + * last modification time remains unchanged between reloads. Ensure these attributes change + * if you need the cache to recognize updates. + *

+ */ public class AsyncResourcePackCacheEvent extends Event { private static final HandlerList HANDLER_LIST = new HandlerList(); private final PackCacheData cacheData; @@ -16,7 +32,7 @@ public class AsyncResourcePackCacheEvent extends Event { @NotNull public PackCacheData cacheData() { - return cacheData; + return this.cacheData; } @NotNull @@ -28,4 +44,24 @@ public class AsyncResourcePackCacheEvent extends Event { public HandlerList getHandlers() { return getHandlerList(); } + + /** + * Adds an external resource pack to the cache. + *

+ * This method accepts either a .zip file or a directory path representing a resource pack. + * The resource pack will be added to the appropriate cache collection based on its type. + *

+ * + * @param path the file system path to the resource pack. Must be either a .zip file or a directory. + * @throws IllegalArgumentException if the provided path is neither a .zip file nor a directory. + */ + public void registerExternalResourcePack(@NotNull final Path path) { + if (Files.isRegularFile(path) && path.getFileName().endsWith(".zip")) { + this.cacheData.externalZips().add(path); + } else if (Files.isDirectory(path)) { + this.cacheData.externalFolders().add(path); + } else { + throw new IllegalArgumentException("Illegal resource pack path: " + path); + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 18da08797..f053a6102 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -273,7 +273,7 @@ public abstract class AbstractPackManager implements PackManager { @Override public void initCachedAssets() { try { - PackCacheData cacheData = new PackCacheData(plugin); + PackCacheData cacheData = new PackCacheData(this.plugin); this.cacheEventDispatcher.accept(cacheData); this.updateCachedAssets(cacheData, null); } catch (Exception e) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java index 702bf2163..1b4d1f23e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackCacheData.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.pack; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import org.jetbrains.annotations.NotNull; @@ -8,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import java.nio.file.Files; import java.nio.file.Path; import java.util.Set; +import java.util.stream.Collectors; public class PackCacheData { private final Set externalZips; @@ -17,13 +17,13 @@ public class PackCacheData { this.externalFolders = Config.foldersToMerge().stream() .map(it -> plugin.dataFolderPath().getParent().resolve(it)) .filter(Files::exists) - .collect(ObjectOpenHashSet.toSet()); + .collect(Collectors.toSet()); this.externalZips = Config.zipsToMerge().stream() .map(it -> plugin.dataFolderPath().getParent().resolve(it)) .filter(Files::exists) .filter(Files::isRegularFile) .filter(file -> file.getFileName().toString().endsWith(".zip")) - .collect(ObjectOpenHashSet.toSet()); + .collect(Collectors.toSet()); } @NotNull From d2153d56c018cdc414108fabe12fc39d2d38eb7e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 22:38:44 +0800 Subject: [PATCH 129/226] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/BukkitPlatform.java | 14 +++++++++++ .../craftengine/bukkit/world/BukkitWorld.java | 5 ++-- .../world/particle/BukkitParticleType.java | 25 +++++++++++++++++++ .../core/block/entity/BlockEntity.java | 2 +- .../entity/tick/TickingBlockEntityImpl.java | 4 +-- .../craftengine/core/plugin/Platform.java | 4 +++ .../craftengine/core/world/CEWorld.java | 13 +++++----- .../craftengine/core/world/World.java | 3 ++- .../core/world/particle/ParticleConfig.java | 11 ++++---- .../core/world/particle/ParticleType.java | 10 ++++++++ gradle.properties | 2 +- 11 files changed, 73 insertions(+), 20 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/particle/BukkitParticleType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleType.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java index 6e1545272..dfccb590f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java @@ -5,12 +5,17 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps; +import net.momirealms.craftengine.bukkit.util.ParticleUtils; +import net.momirealms.craftengine.bukkit.world.particle.BukkitParticleType; import net.momirealms.craftengine.core.plugin.Platform; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.particle.ParticleType; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; import org.bukkit.Bukkit; +import org.bukkit.Particle; import java.util.Map; @@ -62,4 +67,13 @@ public class BukkitPlatform implements Platform { } return BukkitAdaptors.adapt(world); } + + @Override + public ParticleType getParticleType(Key name) { + Particle particle = ParticleUtils.getParticle(name); + if (particle == null) { + throw new IllegalArgumentException("Invalid particle: " + name); + } + return new BukkitParticleType(particle, name); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 2624bc78d..4a3b36f80 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.particle.ParticleData; +import net.momirealms.craftengine.core.world.particle.ParticleType; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.SoundCategory; @@ -106,8 +107,8 @@ public class BukkitWorld implements World { } @Override - public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context) { - Particle particleType = ParticleUtils.getParticle(particle); + public void spawnParticle(Position location, ParticleType particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context) { + Particle particleType = (Particle) particle.platformParticle(); if (particleType == null) return; org.bukkit.World platformWorld = platformWorld(); platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z())); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/particle/BukkitParticleType.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/particle/BukkitParticleType.java new file mode 100644 index 000000000..0bba8abb3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/particle/BukkitParticleType.java @@ -0,0 +1,25 @@ +package net.momirealms.craftengine.bukkit.world.particle; + +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.world.particle.ParticleType; +import org.bukkit.Particle; + +public class BukkitParticleType implements ParticleType { + private final Particle particle; + private final Key type; + + public BukkitParticleType(Particle particle, Key type) { + this.particle = particle; + this.type = type; + } + + @Override + public Key type() { + return this.type; + } + + @Override + public Particle platformParticle() { + return particle; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java index fd582ffe5..78dc7210a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -13,7 +13,7 @@ public abstract class BlockEntity { protected final BlockPos pos; protected ImmutableBlockState blockState; protected BlockEntityType type; - protected CEWorld world; + public CEWorld world; protected boolean valid; @Nullable protected DynamicBlockEntityRenderer blockEntityRenderer; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java index ee323e5a9..12d09ee8c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java @@ -25,10 +25,8 @@ public class TickingBlockEntityImpl implements TickingBlo @Override public void tick() { - // 已无效 - if (!this.isValid()) return; // 还没加载完全 - if (this.blockEntity.world() == null) return; + if (this.blockEntity.world == null) return; BlockPos pos = pos(); ImmutableBlockState state = this.chunk.getBlockState(pos); // 不是合法方块 diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java index 3ab8e0267..6a8ec531b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java @@ -1,7 +1,9 @@ package net.momirealms.craftengine.core.plugin; import com.google.gson.JsonElement; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.particle.ParticleType; import net.momirealms.sparrow.nbt.Tag; public interface Platform { @@ -17,4 +19,6 @@ public interface Platform { Tag javaToSparrowNBT(Object object); World getWorld(String name); + + ParticleType getParticleType(Key name); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 5d35506a7..ac187bb3a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -31,8 +31,6 @@ public abstract class CEWorld { protected volatile boolean isUpdatingLights = false; protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; - @SuppressWarnings("FieldCanBeLocal") - private int tileTickPosition; public CEWorld(World world, StorageAdaptor adaptor) { this.world = world; @@ -207,12 +205,13 @@ public abstract class CEWorld { this.pendingTickingBlockEntities.clear(); } if (!this.tickingBlockEntities.isEmpty()) { - for (this.tileTickPosition = 0; this.tileTickPosition < this.tickingBlockEntities.size(); this.tileTickPosition++) { - TickingBlockEntity blockEntity = this.tickingBlockEntities.get(this.tileTickPosition); - if (blockEntity.isValid()) { - blockEntity.tick(); + Object[] entities = this.tickingBlockEntities.elements(); + for (int i = 0, size = this.tickingBlockEntities.size(); i < size; i++) { + TickingBlockEntity entity = (TickingBlockEntity) entities[i]; + if (entity.isValid()) { + entity.tick(); } else { - this.tickingBlockEntities.markAsRemoved(this.tileTickPosition); + this.tickingBlockEntities.markAsRemoved(i); } } this.tickingBlockEntities.removeMarkedEntries(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/World.java b/core/src/main/java/net/momirealms/craftengine/core/world/World.java index bc6181c8b..6bf1e2eed 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/World.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/World.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.particle.ParticleData; +import net.momirealms.craftengine.core.world.particle.ParticleType; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -58,7 +59,7 @@ public interface World { void levelEvent(int id, BlockPos pos, int data); - void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context); + void spawnParticle(Position location, ParticleType particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context); long time(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java index b96863555..0621016d6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleConfig.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.world.particle; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.util.Key; @@ -9,7 +10,7 @@ import java.util.Map; import java.util.Optional; public class ParticleConfig { - public final Key particleType; + public final ParticleType particleType; public final NumberProvider x; public final NumberProvider y; public final NumberProvider z; @@ -20,7 +21,7 @@ public class ParticleConfig { public final NumberProvider speed; public final ParticleData particleData; - public ParticleConfig(Key particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count, NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData) { + public ParticleConfig(ParticleType particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count, NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData) { this.particleType = particleType; this.x = x; this.y = y; @@ -43,7 +44,7 @@ public class ParticleConfig { NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0)); NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0)); NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0)); - return new ParticleConfig(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null)); + return new ParticleConfig(CraftEngine.instance().platform().getParticleType(particleType), x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null)); } public static ParticleConfig fromMap$blockEntity(Map arguments) { @@ -56,10 +57,10 @@ public class ParticleConfig { NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0)); NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0)); NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0)); - return new ParticleConfig(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null)); + return new ParticleConfig(CraftEngine.instance().platform().getParticleType(particleType), x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null)); } - public Key particleType() { + public ParticleType particleType() { return particleType; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleType.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleType.java new file mode 100644 index 000000000..1a0a59998 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleType.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.util.Key; + +public interface ParticleType { + + Key type(); + + Object platformParticle(); +} diff --git a/gradle.properties b/gradle.properties index d93169b9c..69959143c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.62.20 +project_version=0.0.63 config_version=45 lang_version=29 project_group=net.momirealms From 9f978156827e888055feb3c583f949d28c8cb4d6 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 14 Sep 2025 23:34:58 +0800 Subject: [PATCH 130/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=81=97=E6=BC=8F?= =?UTF-8?q?=E7=9A=84tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/entity/WallTorchParticleBlockEntity.java | 1 - .../resources/default/configuration/blocks/copper_coil.yml | 2 ++ .../momirealms/craftengine/core/pack/AbstractPackManager.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java index f571661e8..cdf545719 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/WallTorchParticleBlockEntity.java @@ -5,7 +5,6 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.SimpleContext; -import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml index c0fe30805..7edbe4e33 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml @@ -26,6 +26,8 @@ items: is-suffocating: true instrument: basedrum map-color: 15 + tags: + - minecraft:mineable/pickaxe behavior: type: lamp_block states: diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index f053a6102..62898220f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -728,7 +728,7 @@ public abstract class AbstractPackManager implements PackManager { this.plugin.logger().info("Validated resource pack in " + (time3 - time2) + "ms"); Path finalPath = resourcePackPath(); Files.createDirectories(finalPath.getParent()); - if (!VersionHelper.PREMIUM) { + if (!VersionHelper.PREMIUM && Config.enableObfuscation()) { Config.instance().setObf(false); this.plugin.logger().warn("Resource pack obfuscation requires Premium Edition."); } From 7363f4c0c7987036439504d932036f888c4694c9 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Mon, 15 Sep 2025 14:44:17 +0800 Subject: [PATCH 131/226] =?UTF-8?q?fix(network):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E8=B8=A2=E5=87=BA=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/plugin/network/PacketConsumers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index b8671f959..21682150b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -2504,7 +2504,7 @@ public class PacketConsumers { if (!user.isUUIDVerified()) { if (Config.strictPlayerUuidValidation()) { TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); - user.kick(Component.translatable("disconnect.loginFailed")); + user.kick(Component.translatable("disconnect.loginFailedInfo").arguments(Component.translatable("argument.uuid.invalid"))); return; } if (Config.debugResourcePack()) { From 69bd789a43c3b053cf72cdabc7215646f78daee9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 16 Sep 2025 21:20:22 +0800 Subject: [PATCH 132/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BE=B9=E7=BC=98?= =?UTF-8?q?=E5=85=89=E7=85=A7=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/injector/WorldStorageInjector.java | 10 +++----- .../bukkit/world/BukkitWorldManager.java | 25 ++++++++++++++----- gradle.properties | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 6a79e1032..430683add 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -313,9 +313,7 @@ public final class WorldStorageInjector { @SuppressWarnings("DuplicatedCode") private static void updateLight(@This InjectedHolder thisObj, Object clientState, Object serverState, int x, int y, int z) { CEWorld world = thisObj.ceChunk().world; - Object blockPos = LocationUtils.toBlockPos(x, y, z); - Object serverWorld = world.world().serverWorld(); - if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(serverState, clientState, serverWorld, blockPos)) { + if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(serverState, clientState)) { SectionPos sectionPos = thisObj.cePos(); List pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15); world.sectionLightUpdated(pos); @@ -325,16 +323,14 @@ public final class WorldStorageInjector { @SuppressWarnings("DuplicatedCode") private static void updateLight$complex(@This InjectedHolder thisObj, Object newClientState, Object newServerState, Object oldServerState, int x, int y, int z) { CEWorld world = thisObj.ceChunk().world; - Object blockPos = LocationUtils.toBlockPos(x, y, z); - Object serverWorld = world.world().serverWorld(); // 如果客户端新状态和服务端新状态光照属性不同 - if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newClientState, newServerState, serverWorld, blockPos)) { + if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newClientState, newServerState)) { SectionPos sectionPos = thisObj.cePos(); List pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15); world.sectionLightUpdated(pos); return; } - if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newServerState, oldServerState, serverWorld, blockPos)) { + if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newServerState, oldServerState)) { SectionPos sectionPos = thisObj.cePos(); List pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15); world.sectionLightUpdated(pos); 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 2a90f6333..9be7ba113 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 @@ -5,6 +5,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -275,16 +276,19 @@ public class BukkitWorldManager implements WorldManager, Listener { } private void handleChunkLoad(CEWorld ceWorld, Chunk chunk) { - ChunkPos pos = new ChunkPos(chunk.getX(), chunk.getZ()); - if (ceWorld.isChunkLoaded(pos.longKey)) return; + int chunkX = chunk.getX(); + int chunkZ = chunk.getZ(); + ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + if (ceWorld.isChunkLoaded(chunkPos.longKey)) return; CEChunk ceChunk; try { - ceChunk = ceWorld.worldDataStorage().readChunkAt(ceWorld, pos); + ceChunk = ceWorld.worldDataStorage().readChunkAt(ceWorld, chunkPos); try { CESection[] ceSections = ceChunk.sections(); Object worldServer = FastNMS.INSTANCE.field$CraftChunk$worldServer(chunk); Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer); - Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunkAtIfLoadedMainThread(chunkSource, chunk.getX(), chunk.getZ()); + Object lightEngine = FastNMS.INSTANCE.method$ChunkSource$getLightEngine(chunkSource); + Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunkAtIfLoadedMainThread(chunkSource, chunkX, chunkZ); Object[] sections = FastNMS.INSTANCE.method$ChunkAccess$getSections(levelChunk); synchronized (sections) { for (int i = 0; i < ceSections.length; i++) { @@ -339,13 +343,22 @@ public class BukkitWorldManager implements WorldManager, Listener { } } if (Config.restoreCustomBlocks()) { + boolean isEmptyBefore = FastNMS.INSTANCE.method$LevelSection$hasOnlyAir(section); + int sectionY = ceSection.sectionY; + if (isEmptyBefore) { + FastNMS.INSTANCE.method$LightEventListener$updateSectionStatus(lightEngine, FastNMS.INSTANCE.method$SectionPos$of(chunkX, sectionY, chunkZ), false); + } if (!ceSection.statesContainer().isEmpty()) { for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { ImmutableBlockState customState = ceSection.getBlockState(x, y, z); if (!customState.isEmpty() && customState.customBlockState() != null) { - FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.customBlockState().literalObject(), false); + Object newState = customState.customBlockState().literalObject(); + Object previous = FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, newState, false); + if (newState != previous && FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(newState, previous)) { + FastNMS.INSTANCE.method$ThreadedLevelLightEngine$checkBlock(lightEngine, LocationUtils.toBlockPos(chunkX * 16 + x, sectionY * 16 + y, chunkZ * 16 + z)); + } } } } @@ -353,7 +366,7 @@ public class BukkitWorldManager implements WorldManager, Listener { } } int finalI = i; - WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z), + WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(chunkPos.x, ceChunk.sectionY(i), chunkPos.z), (injected) -> sections[finalI] = injected); } } diff --git a/gradle.properties b/gradle.properties index 69959143c..3a5d0e20e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.87 +nms_helper_version=1.0.89 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 35807d7a214ded8a38eb6b0d8e8c7abfa214a143 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 16 Sep 2025 22:33:02 +0800 Subject: [PATCH 133/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/recipe/RecipeEventListener.java | 21 ++++++++++++------- .../item/recipe/AbstractRecipeSerializer.java | 11 ++++++++++ .../recipe/CustomCraftingTableRecipe.java | 16 +++++++++++++- .../core/item/recipe/CustomShapedRecipe.java | 14 +++++++++---- .../item/recipe/CustomShapelessRecipe.java | 13 ++++++++---- gradle.properties | 2 +- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 59894f3e4..7d98b15d5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -23,6 +23,8 @@ import net.momirealms.craftengine.core.item.recipe.input.SmithingInput; import net.momirealms.craftengine.core.item.setting.AnvilRepairItem; import net.momirealms.craftengine.core.item.setting.ItemEquipment; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.util.*; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -619,7 +621,7 @@ public class RecipeEventListener implements Listener { } } - @EventHandler(ignoreCancelled = true) + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onCraftingFinish(CraftItemEvent event) { if (!Config.enableRecipeSystem()) return; org.bukkit.inventory.Recipe recipe = event.getRecipe(); @@ -634,14 +636,19 @@ public class RecipeEventListener implements Listener { if (!(optionalRecipe.get() instanceof CustomCraftingTableRecipe craftingTableRecipe)) { return; } - if (!craftingTableRecipe.hasVisualResult()) { - return; - } - CraftingInput input = getCraftingInput(inventory); - if (input == null) return; Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); - inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); + if (!craftingTableRecipe.hasVisualResult()) { + CraftingInput input = getCraftingInput(inventory); + inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); + } + Function[] functions = craftingTableRecipe.craftingFunctions(); + if (functions != null) { + PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer); + for (Function function : functions) { + function.run(context); + } + } } private CraftingInput getCraftingInput(CraftingInventory inventory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java index d6a3460f5..8f4632676 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java @@ -9,6 +9,9 @@ import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; import net.momirealms.craftengine.core.item.recipe.result.PostProcessor; import net.momirealms.craftengine.core.item.recipe.result.PostProcessors; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.*; import org.jetbrains.annotations.NotNull; @@ -24,6 +27,14 @@ public abstract class AbstractRecipeSerializer> implement new VanillaRecipeReader1_20_5() : new VanillaRecipeReader1_20(); + @SuppressWarnings("unchecked") + protected Function[] functions(Map arguments) { + Object functions = arguments.get("functions"); + if (functions == null) return null; + List> functionList = ResourceConfigUtils.parseConfigAsList(functions, EventFunctions::fromMap); + return functionList.toArray(new Function[0]); + } + protected boolean showNotification(Map arguments) { return ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("show-notification", true), "show-notification"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java index 655f40ea4..d6cb7b67a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java @@ -4,17 +4,27 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.Nullable; public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe { protected final CraftingRecipeCategory category; private final CustomRecipeResult visualResult; + private final Function[] craftingFunctions; - protected CustomCraftingTableRecipe(Key id, boolean showNotification, CustomRecipeResult result, @Nullable CustomRecipeResult visualResult, String group, CraftingRecipeCategory category) { + protected CustomCraftingTableRecipe(Key id, + boolean showNotification, + CustomRecipeResult result, + @Nullable CustomRecipeResult visualResult, + String group, + CraftingRecipeCategory category, + Function[] craftingFunctions) { super(id, showNotification, result, group); this.category = category == null ? CraftingRecipeCategory.MISC : category; this.visualResult = visualResult; + this.craftingFunctions = craftingFunctions; } public CraftingRecipeCategory category() { @@ -49,4 +59,8 @@ public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe return super.result.buildItem(context); } } + + public Function[] craftingFunctions() { + return craftingFunctions; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java index 40af56a0f..bad93556a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java @@ -5,6 +5,9 @@ import com.google.gson.JsonObject; import net.momirealms.craftengine.core.item.recipe.input.CraftingInput; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -24,8 +27,9 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { CustomRecipeResult visualResult, String group, CraftingRecipeCategory category, - Pattern pattern) { - super(id, showNotification, result, visualResult, group, category); + Pattern pattern, + Function[] craftingFunctions) { + super(id, showNotification, result, visualResult, group, category, craftingFunctions); this.pattern = pattern; this.parsedPattern = pattern.parse(); } @@ -169,7 +173,8 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { parseResult(arguments), parseVisualResult(arguments), arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), - new Pattern<>(pattern.toArray(new String[0]), ingredients) + new Pattern<>(pattern.toArray(new String[0]), ingredients), + functions(arguments) ); } @@ -182,7 +187,8 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { null, VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json), - new Pattern<>(VANILLA_RECIPE_HELPER.craftingShapedPattern(json), ingredients) + new Pattern<>(VANILLA_RECIPE_HELPER.craftingShapedPattern(json), ingredients), + null ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java index 678c75543..a23c0c4bf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java @@ -4,6 +4,8 @@ import com.google.gson.JsonObject; import net.momirealms.craftengine.core.item.recipe.input.CraftingInput; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import org.jetbrains.annotations.NotNull; @@ -23,8 +25,9 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { CustomRecipeResult visualResult, String group, CraftingRecipeCategory category, - List> ingredients) { - super(id, showNotification, result, visualResult, group, category); + List> ingredients, + Function[] craftingFunctions) { + super(id, showNotification, result, visualResult, group, category, craftingFunctions); this.ingredients = ingredients; this.placementInfo = PlacementInfo.create(ingredients); } @@ -88,7 +91,8 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { parseResult(arguments), parseVisualResult(arguments), arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), - ingredients + ingredients, + functions(arguments) ); } @@ -99,7 +103,8 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { parseResult(VANILLA_RECIPE_HELPER.craftingResult(json.getAsJsonObject("result"))), null, VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json), - VANILLA_RECIPE_HELPER.shapelessIngredients(json.getAsJsonArray("ingredients")).stream().map(this::toIngredient).toList() + VANILLA_RECIPE_HELPER.shapelessIngredients(json.getAsJsonArray("ingredients")).stream().map(this::toIngredient).toList(), + null ); } } diff --git a/gradle.properties b/gradle.properties index 3a5d0e20e..c40afd9f7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63 +project_version=0.0.63.1 config_version=45 lang_version=29 project_group=net.momirealms From 5bfc2be9c9ff41dc6841aed69e7f245c7c3b1ba5 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 16 Sep 2025 22:48:23 +0800 Subject: [PATCH 134/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E7=94=A8=E9=80=94=E5=87=BA=E7=8E=B0trim=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/item/recipe/AbstractRecipeManager.java | 14 ++++++++------ .../core/item/recipe/CustomSmithingTrimRecipe.java | 5 +++++ .../craftengine/core/item/recipe/Recipe.java | 4 ++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java index 2e2b739df..670407315 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java @@ -106,12 +106,14 @@ public abstract class AbstractRecipeManager implements RecipeManager { if (recipe instanceof AbstractedFixedResultRecipe fixedResult) { this.byResult.computeIfAbsent(fixedResult.result().item().id(), k -> new ArrayList<>()).add(recipe); } - HashSet usedKeys = new HashSet<>(); - for (Ingredient ingredient : recipe.ingredientsInUse()) { - for (UniqueKey holder : ingredient.items()) { - Key key = holder.key(); - if (usedKeys.add(key)) { - this.byIngredient.computeIfAbsent(key, k -> new ArrayList<>()).add(recipe); + if (recipe.canBeSearchedByIngredients()) { + HashSet usedKeys = new HashSet<>(); + for (Ingredient ingredient : recipe.ingredientsInUse()) { + for (UniqueKey holder : ingredient.items()) { + Key key = holder.key(); + if (usedKeys.add(key)) { + this.byIngredient.computeIfAbsent(key, k -> new ArrayList<>()).add(recipe); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java index a85d30573..1d1195624 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java @@ -103,6 +103,11 @@ public class CustomSmithingTrimRecipe extends AbstractRecipe { return pattern; } + @Override + public boolean canBeSearchedByIngredients() { + return false; + } + @SuppressWarnings({"DuplicatedCode"}) public static class Serializer extends AbstractRecipeSerializer> { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/Recipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/Recipe.java index db3d2f675..ff5864916 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/Recipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/Recipe.java @@ -25,4 +25,8 @@ public interface Recipe { default boolean showNotification() { return true; } + + default boolean canBeSearchedByIngredients() { + return true; + } } From f1723df61604d7b608657fbec1eefa55abadad50 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 17 Sep 2025 01:13:59 +0800 Subject: [PATCH 135/226] =?UTF-8?q?fix(sound):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=B8=B8=E6=88=8F=E4=BA=8B=E4=BB=B6=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E6=96=B9=E5=9D=97=E7=A0=B4=E5=9D=8F=E9=9F=B3?= =?UTF-8?q?=E6=95=88=E8=A2=ABremap=E5=90=8E=E6=9C=AA=E8=A1=A5=E5=8C=85?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=97=A0=E5=A3=B0=E9=9F=B3=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/PacketConsumers.java | 23 +++++++++++++++++++ .../craftengine/core/util/RandomUtils.java | 4 ++++ gradle.properties | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 21682150b..56260a14a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -502,6 +502,29 @@ public class PacketConsumers { int state = buf.readInt(); boolean global = buf.readBoolean(); int newState = user.clientModEnabled() ? remapMOD(state) : remap(state); + Object blockState = BlockStateUtils.idToBlockState(newState); + Object block = BlockStateUtils.getBlockOwner(blockState); + if (BukkitBlockManager.instance().isBlockSoundRemoved(block) && !FastNMS.INSTANCE.method$BlockStateBase$isAir(blockState)) { + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); + Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); + Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); + if (mappedSoundId != null) { + Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty()); + Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); + Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( + mappedBreakSoundHolder, + CoreReflections.instance$SoundSource$BLOCKS, + blockPos.x() + 0.5, + blockPos.y() + 0.5, + blockPos.z() + 0.5, + (FastNMS.INSTANCE.field$SoundType$volume(soundType) + 1.0F) / 2.0F, + FastNMS.INSTANCE.field$SoundType$pitch(soundType) * 0.8F, + RandomUtils.generateRandomLong() + ); + user.sendPacket(packet, true); + } + } if (newState == state) { return; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/RandomUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/RandomUtils.java index b659d4c28..7904d1776 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/RandomUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/RandomUtils.java @@ -22,6 +22,10 @@ public final class RandomUtils { return ThreadLocalRandom.current().nextBoolean(); } + public static long generateRandomLong() { + return ThreadLocalRandom.current().nextLong(); + } + public static double triangle(double mode, double deviation) { return mode + deviation * (generateRandomDouble(0,1) - generateRandomDouble(0,1)); } diff --git a/gradle.properties b/gradle.properties index c40afd9f7..873f9bbbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.89 +nms_helper_version=1.0.90 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 5b1c12a3ae13f157015a6d1f4ff0f7631bbb767f Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 17 Sep 2025 01:47:30 +0800 Subject: [PATCH 136/226] =?UTF-8?q?fix(sound):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=86=99=E6=B3=95=E4=B8=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=8F=91=E5=A3=B0=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/api/CraftEngineBlocks.java | 32 ++++++++++++++----- .../AbstractCanSurviveBlockBehavior.java | 8 ++--- .../behavior/DoubleHighBlockBehavior.java | 8 ++--- .../behavior/PressurePlateBlockBehavior.java | 8 ++--- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java index 93ec4c025..88fa7042b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java @@ -188,16 +188,14 @@ public final class CraftEngineBlocks { * @param player player who breaks the block * @param dropLoot whether to drop block loots * @param isMoving is moving - * @param playSound whether to play break sounds - * @param sendParticles whether to send break particles + * @param sendLevelEvent whether to send break particles and sounds * @return success or not */ public static boolean remove(@NotNull Block block, @Nullable Player player, boolean isMoving, boolean dropLoot, - boolean playSound, - boolean sendParticles) { + boolean sendLevelEvent) { ImmutableBlockState state = getCustomBlockState(block); if (state == null || state.isEmpty()) return false; World world = new BukkitWorld(block.getWorld()); @@ -215,16 +213,34 @@ public final class CraftEngineBlocks { world.dropItemNaturally(position, item); } } - if (playSound) { - world.playBlockSound(position, state.settings().sounds().breakSound()); - } - if (sendParticles) { + if (sendLevelEvent) { FastNMS.INSTANCE.method$LevelAccessor$levelEvent(world.serverWorld(), WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), state.customBlockState().registryId()); } FastNMS.INSTANCE.method$Level$removeBlock(world.serverWorld(), LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), isMoving); return true; } + /** + * Removes a block from the world if it's custom + * + * @param block block to remove + * @param player player who breaks the block + * @param dropLoot whether to drop block loots + * @param isMoving is moving + * @param playSound whether to play break sounds + * @param sendParticles whether to send break particles + * @return success or not + */ + @Deprecated + public static boolean remove(@NotNull Block block, + @Nullable Player player, + boolean isMoving, + boolean dropLoot, + boolean playSound, + boolean sendParticles) { + return remove(block, player, dropLoot, isMoving, playSound || sendParticles); + } + /** * Checks if a block is custom * diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java index 2ab895fe7..6e2ef1443 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java @@ -70,11 +70,11 @@ public abstract class AbstractCanSurviveBlockBehavior extends BukkitBlockBehavio return state; } if (!canSurvive(thisBlock, new Object[] {state, level, blockPos}, () -> true)) { - BlockPos pos = LocationUtils.fromBlockPos(blockPos); + // BlockPos pos = LocationUtils.fromBlockPos(blockPos); ImmutableBlockState customState = optionalCustomState.get(); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - world.playBlockSound(position, customState.settings().sounds().breakSound()); + // net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + // WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); + // world.playBlockSound(position, customState.settings().sounds().breakSound()); // 下面触发事件也会有声音 FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId()); return MBlocks.AIR$defaultState; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java index 14305b479..7cac0ad6d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java @@ -50,10 +50,10 @@ public class DoubleHighBlockBehavior extends BukkitBlockBehavior { if (anotherHalfCustomState != null && !anotherHalfCustomState.isEmpty()) return blockState; // 破坏 - BlockPos pos = LocationUtils.fromBlockPos(blockPos); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - world.playBlockSound(position, customState.settings().sounds().breakSound()); + // BlockPos pos = LocationUtils.fromBlockPos(blockPos); + // net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + // WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); + // world.playBlockSound(position, customState.settings().sounds().breakSound()); // 下面触发事件也会有声音 FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId()); return MBlocks.AIR$defaultState; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index da6eb9d78..3d807c87c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -66,10 +66,10 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { return MBlocks.AIR$defaultState; } ImmutableBlockState customState = optionalCustomState.get(); - BlockPos pos = LocationUtils.fromBlockPos(blockPos); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - world.playBlockSound(position, customState.settings().sounds().breakSound()); + // BlockPos pos = LocationUtils.fromBlockPos(blockPos); + // net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + // WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); + // world.playBlockSound(position, customState.settings().sounds().breakSound()); // 下面触发事件也会有声音 FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId()); return MBlocks.AIR$defaultState; } From 811523a0c8ff78439877265425ba8079ee3528e4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 17 Sep 2025 03:06:38 +0800 Subject: [PATCH 137/226] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/ToggleableLampBlockBehavior.java | 2 +- .../craftengine/bukkit/item/recipe/RecipeEventListener.java | 4 ++-- .../resources/default/configuration/blocks/safe_block.yml | 2 ++ .../resources/default/configuration/blocks/table_lamp.yml | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java index 577c167c0..4fbb1b43f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ToggleableLampBlockBehavior.java @@ -91,7 +91,7 @@ public class ToggleableLampBlockBehavior extends BukkitBlockBehavior { public static class Factory implements BlockBehaviorFactory { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-open-with-hand", false), "can-open-with-hand"); + boolean canOpenWithHand = ResourceConfigUtils.getAsBoolean(ResourceConfigUtils.get(arguments, "can-open-with-hand", "can-toggle-with-hand"), "can-toggle-with-hand"); Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.toggleable_lamp.missing_lit"); Property powered = (Property) (canOpenWithHand ? block.getProperty("powered") : ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.toggleable_lamp.missing_powered")); return new ToggleableLampBlockBehavior(block, lit, powered, canOpenWithHand); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 7d98b15d5..6120f6855 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -614,7 +614,7 @@ public class RecipeEventListener implements Listener { if (input == null) return; Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); - if (craftingTableRecipe.hasVisualResult()) { + if (craftingTableRecipe.hasVisualResult() && VersionHelper.PREMIUM) { inventory.setResult(craftingTableRecipe.assembleVisual(input, ItemBuildContext.of(serverPlayer))); } else { inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); @@ -623,7 +623,7 @@ public class RecipeEventListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onCraftingFinish(CraftItemEvent event) { - if (!Config.enableRecipeSystem()) return; + if (!Config.enableRecipeSystem() || !VersionHelper.PREMIUM) return; org.bukkit.inventory.Recipe recipe = event.getRecipe(); if (!(recipe instanceof CraftingRecipe craftingRecipe)) return; Key recipeId = Key.of(craftingRecipe.getKey().namespace(), craftingRecipe.getKey().value()); diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index 99d1a28e3..a1349bcbd 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -22,6 +22,8 @@ items: push-reaction: block instrument: basedrum map-color: 6 + tags: + - minecraft:mineable/pickaxe sounds: break: minecraft:block.stone.break fall: minecraft:block.stone.fall diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml index 8802410b6..7539c0882 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml @@ -21,9 +21,10 @@ items: replaceable: false is-redstone-conductor: false is-suffocating: false + support-shape: cobweb behaviors: - type: toggleable_lamp_block - can-open-with-hand: true + can-toggle-with-hand: true - type: sturdy_base_block direction: down support-types: From 6faf6ca88bc64f2510d63126b43510f005ce20e1 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 17 Sep 2025 04:54:51 +0800 Subject: [PATCH 138/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=80=E5=AF=B9?= =?UTF-8?q?=E4=B8=80=E5=88=97=E8=A1=A8=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/PacketConsumers.java | 16 +++++++-- .../core/util/IntIdentityList.java | 35 ++++++++++++++----- .../core/world/chunk/packet/MCSection.java | 6 +++- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 21682150b..f0e46f014 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -87,14 +87,15 @@ import org.jetbrains.annotations.Nullable; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.BiConsumer; +import java.util.function.Consumer; public class PacketConsumers { private static BukkitNetworkManager.Handlers[] ADD_ENTITY_HANDLERS; private static int[] BLOCK_STATE_MAPPINGS; private static int[] MOD_BLOCK_STATE_MAPPINGS; private static IntIdentityList SERVER_BLOCK_LIST; - private static IntIdentityList CLIENT_BLOCK_LIST; private static IntIdentityList BIOME_LIST; + private static Consumer> BIOME_MAPPER; public static void initEntities(int registrySize) { ADD_ENTITY_HANDLERS = new BukkitNetworkManager.Handlers[registrySize]; @@ -230,6 +231,16 @@ public class PacketConsumers { }; } + public static void setBiomeMapper(Consumer> mapper) { + BIOME_MAPPER = mapper; + } + + public static void remapBiomes(PalettedContainer biomes) { + if (BIOME_MAPPER != null) { + BIOME_MAPPER.accept(biomes); + } + } + public static void initBlocks(Map map, int registrySize) { int[] newMappings = new int[registrySize]; for (int i = 0; i < registrySize; i++) { @@ -250,7 +261,6 @@ public class PacketConsumers { BLOCK_STATE_MAPPINGS = newMappings; MOD_BLOCK_STATE_MAPPINGS = newMappingsMOD; SERVER_BLOCK_LIST = new IntIdentityList(registrySize); - CLIENT_BLOCK_LIST = new IntIdentityList(BlockStateUtils.vanillaStateSize()); BIOME_LIST = new IntIdentityList(RegistryUtils.currentBiomeRegistrySize()); } @@ -340,6 +350,7 @@ public class PacketConsumers { MCSection mcSection = new MCSection(user.clientBlockList(), SERVER_BLOCK_LIST, BIOME_LIST); mcSection.readPacket(friendlyByteBuf); PalettedContainer container = mcSection.blockStateContainer(); + remapBiomes(mcSection.biomeContainer()); Palette palette = container.data().palette(); if (palette.canRemap()) { palette.remap(PacketConsumers::remapMOD); @@ -363,6 +374,7 @@ public class PacketConsumers { MCSection mcSection = new MCSection(user.clientBlockList(), SERVER_BLOCK_LIST, BIOME_LIST); mcSection.readPacket(friendlyByteBuf); PalettedContainer container = mcSection.blockStateContainer(); + remapBiomes(mcSection.biomeContainer()); Palette palette = container.data().palette(); if (palette.canRemap()) { palette.remap(PacketConsumers::remap); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java b/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java index a60be053e..10df6acd1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java @@ -1,22 +1,16 @@ package net.momirealms.craftengine.core.util; -import it.unimi.dsi.fastutil.ints.IntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Iterator; -import java.util.List; +import java.util.NoSuchElementException; public class IntIdentityList implements IndexedIterable { private final int size; - private final List list; public IntIdentityList(int size) { this.size = size; - list = new IntArrayList(size); - for (int i = 0; i < size; i++) { - list.add(i); - } } @Override @@ -36,6 +30,29 @@ public class IntIdentityList implements IndexedIterable { @Override public @NotNull Iterator iterator() { - return list.iterator(); + return new IntIterator(size); } -} + + private static class IntIterator implements Iterator { + private final int size; + private int current; + + public IntIterator(int size) { + this.size = size; + this.current = 0; + } + + @Override + public boolean hasNext() { + return current < size; + } + + @Override + public Integer next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return current++; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java index d0931a2ee..9967eb586 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java @@ -9,7 +9,7 @@ public class MCSection { private short nonEmptyBlockCount; private final PalettedContainer serverBlockStateContainer; private final IndexedIterable clientBlockStateList; - private ReadableContainer biomeContainer; + private PalettedContainer biomeContainer; public MCSection(IndexedIterable clientBlockStateList, IndexedIterable serverBlockStateList, IndexedIterable biomeList) { this.serverBlockStateContainer = new PalettedContainer<>(serverBlockStateList, 0, PalettedContainer.PaletteProvider.BLOCK_STATE); @@ -42,4 +42,8 @@ public class MCSection { public PalettedContainer blockStateContainer() { return serverBlockStateContainer; } + + public PalettedContainer biomeContainer() { + return biomeContainer; + } } From 7d3029c193f726e13f7d3136b28646fbca150311 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 17 Sep 2025 18:43:56 +0800 Subject: [PATCH 139/226] =?UTF-8?q?refactor(bukkit):=20=E6=B8=85=E7=90=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/api/CraftEngineBlocks.java | 2 +- .../behavior/AbstractCanSurviveBlockBehavior.java | 10 ++-------- .../bukkit/block/behavior/DoubleHighBlockBehavior.java | 4 ---- .../block/behavior/PressurePlateBlockBehavior.java | 4 ---- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java index 88fa7042b..3fb08edd3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java @@ -231,7 +231,7 @@ public final class CraftEngineBlocks { * @param sendParticles whether to send break particles * @return success or not */ - @Deprecated + @Deprecated(forRemoval = true) public static boolean remove(@NotNull Block block, @Nullable Player player, boolean isMoving, diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java index 6e2ef1443..851371fa6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java @@ -7,7 +7,6 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldEvents; import net.momirealms.craftengine.core.world.WorldPosition; @@ -69,13 +68,8 @@ public abstract class AbstractCanSurviveBlockBehavior extends BukkitBlockBehavio FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, blockPos, thisBlock, this.delay); return state; } - if (!canSurvive(thisBlock, new Object[] {state, level, blockPos}, () -> true)) { - // BlockPos pos = LocationUtils.fromBlockPos(blockPos); - ImmutableBlockState customState = optionalCustomState.get(); - // net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - // WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - // world.playBlockSound(position, customState.settings().sounds().breakSound()); // 下面触发事件也会有声音 - FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId()); + if (!FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state, level, blockPos)) { + FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, optionalCustomState.get().customBlockState().registryId()); return MBlocks.AIR$defaultState; } return state; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java index 7cac0ad6d..7845bddd2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java @@ -50,10 +50,6 @@ public class DoubleHighBlockBehavior extends BukkitBlockBehavior { if (anotherHalfCustomState != null && !anotherHalfCustomState.isEmpty()) return blockState; // 破坏 - // BlockPos pos = LocationUtils.fromBlockPos(blockPos); - // net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - // WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - // world.playBlockSound(position, customState.settings().sounds().breakSound()); // 下面触发事件也会有声音 FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId()); return MBlocks.AIR$defaultState; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 3d807c87c..16f852fd2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -66,10 +66,6 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { return MBlocks.AIR$defaultState; } ImmutableBlockState customState = optionalCustomState.get(); - // BlockPos pos = LocationUtils.fromBlockPos(blockPos); - // net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - // WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - // world.playBlockSound(position, customState.settings().sounds().breakSound()); // 下面触发事件也会有声音 FastNMS.INSTANCE.method$LevelAccessor$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, customState.customBlockState().registryId()); return MBlocks.AIR$defaultState; } From ca25e7b7921883cdfd98f22e6b8c43ed127ecd03 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 17 Sep 2025 19:55:52 +0800 Subject: [PATCH 140/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=AE=9E=E4=BD=93tick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/DoubleHighBlockBehavior.java | 5 +- .../behavior/PressurePlateBlockBehavior.java | 4 +- .../behavior/SimpleParticleBlockBehavior.java | 2 +- .../WallTorchParticleBlockBehavior.java | 2 +- .../item/recipe/RecipeEventListener.java | 25 ++++-- .../plugin/injector/WorldStorageInjector.java | 1 - .../core/block/ImmutableBlockState.java | 11 ++- .../block/behavior/EntityBlockBehavior.java | 6 +- .../item/recipe/AbstractRecipeSerializer.java | 14 ++- .../core/item/recipe/ConditionalRecipe.java | 8 ++ .../recipe/CustomCraftingTableRecipe.java | 15 +++- .../core/item/recipe/CustomShapedRecipe.java | 11 ++- .../item/recipe/CustomShapelessRecipe.java | 10 ++- .../recipe/CustomSmithingTransformRecipe.java | 30 +++++-- .../item/recipe/CustomSmithingTrimRecipe.java | 22 ++++- .../craftengine/core/world/CEWorld.java | 71 +++++++++++---- .../craftengine/core/world/chunk/CEChunk.java | 86 ++++++++++++++----- .../core/world/chunk/packet/MCSection.java | 1 - gradle.properties | 2 +- 19 files changed, 246 insertions(+), 80 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/recipe/ConditionalRecipe.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java index 7845bddd2..723af40e4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java @@ -6,7 +6,6 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; @@ -14,7 +13,9 @@ import net.momirealms.craftengine.core.block.state.properties.DoubleBlockHalf; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldEvents; import java.util.Map; import java.util.concurrent.Callable; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 16f852fd2..09c8c920d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -8,7 +8,6 @@ import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; @@ -20,7 +19,8 @@ import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.PressurePlateSensitivity; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldEvents; import org.bukkit.GameEvent; import org.bukkit.util.Vector; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java index d52bda8a4..fa0944580 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleParticleBlockBehavior.java @@ -48,7 +48,7 @@ public class SimpleParticleBlockBehavior extends BukkitBlockBehavior implements } @Override - public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + public BlockEntityTicker createAsyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { if (this.particles.length == 0) return null; return EntityBlockBehavior.createTickerHelper(SimpleParticleBlockEntity::tick); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java index 84a2d19fb..85888fc1e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/WallTorchParticleBlockBehavior.java @@ -57,7 +57,7 @@ public class WallTorchParticleBlockBehavior extends BukkitBlockBehavior implemen } @Override - public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + public BlockEntityTicker createAsyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { if (this.particles.length == 0) return null; return EntityBlockBehavior.createTickerHelper(WallTorchParticleBlockEntity::tick); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 6120f6855..58392558e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -610,14 +610,19 @@ public class RecipeEventListener implements Listener { inventory.setResult(null); return; } - CraftingInput input = getCraftingInput(inventory); - if (input == null) return; Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + ItemBuildContext itemBuildContext = ItemBuildContext.of(serverPlayer); + if (!craftingTableRecipe.canUse(itemBuildContext)) { + inventory.setResult(null); + return; + } + CraftingInput input = getCraftingInput(inventory); + if (input == null) return; if (craftingTableRecipe.hasVisualResult() && VersionHelper.PREMIUM) { - inventory.setResult(craftingTableRecipe.assembleVisual(input, ItemBuildContext.of(serverPlayer))); + inventory.setResult(craftingTableRecipe.assembleVisual(input, itemBuildContext)); } else { - inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); + inventory.setResult(craftingTableRecipe.assemble(input, itemBuildContext)); } } @@ -638,7 +643,7 @@ public class RecipeEventListener implements Listener { } Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); - if (!craftingTableRecipe.hasVisualResult()) { + if (craftingTableRecipe.hasVisualResult()) { CraftingInput input = getCraftingInput(inventory); inventory.setResult(craftingTableRecipe.assemble(input, ItemBuildContext.of(serverPlayer))); } @@ -697,10 +702,16 @@ public class RecipeEventListener implements Listener { event.setResult(null); return; } + Player player = InventoryUtils.getPlayerFromInventoryEvent(event); + ItemBuildContext itemBuildContext = ItemBuildContext.of(BukkitAdaptors.adapt(player)); + if (!smithingTrimRecipe.canUse(itemBuildContext)) { + event.setResult(null); + return; + } + SmithingInput input = getSmithingInput(inventory); if (smithingTrimRecipe.matches(input)) { - Player player = InventoryUtils.getPlayerFromInventoryEvent(event); - ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), ItemBuildContext.of(BukkitAdaptors.adapt(player))); + ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), itemBuildContext); event.setResult(result); } else { event.setResult(null); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 430683add..b1e7d7ff1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -14,7 +14,6 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.DelegatingBlockState; import net.momirealms.craftengine.core.block.EmptyBlock; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 8bdeb7115..18e6460d2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -152,9 +152,16 @@ public final class ImmutableBlockState extends BlockStateHolder { } @SuppressWarnings("unchecked") - public BlockEntityTicker createBlockEntityTicker(CEWorld world, BlockEntityType type) { + public BlockEntityTicker createSyncBlockEntityTicker(CEWorld world, BlockEntityType type) { EntityBlockBehavior blockBehavior = this.behavior.getEntityBehavior(); if (blockBehavior == null) return null; - return (BlockEntityTicker) blockBehavior.createBlockEntityTicker(world, this, type); + return (BlockEntityTicker) blockBehavior.createSyncBlockEntityTicker(world, this, type); + } + + @SuppressWarnings("unchecked") + public BlockEntityTicker createAsyncBlockEntityTicker(CEWorld world, BlockEntityType type) { + EntityBlockBehavior blockBehavior = this.behavior.getEntityBehavior(); + if (blockBehavior == null) return null; + return (BlockEntityTicker) blockBehavior.createAsyncBlockEntityTicker(world, this, type); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java index f92b0e59a..2eb34bae7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java @@ -15,7 +15,11 @@ public interface EntityBlockBehavior { BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state); - default BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + default BlockEntityTicker createSyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + return null; + } + + default BlockEntityTicker createAsyncBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { return null; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java index 8f4632676..a95d06e39 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java @@ -9,7 +9,10 @@ import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; import net.momirealms.craftengine.core.item.recipe.result.PostProcessor; import net.momirealms.craftengine.core.item.recipe.result.PostProcessors; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.condition.AllOfCondition; +import net.momirealms.craftengine.core.plugin.context.event.EventConditions; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -29,12 +32,21 @@ public abstract class AbstractRecipeSerializer> implement @SuppressWarnings("unchecked") protected Function[] functions(Map arguments) { - Object functions = arguments.get("functions"); + Object functions = ResourceConfigUtils.get(arguments, "functions", "function"); if (functions == null) return null; List> functionList = ResourceConfigUtils.parseConfigAsList(functions, EventFunctions::fromMap); return functionList.toArray(new Function[0]); } + protected Condition conditions(Map arguments) { + Object functions = ResourceConfigUtils.get(arguments, "conditions", "condition"); + if (functions == null) return null; + List> conditionList = ResourceConfigUtils.parseConfigAsList(functions, EventConditions::fromMap); + if (conditionList.isEmpty()) return null; + if (conditionList.size() == 1) return conditionList.getFirst(); + return new AllOfCondition<>(conditionList); + } + protected boolean showNotification(Map arguments) { return ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("show-notification", true), "show-notification"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/ConditionalRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/ConditionalRecipe.java new file mode 100644 index 000000000..fd0a8b649 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/ConditionalRecipe.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.item.recipe; + +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; + +public interface ConditionalRecipe { + + boolean canUse(final PlayerOptionalContext context); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java index d6cb7b67a..9445e2472 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java @@ -4,15 +4,17 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; +import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.Nullable; -public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe { +public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe implements ConditionalRecipe { protected final CraftingRecipeCategory category; private final CustomRecipeResult visualResult; private final Function[] craftingFunctions; + private final Condition craftingCondition; protected CustomCraftingTableRecipe(Key id, boolean showNotification, @@ -20,11 +22,20 @@ public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe @Nullable CustomRecipeResult visualResult, String group, CraftingRecipeCategory category, - Function[] craftingFunctions) { + Function[] craftingFunctions, + Condition craftingCondition) { super(id, showNotification, result, group); this.category = category == null ? CraftingRecipeCategory.MISC : category; this.visualResult = visualResult; this.craftingFunctions = craftingFunctions; + this.craftingCondition = craftingCondition; + } + + + @Override + public boolean canUse(PlayerOptionalContext context) { + if (this.craftingCondition == null) return true; + return this.craftingCondition.test(context); } public CraftingRecipeCategory category() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java index bad93556a..75613d5d8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapedRecipe.java @@ -5,8 +5,8 @@ import com.google.gson.JsonObject; import net.momirealms.craftengine.core.item.recipe.input.CraftingInput; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; +import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; -import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; @@ -28,8 +28,9 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { String group, CraftingRecipeCategory category, Pattern pattern, - Function[] craftingFunctions) { - super(id, showNotification, result, visualResult, group, category, craftingFunctions); + Function[] craftingFunctions, + Condition craftingCondition) { + super(id, showNotification, result, visualResult, group, category, craftingFunctions, craftingCondition); this.pattern = pattern; this.parsedPattern = pattern.parse(); } @@ -174,7 +175,8 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { parseVisualResult(arguments), arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), new Pattern<>(pattern.toArray(new String[0]), ingredients), - functions(arguments) + functions(arguments), + conditions(arguments) ); } @@ -188,6 +190,7 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json), new Pattern<>(VANILLA_RECIPE_HELPER.craftingShapedPattern(json), ingredients), + null, null ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java index a23c0c4bf..a60c241a4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java @@ -4,6 +4,7 @@ import com.google.gson.JsonObject; import net.momirealms.craftengine.core.item.recipe.input.CraftingInput; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; +import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.util.Key; @@ -26,8 +27,9 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { String group, CraftingRecipeCategory category, List> ingredients, - Function[] craftingFunctions) { - super(id, showNotification, result, visualResult, group, category, craftingFunctions); + Function[] craftingFunctions, + Condition craftingCondition) { + super(id, showNotification, result, visualResult, group, category, craftingFunctions, craftingCondition); this.ingredients = ingredients; this.placementInfo = PlacementInfo.create(ingredients); } @@ -92,7 +94,8 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { parseVisualResult(arguments), arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), ingredients, - functions(arguments) + functions(arguments), + conditions(arguments) ); } @@ -104,6 +107,7 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { null, VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json), VANILLA_RECIPE_HELPER.shapelessIngredients(json.getAsJsonArray("ingredients")).stream().map(this::toIngredient).toList(), + null, null ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java index 9ebe583a7..852b8ff8e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java @@ -7,6 +7,8 @@ import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.input.SmithingInput; import net.momirealms.craftengine.core.item.recipe.result.CustomRecipeResult; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Registries; @@ -20,13 +22,14 @@ import java.util.List; import java.util.Map; import java.util.Objects; -public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecipe { +public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecipe implements ConditionalRecipe { public static final Serializer SERIALIZER = new Serializer<>(); private final Ingredient base; private final Ingredient template; private final Ingredient addition; private final boolean mergeComponents; private final List processors; + private final Condition condition; public CustomSmithingTransformRecipe(Key id, boolean showNotification, @@ -35,7 +38,8 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip @Nullable Ingredient addition, CustomRecipeResult result, List processors, - boolean mergeComponents + boolean mergeComponents, + Condition condition ) { super(id, showNotification, result); this.base = base; @@ -43,6 +47,13 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip this.addition = addition; this.processors = processors; this.mergeComponents = mergeComponents; + this.condition = condition; + } + + @Override + public boolean canUse(PlayerOptionalContext context) { + if (this.condition != null) return this.condition.test(context); + return true; } @SuppressWarnings("unchecked") @@ -140,14 +151,23 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip toIngredient(addition), parseResult(arguments), ItemDataProcessors.fromMapList(processors), - mergeComponents + mergeComponents, + conditions(arguments) ); } @Override public CustomSmithingTransformRecipe readJson(Key id, JsonObject json) { - return new CustomSmithingTransformRecipe<>(id, - true, toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("template"))), Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("base")))), toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("addition"))), parseResult(VANILLA_RECIPE_HELPER.smithingResult(json.getAsJsonObject("result"))), null, true + return new CustomSmithingTransformRecipe<>( + id, + true, + toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("template"))), + Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("base")))), + toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("addition"))), + parseResult(VANILLA_RECIPE_HELPER.smithingResult(json.getAsJsonObject("result"))), + null, + true, + null ); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java index 1d1195624..020a5d460 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTrimRecipe.java @@ -6,6 +6,8 @@ import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.recipe.input.RecipeInput; import net.momirealms.craftengine.core.item.recipe.input.SmithingInput; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -18,31 +20,41 @@ import java.util.List; import java.util.Map; import java.util.Objects; -public class CustomSmithingTrimRecipe extends AbstractRecipe { +public class CustomSmithingTrimRecipe extends AbstractRecipe implements ConditionalRecipe { public static final Serializer SERIALIZER = new Serializer<>(); private final Ingredient base; private final Ingredient template; private final Ingredient addition; @Nullable // 1.21.5 private final Key pattern; + @Nullable + private final Condition condition; public CustomSmithingTrimRecipe(@NotNull Key id, boolean showNotification, @NotNull Ingredient template, @NotNull Ingredient base, @NotNull Ingredient addition, - @Nullable Key pattern + @Nullable Key pattern, + @Nullable Condition condition ) { super(id, showNotification); this.base = base; this.template = template; this.addition = addition; this.pattern = pattern; + this.condition = condition; if (pattern == null && VersionHelper.isOrAbove1_21_5()) { throw new IllegalStateException("SmithingTrimRecipe cannot have a null pattern on 1.21.5 and above."); } } + @Override + public boolean canUse(PlayerOptionalContext context) { + if (this.condition != null) return this.condition.test(context); + return true; + } + @SuppressWarnings("unchecked") @Override public T assemble(RecipeInput input, ItemBuildContext context) { @@ -122,7 +134,8 @@ public class CustomSmithingTrimRecipe extends AbstractRecipe { ResourceConfigUtils.requireNonNullOrThrow(toIngredient(template), "warning.config.recipe.smithing_trim.missing_template_type"), ResourceConfigUtils.requireNonNullOrThrow(toIngredient(base), "warning.config.recipe.smithing_trim.missing_base"), ResourceConfigUtils.requireNonNullOrThrow(toIngredient(addition), "warning.config.recipe.smithing_trim.missing_addition"), - pattern + pattern, + conditions(arguments) ); } @@ -133,7 +146,8 @@ public class CustomSmithingTrimRecipe extends AbstractRecipe { Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("template")))), Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("base")))), Objects.requireNonNull(toIngredient(VANILLA_RECIPE_HELPER.singleIngredient(json.get("addition")))), - VersionHelper.isOrAbove1_21_5() ? Key.of(json.get("pattern").getAsString()) : null + VersionHelper.isOrAbove1_21_5() ? Key.of(json.get("pattern").getAsString()) : null, + null ); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index ac187bb3a..60098466b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -25,9 +25,12 @@ public abstract class CEWorld { protected final WorldHeight worldHeightAccessor; protected List pendingLightSections = new ArrayList<>(); protected final Set lightSections = ConcurrentHashMap.newKeySet(128); - protected final BlockEntityTickersList tickingBlockEntities = new BlockEntityTickersList(); - protected final List pendingTickingBlockEntities = new ArrayList<>(); - protected volatile boolean isTickingBlockEntities = false; + protected final BlockEntityTickersList tickingSyncBlockEntities = new BlockEntityTickersList(); + protected final List pendingSyncTickingBlockEntities = new ArrayList<>(); + protected final BlockEntityTickersList tickingAsyncBlockEntities = new BlockEntityTickersList(); + protected final List pendingAsyncTickingBlockEntities = new ArrayList<>(); + protected volatile boolean isTickingSyncBlockEntities = false; + protected volatile boolean isTickingAsyncBlockEntities = false; protected volatile boolean isUpdatingLights = false; protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; @@ -176,13 +179,14 @@ public abstract class CEWorld { } public void syncTick() { - this.tickBlockEntities(); + this.tickSyncBlockEntities(); if (!Config.asyncLightUpdate()) { this.updateLight(); } } public void asyncTick() { + this.tickAsyncBlockEntities(); if (Config.asyncLightUpdate()) { this.updateLight(); } @@ -190,32 +194,61 @@ public abstract class CEWorld { public abstract void updateLight(); - public void addBlockEntityTicker(TickingBlockEntity ticker) { - if (this.isTickingBlockEntities) { - this.pendingTickingBlockEntities.add(ticker); + public void addSyncBlockEntityTicker(TickingBlockEntity ticker) { + if (this.isTickingSyncBlockEntities) { + this.pendingSyncTickingBlockEntities.add(ticker); } else { - this.tickingBlockEntities.add(ticker); + this.tickingSyncBlockEntities.add(ticker); } } - protected void tickBlockEntities() { - this.isTickingBlockEntities = true; - if (!this.pendingTickingBlockEntities.isEmpty()) { - this.tickingBlockEntities.addAll(this.pendingTickingBlockEntities); - this.pendingTickingBlockEntities.clear(); + public void addAsyncBlockEntityTicker(TickingBlockEntity ticker) { + if (this.isTickingAsyncBlockEntities) { + this.pendingAsyncTickingBlockEntities.add(ticker); + } else { + this.tickingAsyncBlockEntities.add(ticker); } - if (!this.tickingBlockEntities.isEmpty()) { - Object[] entities = this.tickingBlockEntities.elements(); - for (int i = 0, size = this.tickingBlockEntities.size(); i < size; i++) { + } + + protected void tickSyncBlockEntities() { + this.isTickingSyncBlockEntities = true; + if (!this.pendingSyncTickingBlockEntities.isEmpty()) { + this.tickingSyncBlockEntities.addAll(this.pendingSyncTickingBlockEntities); + this.pendingSyncTickingBlockEntities.clear(); + } + if (!this.tickingSyncBlockEntities.isEmpty()) { + Object[] entities = this.tickingSyncBlockEntities.elements(); + for (int i = 0, size = this.tickingSyncBlockEntities.size(); i < size; i++) { TickingBlockEntity entity = (TickingBlockEntity) entities[i]; if (entity.isValid()) { entity.tick(); } else { - this.tickingBlockEntities.markAsRemoved(i); + this.tickingSyncBlockEntities.markAsRemoved(i); } } - this.tickingBlockEntities.removeMarkedEntries(); + this.tickingSyncBlockEntities.removeMarkedEntries(); } - this.isTickingBlockEntities = false; + this.isTickingSyncBlockEntities = false; + } + + protected void tickAsyncBlockEntities() { + this.isTickingAsyncBlockEntities = true; + if (!this.pendingAsyncTickingBlockEntities.isEmpty()) { + this.tickingAsyncBlockEntities.addAll(this.pendingAsyncTickingBlockEntities); + this.pendingAsyncTickingBlockEntities.clear(); + } + if (!this.tickingAsyncBlockEntities.isEmpty()) { + Object[] entities = this.tickingAsyncBlockEntities.elements(); + for (int i = 0, size = this.tickingAsyncBlockEntities.size(); i < size; i++) { + TickingBlockEntity entity = (TickingBlockEntity) entities[i]; + if (entity.isValid()) { + entity.tick(); + } else { + this.tickingAsyncBlockEntities.markAsRemoved(i); + } + } + this.tickingAsyncBlockEntities.removeMarkedEntries(); + } + this.isTickingAsyncBlockEntities = false; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 442ac8999..d48e09472 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.world.chunk; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.block.entity.render.DynamicBlockEntityRenderer; @@ -27,7 +28,8 @@ public class CEChunk { public final CESection[] sections; public final WorldHeight worldHeightAccessor; public final Map blockEntities; // 从区域线程上访问,安全 - public final Map tickingBlockEntitiesByPos; // 从区域线程上访问,安全 + public final Map tickingSyncBlockEntitiesByPos; // 从区域线程上访问,安全 + public final Map tickingAsyncBlockEntitiesByPos; // 从区域线程上访问,安全 public final Map constantBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 public final Map dynamicBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 private final ReentrantReadWriteLock renderLock = new ReentrantReadWriteLock(); @@ -42,7 +44,8 @@ public class CEChunk { this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.tickingSyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.tickingAsyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); this.fillEmptySection(); } @@ -51,7 +54,8 @@ public class CEChunk { this.chunkPos = chunkPos; this.worldHeightAccessor = world.worldHeight(); this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.tickingSyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.tickingAsyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); int sectionCount = this.worldHeightAccessor.getSectionsCount(); this.sections = new CESection[sectionCount]; if (sections != null) { @@ -198,27 +202,51 @@ public class CEChunk { this.blockEntities.values().forEach(e -> e.setValid(false)); this.constantBlockEntityRenderers.values().forEach(ConstantBlockEntityRenderer::deactivate); this.dynamicBlockEntityRenderers.clear(); - this.tickingBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); - this.tickingBlockEntitiesByPos.clear(); + this.tickingSyncBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); + this.tickingSyncBlockEntitiesByPos.clear(); + this.tickingAsyncBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE)); + this.tickingAsyncBlockEntitiesByPos.clear(); } + @SuppressWarnings("unchecked") public void replaceOrCreateTickingBlockEntity(T blockEntity) { ImmutableBlockState blockState = blockEntity.blockState(); - BlockEntityTicker ticker = blockState.createBlockEntityTicker(this.world, blockEntity.type()); - if (ticker != null) { - this.tickingBlockEntitiesByPos.compute(blockEntity.pos(), ((pos, previousTicker) -> { - TickingBlockEntity newTicker = new TickingBlockEntityImpl<>(this, blockEntity, ticker); - if (previousTicker != null) { - previousTicker.setTicker(newTicker); - return previousTicker; - } else { - ReplaceableTickingBlockEntity replaceableTicker = new ReplaceableTickingBlockEntity(newTicker); - this.world.addBlockEntityTicker(replaceableTicker); - return replaceableTicker; - } - })); - } else { + EntityBlockBehavior blockBehavior = blockState.behavior().getEntityBehavior(); + if (blockBehavior == null) { this.removeBlockEntityTicker(blockEntity.pos()); + } else { + BlockEntityTicker syncTicker = (BlockEntityTicker) blockBehavior.createSyncBlockEntityTicker(this.world, blockState, blockEntity.type()); + if (syncTicker != null) { + this.tickingSyncBlockEntitiesByPos.compute(blockEntity.pos(), ((pos, previousTicker) -> { + TickingBlockEntity newTicker = new TickingBlockEntityImpl<>(this, blockEntity, syncTicker); + if (previousTicker != null) { + previousTicker.setTicker(newTicker); + return previousTicker; + } else { + ReplaceableTickingBlockEntity replaceableTicker = new ReplaceableTickingBlockEntity(newTicker); + this.world.addSyncBlockEntityTicker(replaceableTicker); + return replaceableTicker; + } + })); + } else { + this.removeSyncBlockEntityTicker(blockEntity.pos()); + } + BlockEntityTicker asyncTicker = (BlockEntityTicker) blockBehavior.createAsyncBlockEntityTicker(this.world, blockState, blockEntity.type()); + if (asyncTicker != null) { + this.tickingAsyncBlockEntitiesByPos.compute(blockEntity.pos(), ((pos, previousTicker) -> { + TickingBlockEntity newTicker = new TickingBlockEntityImpl<>(this, blockEntity, asyncTicker); + if (previousTicker != null) { + previousTicker.setTicker(newTicker); + return previousTicker; + } else { + ReplaceableTickingBlockEntity replaceableTicker = new ReplaceableTickingBlockEntity(newTicker); + this.world.addAsyncBlockEntityTicker(replaceableTicker); + return replaceableTicker; + } + })); + } else { + this.removeAsyncBlockEntityTicker(blockEntity.pos()); + } } } @@ -250,13 +278,25 @@ public class CEChunk { } } - private void removeBlockEntityTicker(BlockPos pos) { - ReplaceableTickingBlockEntity blockEntity = this.tickingBlockEntitiesByPos.remove(pos); - if (blockEntity != null) { - blockEntity.setTicker(DummyTickingBlockEntity.INSTANCE); + private void removeSyncBlockEntityTicker(BlockPos pos) { + ReplaceableTickingBlockEntity e1 = this.tickingSyncBlockEntitiesByPos.remove(pos); + if (e1 != null) { + e1.setTicker(DummyTickingBlockEntity.INSTANCE); } } + private void removeAsyncBlockEntityTicker(BlockPos pos) { + ReplaceableTickingBlockEntity e2 = this.tickingAsyncBlockEntitiesByPos.remove(pos); + if (e2 != null) { + e2.setTicker(DummyTickingBlockEntity.INSTANCE); + } + } + + private void removeBlockEntityTicker(BlockPos pos) { + removeSyncBlockEntityTicker(pos); + removeAsyncBlockEntityTicker(pos); + } + public void setBlockEntity(BlockEntity blockEntity) { BlockPos pos = blockEntity.pos(); ImmutableBlockState blockState = this.getBlockState(pos); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java index 9967eb586..81948b867 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/packet/MCSection.java @@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.world.chunk.packet; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.IndexedIterable; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; -import net.momirealms.craftengine.core.world.chunk.ReadableContainer; public class MCSection { private short nonEmptyBlockCount; diff --git a/gradle.properties b/gradle.properties index 873f9bbbc..3dfd4ae54 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.1 +project_version=0.0.63.2 config_version=45 lang_version=29 project_group=net.momirealms From b8fb2d1201f3f370c9a357ea161e334c95617a53 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 17 Sep 2025 20:24:58 +0800 Subject: [PATCH 141/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B2=99=E5=8F=91?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=E5=BD=A2=E7=8A=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/default/configuration/blocks/sofa.yml | 1 + .../core/item/recipe/AbstractRecipeSerializer.java | 6 +++--- .../core/item/recipe/CustomCraftingTableRecipe.java | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index 1f4a86917..131392513 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -23,6 +23,7 @@ items: is-redstone-conductor: false push-reaction: block instrument: bass + support-shape: cobweb sounds: break: minecraft:block.wood.break fall: minecraft:block.wood.fall diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java index a95d06e39..43dd6dfc5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java @@ -39,9 +39,9 @@ public abstract class AbstractRecipeSerializer> implement } protected Condition conditions(Map arguments) { - Object functions = ResourceConfigUtils.get(arguments, "conditions", "condition"); - if (functions == null) return null; - List> conditionList = ResourceConfigUtils.parseConfigAsList(functions, EventConditions::fromMap); + Object conditions = ResourceConfigUtils.get(arguments, "conditions", "condition"); + if (conditions == null) return null; + List> conditionList = ResourceConfigUtils.parseConfigAsList(conditions, EventConditions::fromMap); if (conditionList.isEmpty()) return null; if (conditionList.size() == 1) return conditionList.getFirst(); return new AllOfCondition<>(conditionList); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java index 9445e2472..13783dabc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomCraftingTableRecipe.java @@ -31,7 +31,6 @@ public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe this.craftingCondition = craftingCondition; } - @Override public boolean canUse(PlayerOptionalContext context) { if (this.craftingCondition == null) return true; From 7c8005c6f5973f8c57db7d47ad56a9ef278f3871 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Thu, 18 Sep 2025 01:01:58 +0800 Subject: [PATCH 142/226] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=A0=85=E6=A0=8F=E6=96=B9=E5=9D=97=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../block/behavior/FenceBlockBehavior.java | 155 ++++++++++++++++++ .../behavior/FenceGateBlockBehavior.java | 21 ++- .../reflection/minecraft/CoreReflections.java | 7 + .../plugin/reflection/minecraft/MBlocks.java | 10 ++ .../plugin/reflection/minecraft/MTagKeys.java | 3 + .../bukkit/util/BlockStateUtils.java | 8 +- .../craftengine/bukkit/util/BlockUtils.java | 22 +++ .../src/main/resources/translations/en.yml | 4 + .../src/main/resources/translations/zh_cn.yml | 4 + .../craftengine/core/world/BlockPos.java | 44 +++++ .../craftengine/core/world/Vec3i.java | 33 ++++ gradle.properties | 6 +- 13 files changed, 310 insertions(+), 9 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockUtils.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index f6cc1c58e..4070b0f48 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -36,6 +36,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key LIQUID_FLOWABLE_BLOCK = Key.from("craftengine:liquid_flowable_block"); public static final Key SIMPLE_PARTICLE_BLOCK = Key.from("craftengine:simple_particle_block"); public static final Key WALL_TORCH_PARTICLE_BLOCK = Key.from("craftengine:wall_torch_particle_block"); + public static final Key FENCE_BLOCK = Key.from("craftengine:fence_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -70,5 +71,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(LIQUID_FLOWABLE_BLOCK, LiquidFlowableBlockBehavior.FACTORY); register(SIMPLE_PARTICLE_BLOCK, SimpleParticleBlockBehavior.FACTORY); register(WALL_TORCH_PARTICLE_BLOCK, WallTorchParticleBlockBehavior.FACTORY); + register(FENCE_BLOCK, FenceBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java new file mode 100644 index 000000000..d167464d2 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java @@ -0,0 +1,155 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MTagKeys; +import net.momirealms.craftengine.bukkit.util.*; +import net.momirealms.craftengine.core.block.*; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.BooleanProperty; +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class FenceBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final BooleanProperty northProperty; + private final BooleanProperty eastProperty; + private final BooleanProperty southProperty; + private final BooleanProperty westProperty; + private final Object selfBlockTag; + private final Object connectableBlockTag; + private final boolean canLeash; + + public FenceBlockBehavior(CustomBlock customBlock, + BooleanProperty northProperty, + BooleanProperty eastProperty, + BooleanProperty southProperty, + BooleanProperty westProperty, + Object selfBlockTag, + Object connectableBlockTag, + boolean canLeash) { + super(customBlock); + this.northProperty = northProperty; + this.eastProperty = eastProperty; + this.southProperty = southProperty; + this.westProperty = westProperty; + this.selfBlockTag = selfBlockTag; + this.connectableBlockTag = connectableBlockTag; + this.canLeash = canLeash; + } + + @Override + public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) { + return false; + } + + public boolean connectsTo(BlockStateWrapper state, boolean isSideSolid, HorizontalDirection direction) { + boolean isSameFence = this.isSameFence(state); + boolean flag = CoreReflections.clazz$FenceGateBlock.isInstance(BlockStateUtils.getBlockOwner(state.literalObject())) + ? FastNMS.INSTANCE.method$FenceGateBlock$connectsToDirection(state.literalObject(), DirectionUtils.toNMSDirection(direction.toDirection())) + : FenceGateBlockBehavior.connectsToDirection(state, direction); + return !BlockUtils.isExceptionForConnection(state) && isSideSolid || isSameFence || flag; + } + + private boolean isSameFence(BlockStateWrapper state) { + Object blockState = state.literalObject(); + return FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.selfBlockTag) + && FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.connectableBlockTag) + == FastNMS.INSTANCE.method$BlockStateBase$is(this.customBlock.defaultState().customBlockState().literalObject(), this.connectableBlockTag); + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + if (!this.canLeash) return InteractionResult.PASS; + Player player = context.getPlayer(); + if (player == null) return InteractionResult.PASS; + if (FastNMS.INSTANCE.method$LeadItem$bindPlayerMobs(player.serverPlayer(), context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()))) { + player.swingHand(InteractionHand.MAIN_HAND); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + World level = context.getLevel(); + BlockPos clickedPos = context.getClickedPos(); + Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(level.serverWorld(), LocationUtils.toBlockPos(clickedPos)); + BlockPos blockPos = clickedPos.north(); + BlockPos blockPos1 = clickedPos.east(); + BlockPos blockPos2 = clickedPos.south(); + BlockPos blockPos3 = clickedPos.west(); + BlockStateWrapper blockState = level.getBlockAt(blockPos).blockState(); + BlockStateWrapper blockState1 = level.getBlockAt(blockPos1).blockState(); + BlockStateWrapper blockState2 = level.getBlockAt(blockPos2).blockState(); + BlockStateWrapper blockState3 = level.getBlockAt(blockPos3).blockState(); + BooleanProperty waterlogged = (BooleanProperty) state.owner().value().getProperty("waterlogged"); + if (waterlogged != null) { + state = state.with(waterlogged, FastNMS.INSTANCE.method$FluidState$getType(fluidState) == MFluids.WATER); + } + return state + .with(this.northProperty, this.connectsTo(blockState, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos), CoreReflections.instance$Direction$SOUTH, CoreReflections.instance$SupportType$FULL), HorizontalDirection.SOUTH)) + .with(this.eastProperty, this.connectsTo(blockState1, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState1.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos1), CoreReflections.instance$Direction$WEST, CoreReflections.instance$SupportType$FULL), HorizontalDirection.WEST)) + .with(this.southProperty, this.connectsTo(blockState2, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState2.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos2), CoreReflections.instance$Direction$NORTH, CoreReflections.instance$SupportType$FULL), HorizontalDirection.NORTH)) + .with(this.westProperty, this.connectsTo(blockState3, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(blockState3.literalObject(), level.serverWorld(), LocationUtils.toBlockPos(blockPos3), CoreReflections.instance$Direction$EAST, CoreReflections.instance$SupportType$FULL), HorizontalDirection.EAST)); + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Optional optionalState = BlockStateUtils.getOptionalCustomBlockState(args[0]); + BooleanProperty waterlogged = (BooleanProperty) optionalState + .map(BlockStateHolder::owner) + .map(Holder::value) + .map(block -> block.getProperty("waterlogged")) + .orElse(null); + if (waterlogged != null) { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[updateShape$level], args[updateShape$blockPos], MFluids.WATER, 5); + } + if (DirectionUtils.fromNMSDirection(args[updateShape$direction]).axis().isHorizontal() && optionalState.isPresent()) { + Direction direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]); + ImmutableBlockState state = optionalState.get(); + if (state.owner() != null) { + BooleanProperty booleanProperty = (BooleanProperty) state.owner().value().getProperty(direction.name().toLowerCase(Locale.ROOT)); + if (booleanProperty != null) { + BlockStateWrapper wrapper = BlockStateUtils.toBlockStateWrapper(args[updateShape$neighborState]); + return state.with(booleanProperty, this.connectsTo(wrapper, FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(wrapper.literalObject(), args[updateShape$level], args[5], DirectionUtils.toNMSDirection(direction.opposite()), CoreReflections.instance$SupportType$FULL), direction.opposite().toHorizontalDirection())).customBlockState().literalObject(); + } + } + } + return superMethod.call(); + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + BooleanProperty north = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("north"), "warning.config.block.behavior.fence.missing_north"); + BooleanProperty east = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("east"), "warning.config.block.behavior.fence.missing_east"); + BooleanProperty south = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("south"), "warning.config.block.behavior.fence.missing_south"); + BooleanProperty west = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("west"), "warning.config.block.behavior.fence.missing_west"); + Object selfBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("self-block-tag", "minecraft:fences").toString()))); + selfBlockTag = selfBlockTag != null ? selfBlockTag : MTagKeys.Block$FENCES; + Object connectableBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("connectable-block-tag", "minecraft:wooden_fences").toString()))); + connectableBlockTag = connectableBlockTag != null ? connectableBlockTag : MTagKeys.Block$WOODEN_FENCES; + boolean canLeash = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-leash", false), "can-leash"); + return new FenceBlockBehavior(block, north, east, south, west, selfBlockTag, connectableBlockTag, canLeash); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java index 99f897792..03f0d1a11 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java @@ -10,10 +10,7 @@ import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.InteractUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.block.BlockBehavior; -import net.momirealms.craftengine.core.block.CustomBlock; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.InteractionResult; @@ -262,6 +259,22 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior { } } + public static boolean connectsToDirection(BlockStateWrapper state, HorizontalDirection direction) { + FenceGateBlockBehavior fence = BlockStateUtils.getOptionalCustomBlockState(state.literalObject()) + .map(ImmutableBlockState::behavior) + .flatMap(behavior -> behavior.getAs(FenceGateBlockBehavior.class)) + .orElse(null); + if (fence == null) return false; + Direction facing = null; + ImmutableBlockState customState = BlockStateUtils.getOptionalCustomBlockState(state.literalObject()).orElse(null); + if (customState == null) return false; + Property facingProperty = customState.owner().value().getProperty("facing"); + if (facingProperty != null && facingProperty.valueClass() == HorizontalDirection.class) { + facing = ((HorizontalDirection) customState.get(facingProperty)).toDirection(); + } + return facing != null && facing.axis() == direction.toDirection().clockWise().axis(); + } + public static class Factory implements BlockBehaviorFactory { @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 48eb0072e..d704b2335 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4389,4 +4389,11 @@ public final class CoreReflections { public static final Constructor constructor$AdvancementHolder = Optional.ofNullable(clazz$AdvancementHolder) .map(it -> ReflectionUtils.getConstructor(it, clazz$ResourceLocation, clazz$Advancement)) .orElse(null); + + public static final Class clazz$FenceGateBlock = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.level.block.BlockFenceGate", + "world.level.block.FenceGateBlock" + ) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java index d8971adba..d9c489fba 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java @@ -22,6 +22,11 @@ public final class MBlocks { public static final Object WATER$defaultState; public static final Object TNT; public static final Object TNT$defaultState; + public static final Object BARRIER; + public static final Object CARVED_PUMPKIN; + public static final Object JACK_O_LANTERN; + public static final Object MELON; + public static final Object PUMPKIN; private static Object getById(String id) { Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id); @@ -45,5 +50,10 @@ public final class MBlocks { WATER$defaultState = FastNMS.INSTANCE.method$Block$defaultState(WATER); TNT = getById("tnt"); TNT$defaultState = FastNMS.INSTANCE.method$Block$defaultState(TNT); + BARRIER = getById("barrier"); + CARVED_PUMPKIN = getById("carved_pumpkin"); + JACK_O_LANTERN = getById("jack_o_lantern"); + MELON = getById("melon"); + PUMPKIN = getById("pumpkin"); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java index bc3a11172..fe3440027 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java @@ -9,6 +9,9 @@ public final class MTagKeys { public static final Object Item$WOOL = create(MRegistries.ITEM, "wool"); public static final Object Block$WALLS = create(MRegistries.BLOCK, "walls"); + public static final Object Block$SHULKER_BOXES = create(MRegistries.BLOCK, "shulker_boxes"); + public static final Object Block$FENCES = create(MRegistries.BLOCK, "fences"); + public static final Object Block$WOODEN_FENCES = create(MRegistries.BLOCK, "wooden_fences"); private static Object create(Object registry, String location) { Object resourceLocation = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", location); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java index 3d3a592f7..a75f5808e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java @@ -45,8 +45,12 @@ public final class BlockStateUtils { public static BlockStateWrapper toBlockStateWrapper(BlockData blockData) { Object state = blockDataToBlockState(blockData); - int id = blockStateToId(state); - return new BukkitBlockStateWrapper(state, id); + return toBlockStateWrapper(state); + } + + public static BlockStateWrapper toBlockStateWrapper(Object blockState) { + int id = blockStateToId(blockState); + return new BukkitBlockStateWrapper(blockState, id); } public static boolean isCorrectTool(@NotNull ImmutableBlockState state, @Nullable Item itemInHand) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockUtils.java new file mode 100644 index 000000000..08546729e --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockUtils.java @@ -0,0 +1,22 @@ +package net.momirealms.craftengine.bukkit.util; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MTagKeys; +import net.momirealms.craftengine.core.block.BlockStateWrapper; + +public final class BlockUtils { + private BlockUtils() {} + + public static boolean isExceptionForConnection(BlockStateWrapper state) { + Object blockState = state.literalObject(); + return CoreReflections.clazz$LeavesBlock.isInstance(BlockStateUtils.getBlockOwner(blockState)) + || FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, MBlocks.BARRIER) + || FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, MBlocks.CARVED_PUMPKIN) + || FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, MBlocks.JACK_O_LANTERN) + || FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, MBlocks.MELON) + || FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, MBlocks.PUMPKIN) + || FastNMS.INSTANCE.method$BlockStateBase$is(blockState, MTagKeys.Block$SHULKER_BOXES); + } +} diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 4fa46057b..559efd97f 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -321,6 +321,10 @@ warning.config.block.behavior.double_high.missing_half: "Issue found in warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' argument for 'change_over_time_block' behavior." warning.config.block.behavior.surface_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'surface_attached_block' behavior." warning.config.block.behavior.wall_torch_particle.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'wall_torch_particle_block' behavior." +warning.config.block.behavior.fence.missing_north: "Issue found in file - The block '' is missing the required 'north' property for 'fence_block' behavior." +warning.config.block.behavior.fence.missing_east: "Issue found in file - The block '' is missing the required 'east' property for 'fence_block' behavior." +warning.config.block.behavior.fence.missing_south: "Issue found in file - The block '' is missing the required 'south' property for 'fence_block' behavior." +warning.config.block.behavior.fence.missing_west: "Issue found in file - The block '' is missing the required 'west' property for 'fence_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 8a5413621..d15fae40a 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -315,6 +315,10 @@ warning.config.block.behavior.double_high.missing_half: "在文件 在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 参数" warning.config.block.behavior.surface_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'surface_attached_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.wall_torch_particle.missing_facing: "在文件 发现问题 - 配置项 '' 的 'wall_torch_particle_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.fence.missing_north: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'north' 属性" +warning.config.block.behavior.fence.missing_east: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'east' 属性" +warning.config.block.behavior.fence.missing_south: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'south' 属性" +warning.config.block.behavior.fence.missing_west: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'west' 属性" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 0bc751b85..88fe4b680 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -55,4 +55,48 @@ public class BlockPos extends Vec3i { public BlockPos offset(int x, int y, int z) { return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.x() + x, this.y() + y, this.z() + z); } + + public BlockPos immutable() { + return this; + } + + @Override + public BlockPos north() { + return new BlockPos(this.x(), this.y(), this.z() - 1); + } + + @Override + public BlockPos north(int distance) { + return distance == 0 ? this.immutable() : new BlockPos(this.x(), this.y(), this.z() - distance); + } + + @Override + public BlockPos south() { + return new BlockPos(this.x(), this.y(), this.z() + 1); + } + + @Override + public BlockPos south(int distance) { + return distance == 0 ? this.immutable() : new BlockPos(this.x(), this.y(), this.z() + distance); + } + + @Override + public BlockPos west() { + return new BlockPos(this.x() - 1, this.y(), this.z()); + } + + @Override + public BlockPos west(int distance) { + return distance == 0 ? this.immutable() : new BlockPos(this.x() - distance, this.y(), this.z()); + } + + @Override + public BlockPos east() { + return new BlockPos(this.x() + 1, this.y(), this.z()); + } + + @Override + public BlockPos east(int distance) { + return distance == 0 ? this.immutable() : new BlockPos(this.x() + distance, this.y(), this.z()); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java index 9413aff03..82b2e4bbb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java @@ -92,4 +92,37 @@ public class Vec3i implements Comparable { return this.y() - vec3i.y(); } } + + public Vec3i north() { + return this.north(1); + } + + public Vec3i north(int distance) { + return this.relative(Direction.NORTH, distance); + } + + public Vec3i south() { + return this.south(1); + } + + public Vec3i south(int distance) { + return this.relative(Direction.SOUTH, distance); + } + + public Vec3i west() { + return this.west(1); + } + + public Vec3i west(int distance) { + return this.relative(Direction.WEST, distance); + } + + public Vec3i east() { + return this.east(1); + } + + public Vec3i east(int distance) { + return this.relative(Direction.EAST, distance); + } + } diff --git a/gradle.properties b/gradle.properties index 3dfd4ae54..e3cd09f69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,8 +3,8 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] project_version=0.0.63.2 -config_version=45 -lang_version=29 +config_version=46 +lang_version=30 project_group=net.momirealms latest_supported_version=1.21.8 @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.90 +nms_helper_version=1.0.91 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 5347d1e9f1451108cb3eb6b34ccf6baa342ac310 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Thu, 18 Sep 2025 02:52:57 +0800 Subject: [PATCH 143/226] =?UTF-8?q?feat(block):=20=E6=94=B9=E8=BF=9BDirect?= =?UTF-8?q?ionalAttachedBlockBehavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DirectionalAttachedBlockBehavior.java | 98 ++++++++++++++++--- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index 228bab889..5e8383ef5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.BlockTags; import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.BlockBehavior; @@ -13,24 +14,41 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.HorizontalDirection; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.World; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; -import java.util.Map; +import java.util.*; import java.util.concurrent.Callable; -public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { +public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; private final boolean isSixDirection; + private final List tagsCanSurviveOn; + private final Set blockStatesCanSurviveOn; + private final Set customBlocksCansSurviveOn; + private final boolean blacklistMode; - public DirectionalAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty, boolean isSixDirection) { - super(customBlock); + public DirectionalAttachedBlockBehavior(CustomBlock customBlock, + Property facingProperty, + boolean isSixDirection, + int delay, + boolean blacklist, + List tagsCanSurviveOn, + Set blockStatesCanSurviveOn, + Set customBlocksCansSurviveOn) { + super(customBlock, delay); this.facingProperty = facingProperty; this.isSixDirection = isSixDirection; + this.tagsCanSurviveOn = tagsCanSurviveOn; + this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; + this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; + this.blacklistMode = blacklist; } @Override @@ -51,8 +69,8 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { } @Override - public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + protected boolean canSurvive(Object thisBlock, Object blockState, Object world, Object pos) throws Exception { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(blockState).orElse(null); if (state == null) return false; DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null); if (behavior == null) return false; @@ -62,10 +80,34 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { } else { direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection(); } - BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction); + BlockPos blockPos = LocationUtils.fromBlockPos(pos).relative(direction); Object nmsPos = LocationUtils.toBlockPos(blockPos); - Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos); - return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL); + Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, nmsPos); + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, world, nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL) + && mayPlaceOn(nmsState); + } + + private boolean mayPlaceOn(Object state) { + for (Object tag : this.tagsCanSurviveOn) { + if (FastNMS.INSTANCE.method$BlockStateBase$is(state, tag)) { + return !this.blacklistMode; + } + } + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); + if (optionalCustomState.isEmpty()) { + if (!this.blockStatesCanSurviveOn.isEmpty() && this.blockStatesCanSurviveOn.contains(state)) { + return !this.blacklistMode; + } + } else { + ImmutableBlockState belowCustomState = optionalCustomState.get(); + if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) { + return !this.blacklistMode; + } + if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) { + return !this.blacklistMode; + } + } + return this.blacklistMode; } @SuppressWarnings("unchecked") @@ -101,7 +143,37 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { if (!(isHorizontalDirection || isDirection)) { throw new LocalizedResourceConfigException("warning.config.block.behavior.surface_attached.missing_facing"); } - return new DirectionalAttachedBlockBehavior(block, facing, isDirection); + Tuple, Set, Set> tuple = readTagsAndState(arguments); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist"); + return new DirectionalAttachedBlockBehavior(block, facing, isDirection, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right()); } } + + @SuppressWarnings("DuplicatedCode") + private static Tuple, Set, Set> readTagsAndState(Map arguments) { + List mcTags = new ArrayList<>(); + for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("wall-block-tags", List.of()))) { + mcTags.add(BlockTags.getOrCreate(Key.of(tag))); + } + Set mcBlocks = new HashSet<>(); + Set customBlocks = new HashSet<>(); + for (String blockStateStr : MiscUtils.getAsStringList(arguments.getOrDefault("wall-blocks", List.of()))) { + int index = blockStateStr.indexOf('['); + Key blockType = index != -1 ? Key.from(blockStateStr.substring(0, index)) : Key.from(blockStateStr); + Material material = Registry.MATERIAL.get(new NamespacedKey(blockType.namespace(), blockType.value())); + if (material != null) { + if (index == -1) { + // vanilla + mcBlocks.addAll(BlockStateUtils.getAllVanillaBlockStates(blockType)); + } else { + mcBlocks.add(BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(blockStateStr))); + } + } else { + // custom maybe + customBlocks.add(blockStateStr); + } + } + return new Tuple<>(mcTags, mcBlocks, customBlocks); + } } From 9c2bedff00e9b45048c86b31f4118c0013f0572e Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Thu, 18 Sep 2025 02:56:16 +0800 Subject: [PATCH 144/226] =?UTF-8?q?=E6=94=B9=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/DirectionalAttachedBlockBehavior.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index 5e8383ef5..1b33c2747 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -153,12 +153,12 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh @SuppressWarnings("DuplicatedCode") private static Tuple, Set, Set> readTagsAndState(Map arguments) { List mcTags = new ArrayList<>(); - for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("wall-block-tags", List.of()))) { + for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("attached-block-tags", List.of()))) { mcTags.add(BlockTags.getOrCreate(Key.of(tag))); } Set mcBlocks = new HashSet<>(); Set customBlocks = new HashSet<>(); - for (String blockStateStr : MiscUtils.getAsStringList(arguments.getOrDefault("wall-blocks", List.of()))) { + for (String blockStateStr : MiscUtils.getAsStringList(arguments.getOrDefault("attached-blocks", List.of()))) { int index = blockStateStr.indexOf('['); Key blockType = index != -1 ? Key.from(blockStateStr.substring(0, index)) : Key.from(blockStateStr); Material material = Registry.MATERIAL.get(new NamespacedKey(blockType.namespace(), blockType.value())); From 2f1ec1ee698c060c5122b6bc6ff5842f4ab77570 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Thu, 18 Sep 2025 23:50:08 +0800 Subject: [PATCH 145/226] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E7=AE=A1=E7=90=86=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 10 +- .../bukkit/plugin/BukkitCraftEngine.java | 5 +- .../plugin/network/BukkitNetworkManager.java | 3133 ++++++++++++++++- .../plugin/network/PacketConsumers.java | 2703 -------------- .../handler/BlockDisplayPacketHandler.java | 9 +- .../handler/CommonItemPacketHandler.java | 2 +- .../handler/EndermanPacketHandler.java | 9 +- .../handler/MinecartPacketHandler.java | 16 +- .../handler/PrimedTNTPacketHandler.java | 9 +- .../plugin/network/id/PacketIdFinder.java | 75 - .../plugin/network/id/PacketIds1_20.java | 104 +- .../plugin/network/id/PacketIds1_20_5.java | 95 +- .../plugin/network/id/PlayPacketIdHelper.java | 61 + .../listener/ByteBufferPacketListener.java | 13 + .../network/listener/NMSPacketListener.java | 13 + .../core/plugin/network/NetworkManager.java | 2 + .../core/plugin/network/PacketFlow.java | 6 + gradle.properties | 12 +- 18 files changed, 3236 insertions(+), 3041 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListener.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/NMSPacketListener.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/network/PacketFlow.java 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 a63b539be..7dfe46fa0 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 @@ -10,7 +10,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator; -import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; @@ -102,7 +102,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { } this.stateId2ImmutableBlockStates = new ImmutableBlockState[this.customBlockCount]; Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - this.resetPacketConsumers(); + this.resetPacketListeners(); } @Override @@ -149,7 +149,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedLoad() { - this.resetPacketConsumers(); + this.resetPacketListeners(); super.delayedLoad(); } @@ -263,14 +263,14 @@ public final class BukkitBlockManager extends AbstractBlockManager { holder.bindValue(emptyBlock); } - private void resetPacketConsumers() { + private void resetPacketListeners() { Map finalMapping = new HashMap<>(this.blockAppearanceMapper); int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); for (int custom : this.internalId2StateId.values()) { finalMapping.put(custom, stoneId); } finalMapping.putAll(this.tempBlockAppearanceConvertor); - PacketConsumers.initBlocks(finalMapping, RegistryUtils.currentBlockRegistrySize()); + BukkitNetworkManager.instance().registerBlockStatePacketListeners(finalMapping, RegistryUtils.currentBlockRegistrySize()); } private void initVanillaRegistry() { 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 6633b1fa3..e00b9b64b 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 @@ -20,12 +20,10 @@ import net.momirealms.craftengine.bukkit.plugin.command.BukkitSenderFactory; import net.momirealms.craftengine.bukkit.plugin.gui.BukkitGuiManager; import net.momirealms.craftengine.bukkit.plugin.injector.*; import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; -import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; import net.momirealms.craftengine.bukkit.plugin.scheduler.BukkitSchedulerAdapter; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.sound.BukkitSoundManager; import net.momirealms.craftengine.bukkit.util.EventUtils; -import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.item.ItemManager; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -146,8 +144,8 @@ public class BukkitCraftEngine extends CraftEngine { throw new InjectionException("Error initializing ProtectedFieldVisitor", e); } super.onPluginLoad(); - super.blockManager.init(); super.networkManager = new BukkitNetworkManager(this); + super.blockManager.init(); super.itemManager = new BukkitItemManager(this); this.successfullyLoaded = true; super.compatibilityManager().onLoad(); @@ -191,7 +189,6 @@ public class BukkitCraftEngine extends CraftEngine { BukkitItemBehaviors.init(); BukkitHitBoxTypes.init(); BukkitBlockEntityElementConfigs.init(); - PacketConsumers.initEntities(RegistryUtils.currentEntityTypeRegistrySize()); super.packManager = new BukkitPackManager(this); super.senderFactory = new BukkitSenderFactory(this); super.recipeManager = new BukkitRecipeManager(this); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index f97d36b3e..370b57d79 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -1,30 +1,92 @@ package net.momirealms.craftengine.bukkit.plugin.network; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.mojang.authlib.GameProfile; +import com.mojang.datafixers.util.Either; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; +import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; +import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; +import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent; +import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; +import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.item.behavior.FurnitureItemBehavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIdFinder; +import net.momirealms.craftengine.bukkit.plugin.injector.ProtectedFieldVisitor; +import net.momirealms.craftengine.bukkit.plugin.network.handler.*; +import net.momirealms.craftengine.bukkit.plugin.network.id.PlayPacketIdHelper; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5; +import net.momirealms.craftengine.bukkit.plugin.network.listener.ByteBufferPacketListener; +import net.momirealms.craftengine.bukkit.plugin.network.listener.NMSPacketListener; +import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; +import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload; import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; +import net.momirealms.craftengine.bukkit.plugin.network.payload.UnknownPayload; import net.momirealms.craftengine.bukkit.plugin.reflection.leaves.LeavesReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.LibraryReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.plugin.user.FakeBukkitServerPlayer; -import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.bukkit.util.*; +import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; +import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; +import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.font.FontManager; +import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipeHolder; +import net.momirealms.craftengine.core.item.recipe.network.modern.RecipeBookEntry; +import net.momirealms.craftengine.core.item.recipe.network.modern.display.RecipeDisplay; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.plugin.network.*; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; -import org.bukkit.Bukkit; +import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.CEChunk; +import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.chunk.Palette; +import net.momirealms.craftengine.core.world.chunk.PalettedContainer; +import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; +import net.momirealms.craftengine.core.world.chunk.packet.MCSection; +import net.momirealms.craftengine.core.world.collision.AABB; +import net.momirealms.sparrow.nbt.Tag; +import org.bukkit.*; +import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -32,49 +94,33 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.messaging.PluginMessageListener; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { private static BukkitNetworkManager instance; - private static final Map, TriConsumer> NMS_PACKET_HANDLERS = new HashMap<>(); - // only for game stage for the moment - private static BiConsumer[] S2C_GAME_BYTE_BUFFER_PACKET_HANDLERS; - private static BiConsumer[] C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS; + private final BukkitCraftEngine plugin; + private final Map, NMSPacketListener> nmsPacketListeners = new IdentityHashMap<>(128); - private static void registerNMSPacketConsumer(final TriConsumer function, @Nullable Class packet) { - if (packet == null) return; - NMS_PACKET_HANDLERS.put(packet, function); - } - - private static void registerS2CByteBufPacketConsumer(final BiConsumer function, int id) { - if (id == -1) return; - if (id < 0 || id >= S2C_GAME_BYTE_BUFFER_PACKET_HANDLERS.length) { - throw new IllegalArgumentException("Invalid packet id: " + id); - } - S2C_GAME_BYTE_BUFFER_PACKET_HANDLERS[id] = function; - } - - private static void registerC2SByteBufPacketConsumer(final BiConsumer function, int id) { - if (id == -1) return; - if (id < 0 || id >= C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS.length) { - throw new IllegalArgumentException("Invalid packet id: " + id); - } - C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS[id] = function; - } + private final ByteBufferPacketListener[] s2cGamePacketListeners; + private final ByteBufferPacketListener[] c2sGamePacketListeners; private final TriConsumer packetConsumer; private final TriConsumer, Object> packetsConsumer; private final TriConsumer immediatePacketConsumer; private final TriConsumer, Runnable> immediatePacketsConsumer; - private final BukkitCraftEngine plugin; private final Map users = new ConcurrentHashMap<>(); private final Map onlineUsers = new ConcurrentHashMap<>(); @@ -89,23 +135,24 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private static final String PACKET_ENCODER = "craftengine_encoder"; private static final String PACKET_DECODER = "craftengine_decoder"; - private static boolean hasModelEngine; - private static boolean hasViaVersion; + private final boolean hasModelEngine; + private final boolean hasViaVersion; + + private int[] blockStateRemapper; + private int[] modBlockStateRemapper; @SuppressWarnings("unchecked") public BukkitNetworkManager(BukkitCraftEngine plugin) { instance = this; - S2C_GAME_BYTE_BUFFER_PACKET_HANDLERS = new BiConsumer[PacketIdFinder.s2cGamePackets()]; - C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS = new BiConsumer[PacketIdFinder.c2sGamePackets()]; - Arrays.fill(S2C_GAME_BYTE_BUFFER_PACKET_HANDLERS, Handlers.DO_NOTHING); - Arrays.fill(C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS, Handlers.DO_NOTHING); - hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null; - hasViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; + this.s2cGamePacketListeners = new ByteBufferPacketListener[PlayPacketIdHelper.count(PacketFlow.CLIENTBOUND)]; + this.c2sGamePacketListeners = new ByteBufferPacketListener[PlayPacketIdHelper.count(PacketFlow.SERVERBOUND)]; + this.hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null; + this.hasViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; this.plugin = plugin; // set up packet id this.packetIds = VersionHelper.isOrAbove1_20_5() ? new PacketIds1_20_5() : new PacketIds1_20(); // register packet handlers - this.registerPacketHandlers(); + this.registerPacketListeners(); PayloadHelper.registerDataTypes(); // set up packet senders this.packetConsumer = FastNMS.INSTANCE::method$Connection$send; @@ -156,6 +203,32 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes return instance; } + @Override + public int remapBlockState(int stateId, boolean enableMod) { + return enableMod ? this.modBlockStateRemapper[stateId] : this.blockStateRemapper[stateId]; + } + + private void registerNMSPacketConsumer(final NMSPacketListener listener, @Nullable Class packet) { + if (packet == null) return; + this.nmsPacketListeners.put(packet, listener); + } + + private void registerS2CGamePacketListener(final ByteBufferPacketListener function, int id) { + if (id == -1) return; + if (id < 0 || id >= this.s2cGamePacketListeners.length) { + throw new IllegalArgumentException("Invalid packet id: " + id); + } + this.s2cGamePacketListeners[id] = function; + } + + private void registerC2SGamePacketListener(final ByteBufferPacketListener function, int id) { + if (id == -1) return; + if (id < 0 || id >= this.c2sGamePacketListeners.length) { + throw new IllegalArgumentException("Invalid packet id: " + id); + } + this.c2sGamePacketListeners[id] = function; + } + public void addFakePlayer(Player player) { FakeBukkitServerPlayer fakePlayer = new FakeBukkitServerPlayer(this.plugin); fakePlayer.setPlayer(player); @@ -189,65 +262,138 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - private void registerPacketHandlers() { - registerNMSPacketConsumer(PacketConsumers.PLAYER_INFO_UPDATE, NetworkReflections.clazz$ClientboundPlayerInfoUpdatePacket); - registerNMSPacketConsumer(PacketConsumers.PLAYER_ACTION, NetworkReflections.clazz$ServerboundPlayerActionPacket); - registerNMSPacketConsumer(PacketConsumers.SWING_HAND, NetworkReflections.clazz$ServerboundSwingPacket); - registerNMSPacketConsumer(PacketConsumers.HELLO_C2S, NetworkReflections.clazz$ServerboundHelloPacket); - registerNMSPacketConsumer(PacketConsumers.USE_ITEM_ON, NetworkReflections.clazz$ServerboundUseItemOnPacket); - registerNMSPacketConsumer(PacketConsumers.PICK_ITEM_FROM_BLOCK, NetworkReflections.clazz$ServerboundPickItemFromBlockPacket); - registerNMSPacketConsumer(PacketConsumers.SET_CREATIVE_SLOT, NetworkReflections.clazz$ServerboundSetCreativeModeSlotPacket); - registerNMSPacketConsumer(PacketConsumers.LOGIN, NetworkReflections.clazz$ClientboundLoginPacket); - registerNMSPacketConsumer(PacketConsumers.RESPAWN, NetworkReflections.clazz$ClientboundRespawnPacket); - registerNMSPacketConsumer(PacketConsumers.SYNC_ENTITY_POSITION, NetworkReflections.clazz$ClientboundEntityPositionSyncPacket); - registerNMSPacketConsumer(PacketConsumers.PICK_ITEM_FROM_ENTITY, NetworkReflections.clazz$ServerboundPickItemFromEntityPacket); - registerNMSPacketConsumer(PacketConsumers.RENAME_ITEM, NetworkReflections.clazz$ServerboundRenameItemPacket); - registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, NetworkReflections.clazz$ServerboundSignUpdatePacket); - registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, NetworkReflections.clazz$ServerboundEditBookPacket); - registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD_1_20_2, VersionHelper.isOrAbove1_20_2() ? NetworkReflections.clazz$ServerboundCustomPayloadPacket : null); - registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_RESPONSE, NetworkReflections.clazz$ServerboundResourcePackPacket); - registerNMSPacketConsumer(PacketConsumers.ENTITY_EVENT, NetworkReflections.clazz$ClientboundEntityEventPacket); - registerNMSPacketConsumer(PacketConsumers.MOVE_POS_AND_ROTATE_ENTITY, NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot); - registerNMSPacketConsumer(PacketConsumers.MOVE_POS_ENTITY, NetworkReflections.clazz$ClientboundMoveEntityPacket$Pos); - registerNMSPacketConsumer(PacketConsumers.ROTATE_HEAD, NetworkReflections.clazz$ClientboundRotateHeadPacket); - registerNMSPacketConsumer(PacketConsumers.SET_ENTITY_MOTION, NetworkReflections.clazz$ClientboundSetEntityMotionPacket); - registerNMSPacketConsumer(PacketConsumers.FINISH_CONFIGURATION, NetworkReflections.clazz$ClientboundFinishConfigurationPacket); - registerNMSPacketConsumer(PacketConsumers.LOGIN_FINISHED, NetworkReflections.clazz$ClientboundLoginFinishedPacket); - registerNMSPacketConsumer(PacketConsumers.UPDATE_TAGS, NetworkReflections.clazz$ClientboundUpdateTagsPacket); - registerNMSPacketConsumer(PacketConsumers.CONTAINER_CLICK_1_21_5, VersionHelper.isOrAbove1_21_5() ? NetworkReflections.clazz$ServerboundContainerClickPacket : null); - registerS2CByteBufPacketConsumer(PacketConsumers.FORGET_LEVEL_CHUNK, this.packetIds.clientboundForgetLevelChunkPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, this.packetIds.clientboundLevelChunkWithLightPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_21_4() ? PacketConsumers.LEVEL_PARTICLE_1_21_4 : (VersionHelper.isOrAbove1_20_5() ? PacketConsumers.LEVEL_PARTICLE_1_20_5 : PacketConsumers.LEVEL_PARTICLE_1_20), this.packetIds.clientboundLevelParticlesPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.LEVEL_EVENT, this.packetIds.clientboundLevelEventPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.OPEN_SCREEN_1_20_3 : PacketConsumers.OPEN_SCREEN_1_20, this.packetIds.clientboundOpenScreenPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_TITLE_TEXT_1_20_3 : PacketConsumers.SET_TITLE_TEXT_1_20, this.packetIds.clientboundSetTitleTextPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_SUBTITLE_TEXT_1_20_3 : PacketConsumers.SET_SUBTITLE_TEXT_1_20, this.packetIds.clientboundSetSubtitleTextPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_ACTIONBAR_TEXT_1_20_3 : PacketConsumers.SET_ACTIONBAR_TEXT_1_20, this.packetIds.clientboundSetActionBarTextPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.BOSS_EVENT_1_20_3 : PacketConsumers.BOSS_EVENT_1_20, this.packetIds.clientboundBossEventPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SYSTEM_CHAT_1_20_3 : PacketConsumers.SYSTEM_CHAT_1_20, this.packetIds.clientboundSystemChatPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.TAB_LIST_1_20_3 : PacketConsumers.TAB_LIST_1_20, this.packetIds.clientboundTabListPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.TEAM_1_20_3 : PacketConsumers.TEAM_1_20, this.packetIds.clientboundSetPlayerTeamPacket()); - registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_OBJECTIVE_1_20_3 : PacketConsumers.SET_OBJECTIVE_1_20, this.packetIds.clientboundSetObjectivePacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SET_SCORE_1_20_3, VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1); - registerS2CByteBufPacketConsumer(PacketConsumers.ADD_RECIPE_BOOK, this.packetIds.clientboundRecipeBookAddPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.PLACE_GHOST_RECIPE, this.packetIds.clientboundPlaceGhostRecipePacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.UPDATE_RECIPES, this.packetIds.clientboundUpdateRecipesPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.UPDATE_ADVANCEMENTS, this.packetIds.clientboundUpdateAdvancementsPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.REMOVE_ENTITY, this.packetIds.clientboundRemoveEntitiesPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.ADD_ENTITY, this.packetIds.clientboundAddEntityPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SOUND, this.packetIds.clientboundSoundPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SET_ENTITY_DATA, this.packetIds.clientboundSetEntityDataPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.CONTAINER_SET_CONTENT, this.packetIds.clientboundContainerSetContentPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.CONTAINER_SET_SLOT, this.packetIds.clientboundContainerSetSlotPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SET_CURSOR_ITEM, this.packetIds.clientboundSetCursorItemPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SET_EQUIPMENT, this.packetIds.clientboundSetEquipmentPacket()); - registerS2CByteBufPacketConsumer(PacketConsumers.SET_PLAYER_INVENTORY_1_21_2, this.packetIds.clientboundSetPlayerInventoryPacket()); - registerC2SByteBufPacketConsumer(PacketConsumers.SET_CREATIVE_MODE_SLOT, this.packetIds.serverboundSetCreativeModeSlotPacket()); - registerC2SByteBufPacketConsumer(PacketConsumers.CONTAINER_CLICK_1_20, VersionHelper.isOrAbove1_21_5() ? -1 : this.packetIds.serverboundContainerClickPacket()); - registerC2SByteBufPacketConsumer(PacketConsumers.INTERACT_ENTITY, this.packetIds.serverboundInteractPacket()); - registerC2SByteBufPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD_1_20, VersionHelper.isOrAbove1_20_2() ? -1 : this.packetIds.serverboundCustomPayloadPacket()); + public void registerBlockStatePacketListeners(Map map, int registrySize) { + int[] newMappings = new int[registrySize]; + for (int i = 0; i < registrySize; i++) { + newMappings[i] = i; + } + int[] newMappingsMOD = Arrays.copyOf(newMappings, registrySize); + for (Map.Entry entry : map.entrySet()) { + newMappings[entry.getKey()] = entry.getValue(); + if (BlockStateUtils.isVanillaBlock((int) entry.getKey())) { + newMappingsMOD[entry.getKey()] = entry.getValue(); + } + } + for (int i = 0; i < newMappingsMOD.length; i++) { + if (BlockStateUtils.isVanillaBlock(i)) { + newMappingsMOD[i] = newMappings[i]; + } + } + this.blockStateRemapper = newMappings; + this.modBlockStateRemapper = newMappingsMOD; + registerS2CGamePacketListener(new LevelChunkWithLightListener(newMappings, newMappingsMOD, registrySize, RegistryUtils.currentBiomeRegistrySize()), this.packetIds.clientboundLevelChunkWithLightPacket()); + registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket()); + registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket()); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_21_4() ? + new LevelParticleListener1_21_4(newMappings, newMappingsMOD) : + (VersionHelper.isOrAbove1_20_5() ? + new LevelParticleListener1_20_5(newMappings, newMappingsMOD) : + new LevelParticleListener1_20(newMappings, newMappingsMOD)), + this.packetIds.clientboundLevelParticlesPacket() + ); + registerS2CGamePacketListener(new LevelEventListener(newMappings, newMappingsMOD), this.packetIds.clientboundLevelEventPacket()); + } + + private void registerPacketListeners() { + registerNMSPacketConsumer(new PlayerInfoUpdateListener(), NetworkReflections.clazz$ClientboundPlayerInfoUpdatePacket); + registerNMSPacketConsumer(new PlayerActionListener(), NetworkReflections.clazz$ServerboundPlayerActionPacket); + registerNMSPacketConsumer(new SwingListener(), NetworkReflections.clazz$ServerboundSwingPacket); + registerNMSPacketConsumer(new HelloListener(), NetworkReflections.clazz$ServerboundHelloPacket); + registerNMSPacketConsumer(new UseItemOnListener(), NetworkReflections.clazz$ServerboundUseItemOnPacket); + registerNMSPacketConsumer(new PickItemFromBlockListener(), NetworkReflections.clazz$ServerboundPickItemFromBlockPacket); + registerNMSPacketConsumer(new PickItemFromEntityListener(), NetworkReflections.clazz$ServerboundPickItemFromEntityPacket); + registerNMSPacketConsumer(new SetCreativeSlotListener(), NetworkReflections.clazz$ServerboundSetCreativeModeSlotPacket); + registerNMSPacketConsumer(new LoginListener(), NetworkReflections.clazz$ClientboundLoginPacket); + registerNMSPacketConsumer(new RespawnListener(), NetworkReflections.clazz$ClientboundRespawnPacket); + registerNMSPacketConsumer(new SyncEntityPositionListener(), NetworkReflections.clazz$ClientboundEntityPositionSyncPacket); + registerNMSPacketConsumer(new RenameItemListener(), NetworkReflections.clazz$ServerboundRenameItemPacket); + registerNMSPacketConsumer(new SignUpdateListener(), NetworkReflections.clazz$ServerboundSignUpdatePacket); + registerNMSPacketConsumer(new EditBookListener(), NetworkReflections.clazz$ServerboundEditBookPacket); + registerNMSPacketConsumer(new CustomPayloadListener1_20_2(), VersionHelper.isOrAbove1_20_2() ? NetworkReflections.clazz$ServerboundCustomPayloadPacket : null); + registerNMSPacketConsumer(new ResourcePackResponseListener(), NetworkReflections.clazz$ServerboundResourcePackPacket); + registerNMSPacketConsumer(new EntityEventListener(), NetworkReflections.clazz$ClientboundEntityEventPacket); + registerNMSPacketConsumer(new MovePosAndRotateEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot); + registerNMSPacketConsumer(new MovePosEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$Pos); + registerNMSPacketConsumer(new RotateHeadListener(), NetworkReflections.clazz$ClientboundRotateHeadPacket); + registerNMSPacketConsumer(new SetEntityMotionListener(), NetworkReflections.clazz$ClientboundSetEntityMotionPacket); + registerNMSPacketConsumer(new FinishConfigurationListener(), NetworkReflections.clazz$ClientboundFinishConfigurationPacket); + registerNMSPacketConsumer(new LoginFinishedListener(), NetworkReflections.clazz$ClientboundLoginFinishedPacket); + registerNMSPacketConsumer(new UpdateTagsListener(), NetworkReflections.clazz$ClientboundUpdateTagsPacket); + registerNMSPacketConsumer(new ContainerClickListener1_21_5(), VersionHelper.isOrAbove1_21_5() ? NetworkReflections.clazz$ServerboundContainerClickPacket : null); + registerS2CGamePacketListener(new ForgetLevelChunkListener(), this.packetIds.clientboundForgetLevelChunkPacket()); + registerS2CGamePacketListener(new SetScoreListener1_20_3(), VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1); + registerS2CGamePacketListener(new AddRecipeBookListener(), this.packetIds.clientboundRecipeBookAddPacket()); + registerS2CGamePacketListener(new PlaceGhostRecipeListener(), this.packetIds.clientboundPlaceGhostRecipePacket()); + registerS2CGamePacketListener(new UpdateRecipesListener(), this.packetIds.clientboundUpdateRecipesPacket()); + registerS2CGamePacketListener(new UpdateAdvancementsListener(), this.packetIds.clientboundUpdateAdvancementsPacket()); + registerS2CGamePacketListener(new RemoveEntityListener(), this.packetIds.clientboundRemoveEntitiesPacket()); + registerS2CGamePacketListener(new SoundListener(), this.packetIds.clientboundSoundPacket()); + registerS2CGamePacketListener(new ContainerSetContentListener(), this.packetIds.clientboundContainerSetContentPacket()); + registerS2CGamePacketListener(new ContainerSetSlotListener(), this.packetIds.clientboundContainerSetSlotPacket()); + registerS2CGamePacketListener(new SetCursorItemListener(), this.packetIds.clientboundSetCursorItemPacket()); + registerS2CGamePacketListener(new SetEquipmentListener(), this.packetIds.clientboundSetEquipmentPacket()); + registerS2CGamePacketListener(new SetPlayerInventoryListener1_21_2(), VersionHelper.isOrAbove1_21_2() ? this.packetIds.clientboundSetPlayerInventoryPacket() : -1); + registerS2CGamePacketListener(new SetEntityDataListener(), this.packetIds.clientboundSetEntityDataPacket()); + registerC2SGamePacketListener(new SetCreativeModeSlotListener(), this.packetIds.serverboundSetCreativeModeSlotPacket()); + registerC2SGamePacketListener(new ContainerClick1_20(), VersionHelper.isOrAbove1_21_5() ? -1 : this.packetIds.serverboundContainerClickPacket()); + registerC2SGamePacketListener(new InteractEntityListener(), this.packetIds.serverboundInteractPacket()); + registerC2SGamePacketListener(new CustomPayloadListener1_20(), VersionHelper.isOrAbove1_20_2() ? -1 : this.packetIds.serverboundCustomPayloadPacket()); + registerS2CGamePacketListener(new AddEntityListener(RegistryUtils.currentEntityTypeRegistrySize()), this.packetIds.clientboundAddEntityPacket()); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new OpenScreenListener1_20_3() : + new OpenScreenListener1_20(), + this.packetIds.clientboundOpenScreenPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new SystemChatListener1_20_3() : + new SystemChatListener1_20(), + this.packetIds.clientboundSystemChatPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new SetActionBarListener1_20_3() : + new SetActionBarListener1_20(), + this.packetIds.clientboundSetActionBarTextPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new TabListListener1_20_3() : + new TabListListener1_20(), + this.packetIds.clientboundTabListPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new SetTitleListener1_20_3() : + new SetTitleListener1_20(), + this.packetIds.clientboundSetTitleTextPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new SetSubtitleListener1_20_3() : + new SetSubtitleListener1_20(), + this.packetIds.clientboundSetSubtitleTextPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new BossEventListener1_20_3() : + new BossEventListener1_20(), + this.packetIds.clientboundBossEventPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new TeamListener1_20_3() : + new TeamListener1_20(), + this.packetIds.clientboundSetPlayerTeamPacket() + ); + registerS2CGamePacketListener( + VersionHelper.isOrAbove1_20_3() ? + new SetObjectiveListener1_20_3() : + new SetObjectiveListener1_20(), + this.packetIds.clientboundSetObjectivePacket() + ); } @EventHandler(priority = EventPriority.LOWEST) @@ -383,11 +529,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - public static boolean hasModelEngine() { + public boolean hasModelEngine() { return hasModelEngine; } - public static boolean hasViaVersion() { + public boolean hasViaVersion() { return hasViaVersion; } @@ -709,7 +855,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private void onNMSPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { Debugger.PACKET.debug(() -> "[C->S]" + packet.getClass()); - handleNMSPacket(user, event, packet); + handleReceiveNMSPacket(user, event, packet); } private void onNMSPacketSend(NetWorkUser player, NMSPacketEvent event, Object packet) { @@ -720,25 +866,54 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } else { Debugger.PACKET.debug(() -> "[S->C]" + packet.getClass()); - handleNMSPacket(player, event, packet); + handleSendNMSPacket(player, event, packet); } } - protected void handleNMSPacket(NetWorkUser user, NMSPacketEvent event, Object packet) { - Optional.ofNullable(NMS_PACKET_HANDLERS.get(packet.getClass())) - .ifPresent(function -> function.accept(user, event, packet)); + protected void handleReceiveNMSPacket(NetWorkUser user, NMSPacketEvent event, Object packet) { + NMSPacketListener nmsPacketListener = this.nmsPacketListeners.get(packet.getClass()); + if (nmsPacketListener != null) { + try { + nmsPacketListener.onPacketReceive(user, event, packet); + } catch (Throwable t) { + this.plugin.logger().warn("An error occurred when handling packet " + packet.getClass(), t); + } + } + } + + protected void handleSendNMSPacket(NetWorkUser user, NMSPacketEvent event, Object packet) { + NMSPacketListener nmsPacketListener = this.nmsPacketListeners.get(packet.getClass()); + if (nmsPacketListener != null) { + try { + nmsPacketListener.onPacketSend(user, event, packet); + } catch (Throwable t) { + this.plugin.logger().warn("An error occurred when handling packet " + packet.getClass(), t); + } + } } protected void handleS2CByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { int packetID = event.packetID(); - Optional.ofNullable(S2C_GAME_BYTE_BUFFER_PACKET_HANDLERS[packetID]) - .ifPresent(function -> function.accept(user, event)); + ByteBufferPacketListener s2cGamePacketListener = this.s2cGamePacketListeners[packetID]; + if (s2cGamePacketListener != null) { + try { + s2cGamePacketListener.onPacketSend(user, event); + } catch (Throwable t) { + this.plugin.logger().warn("An error occurred when handling sent packet id: " + packetID, t); + } + } } protected void handleC2SByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { int packetID = event.packetID(); - Optional.ofNullable(C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS[packetID]) - .ifPresent(function -> function.accept(user, event)); + ByteBufferPacketListener c2sGamePacketListener = this.c2sGamePacketListeners[packetID]; + if (c2sGamePacketListener != null) { + try { + c2sGamePacketListener.onPacketReceive(user, event); + } catch (Throwable t) { + this.plugin.logger().warn("An error occurred when handling received packet id: " + packetID, t); + } + } } private void compress(ChannelHandlerContext ctx, ByteBuf input) { @@ -784,13 +959,2741 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes return output; } - @FunctionalInterface - public interface Handlers extends BiConsumer { - Handlers DO_NOTHING = doNothing(); + /* + * + * + * Packet Listener Implementations + * + * + */ + public static class HelloListener implements NMSPacketListener { - static Handlers doNothing() { - return (user, byteBufPacketEvent) -> { + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + String name; + try { + name = (String) NetworkReflections.methodHandle$ServerboundHelloPacket$nameGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().severe("Failed to get name from ServerboundHelloPacket", t); + return; + } + player.setUnverifiedName(name); + if (VersionHelper.isOrAbove1_20_2()) { + UUID uuid; + try { + uuid = (UUID) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().severe("Failed to get uuid from ServerboundHelloPacket", t); + return; + } + player.setUnverifiedUUID(uuid); + } else { + Optional uuid; + try { + // noinspection unchecked + uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().severe("Failed to get uuid from ServerboundHelloPacket", t); + return; + } + if (uuid.isPresent()) { + player.setUnverifiedUUID(uuid.get()); + } else { + player.setUnverifiedUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); + } + } + } + } + + public static class PlayerActionListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + Player platformPlayer = player.platformPlayer(); + World world = platformPlayer.getWorld(); + Object blockPos = FastNMS.INSTANCE.field$ServerboundPlayerActionPacket$pos(packet); + BlockPos pos = LocationUtils.fromBlockPos(blockPos); + if (VersionHelper.isFolia()) { + platformPlayer.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), (t) -> { + try { + handlePlayerActionPacketOnMainThread(player, world, pos, packet); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundPlayerActionPacket", e); + } + }, () -> {}); + } else { + handlePlayerActionPacketOnMainThread(player, world, pos, packet); + } + } + + private static void handlePlayerActionPacketOnMainThread(BukkitServerPlayer player, World world, BlockPos pos, Object packet) { + Object action = FastNMS.INSTANCE.field$ServerboundPlayerActionPacket$action(packet); + if (action == NetworkReflections.instance$ServerboundPlayerActionPacket$Action$START_DESTROY_BLOCK) { + Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world); + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(serverLevel, LocationUtils.toBlockPos(pos)); + int stateId = BlockStateUtils.blockStateToId(blockState); + // not a custom block + if (BlockStateUtils.isVanillaBlock(stateId)) { + if (Config.enableSoundSystem()) { + Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(blockState); + if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) { + player.startMiningBlock(pos, blockState, null); + return; + } + } + if (player.isMiningBlock()) { + player.stopMiningBlock(); + } else { + player.setClientSideCanBreakBlock(true); + } + return; + } + if (player.isAdventureMode()) { + if (Config.simplifyAdventureBreakCheck()) { + ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); + if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) { + player.preventMiningBlock(); + return; + } + } else { + if (!player.canBreak(pos, null)) { + player.preventMiningBlock(); + return; + } + } + } + player.startMiningBlock(pos, blockState, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId)); + } else if (action == NetworkReflections.instance$ServerboundPlayerActionPacket$Action$ABORT_DESTROY_BLOCK) { + if (player.isMiningBlock()) { + player.abortMiningBlock(); + } + } else if (action == NetworkReflections.instance$ServerboundPlayerActionPacket$Action$STOP_DESTROY_BLOCK) { + if (player.isMiningBlock()) { + player.stopMiningBlock(); + } + } + } + } + + public static class SwingListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + if (!player.isMiningBlock()) return; + Object hand = FastNMS.INSTANCE.field$ServerboundSwingPacket$hand(packet); + if (hand == CoreReflections.instance$InteractionHand$MAIN_HAND) { + player.onSwingHand(); + } + } + } + + public static class UseItemOnListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + if (player.isMiningBlock()) { + player.stopMiningBlock(); + } + } + } + + public static class PlayerInfoUpdateListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!Config.interceptPlayerInfo()) return; + List entries = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$entries(packet); + if (entries instanceof MarkedArrayList) { + return; + } + EnumSet> enums = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$actions(packet); + outer: { + for (Object entry : enums) { + if (entry == NetworkReflections.instance$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_DISPLAY_NAME) { + break outer; + } + } + return; + } + boolean isChanged = false; + List newEntries = new MarkedArrayList<>(); + for (Object entry : entries) { + Object mcComponent = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$Entry$displayName(entry); + if (mcComponent == null) { + newEntries.add(entry); + } else { + String json = ComponentUtils.minecraftToJson(mcComponent); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) { + newEntries.add(entry); + } else { + Object newEntry = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket$Entry(entry, + ComponentUtils.adventureToMinecraft(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + newEntries.add(newEntry); + isChanged = true; + } + } + } + if (isChanged) { + event.replacePacket(FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket(enums, newEntries)); + } + } + } + + public static class PickItemFromBlockListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + Player player = (Player) user.platformPlayer(); + if (player == null) return; + Object pos; + try { + pos = NetworkReflections.methodHandle$ServerboundPickItemFromBlockPacket$posGetter.invokeExact(packet); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to get pos from ServerboundPickItemFromBlockPacket", e); + return; + } + if (VersionHelper.isFolia()) { + int x = FastNMS.INSTANCE.field$Vec3i$x(pos); + int z = FastNMS.INSTANCE.field$Vec3i$z(pos); + BukkitCraftEngine.instance().scheduler().sync().run(() -> { + try { + handlePickItemFromBlockPacketOnMainThread(player, pos); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket on region thread", e); + } + }, player.getWorld(), x >> 4, z >> 4); + } else { + BukkitCraftEngine.instance().scheduler().sync().run(() -> { + try { + handlePickItemFromBlockPacketOnMainThread(player, pos); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket on main thread", e); + } + }); + } + } + + private static void handlePickItemFromBlockPacketOnMainThread(Player player, Object pos) throws Throwable { + Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()); + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(serverLevel, pos); + ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); + if (state == null) return; + Key itemId = state.settings().itemId(); + if (itemId == null) return; + pickItem(player, itemId, pos, null); + } + } + + public static class PickItemFromEntityListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + int entityId; + try { + entityId = (int) NetworkReflections.methodHandle$ServerboundPickItemFromEntityPacket$idGetter.invokeExact(packet); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to get entityId from ServerboundPickItemFromEntityPacket", e); + return; + } + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); + if (furniture == null) return; + Player player = (Player) user.platformPlayer(); + if (player == null) return; + if (VersionHelper.isFolia()) { + player.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), (t) -> { + try { + handlePickItemFromEntityOnMainThread(player, furniture); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket on region thread", e); + } + }, () -> {}); + } else { + BukkitCraftEngine.instance().scheduler().sync().run(() -> { + try { + handlePickItemFromEntityOnMainThread(player, furniture); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket on main thread", e); + } + }); + } + } + + private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Throwable { + Key itemId = furniture.config().settings().itemId(); + if (itemId == null) return; + pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.baseEntity())); + } + } + + private static void pickItem(Player player, Key itemId, @Nullable Object blockPos, @Nullable Object entity) throws Throwable { + ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, BukkitCraftEngine.instance().adapt(player)); + if (itemStack == null) { + CraftEngine.instance().logger().warn("Item: " + itemId + " is not a valid item"); + return; + } + assert CoreReflections.method$ServerGamePacketListenerImpl$tryPickItem != null; + if (VersionHelper.isOrAbove1_21_5()) { + CoreReflections.method$ServerGamePacketListenerImpl$tryPickItem.invoke( + CoreReflections.methodHandle$ServerPlayer$connectionGetter.invokeExact(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)), + FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack), blockPos, entity, true); + } else { + CoreReflections.method$ServerGamePacketListenerImpl$tryPickItem.invoke( + CoreReflections.methodHandle$ServerPlayer$connectionGetter.invokeExact(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)), FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)); + } + } + + + public static class SetCreativeSlotListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (VersionHelper.isOrAbove1_21_4()) return; + if (!user.isOnline()) return; + BukkitServerPlayer player = (BukkitServerPlayer) user; + if (VersionHelper.isFolia()) { + player.platformPlayer().getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), (t) -> { + try { + handleSetCreativeSlotPacketOnMainThread(player, packet); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket on region thread", e); + } + }, () -> {}); + } else { + try { + handleSetCreativeSlotPacketOnMainThread(player, packet); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket on main thread", e); + } + } + } + + private static void handleSetCreativeSlotPacketOnMainThread(BukkitServerPlayer player, Object packet) throws Throwable { + Player bukkitPlayer = player.platformPlayer(); + if (bukkitPlayer == null) return; + if (bukkitPlayer.getGameMode() != GameMode.CREATIVE) return; + int slot = VersionHelper.isOrAbove1_20_5() ? (short) NetworkReflections.methodHandle$ServerboundSetCreativeModeSlotPacket$slotNumGetter.invokeExact(packet) : (int) NetworkReflections.methodHandle$ServerboundSetCreativeModeSlotPacket$slotNumGetter.invokeExact(packet); + if (slot < 36 || slot > 44) return; + ItemStack item = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(NetworkReflections.methodHandle$ServerboundSetCreativeModeSlotPacket$itemStackGetter.invokeExact(packet)); + if (ItemStackUtils.isEmpty(item)) return; + if (slot - 36 != bukkitPlayer.getInventory().getHeldItemSlot()) { + return; + } + double interactionRange = player.getCachedInteractionRange(); + // do ray trace to get current block + RayTraceResult result = bukkitPlayer.rayTraceBlocks(interactionRange, FluidCollisionMode.NEVER); + if (result == null) return; + Block hitBlock = result.getHitBlock(); + if (hitBlock == null) return; + ImmutableBlockState state = CraftEngineBlocks.getCustomBlockState(hitBlock); + // not a custom block + if (state == null || state.isEmpty()) return; + Key itemId = state.settings().itemId(); + // no item available + if (itemId == null) return; + Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.vanillaBlockState().literalObject()); + Object vanillaBlockItem = FastNMS.INSTANCE.method$Block$asItem(vanillaBlock); + if (vanillaBlockItem == null) return; + Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey()); + Key blockItemId = KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.method$Registry$getKey(MBuiltInRegistries.ITEM, vanillaBlockItem)); + if (!addItemId.equals(blockItemId)) return; + ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, player); + if (ItemStackUtils.isEmpty(itemStack)) { + CraftEngine.instance().logger().warn("Item: " + itemId + " is not a valid item"); + return; + } + PlayerInventory inventory = bukkitPlayer.getInventory(); + int sameItemSlot = -1; + int emptySlot = -1; + for (int i = 0; i < 9 + 27; i++) { + ItemStack invItem = inventory.getItem(i); + if (ItemStackUtils.isEmpty(invItem)) { + if (emptySlot == -1 && i < 9) emptySlot = i; + continue; + } + if (invItem.getType().equals(itemStack.getType()) && invItem.getItemMeta().equals(itemStack.getItemMeta())) { + if (sameItemSlot == -1) sameItemSlot = i; + } + } + if (sameItemSlot != -1) { + if (sameItemSlot < 9) { + inventory.setHeldItemSlot(sameItemSlot); + ItemStack previousItem = inventory.getItem(slot - 36); + BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, previousItem)); + } else { + ItemStack sameItem = inventory.getItem(sameItemSlot); + int finalSameItemSlot = sameItemSlot; + BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> { + inventory.setItem(finalSameItemSlot, new ItemStack(Material.AIR)); + inventory.setItem(slot - 36, sameItem); + }); + } + } else { + if (item.getAmount() == 1) { + if (ItemStackUtils.isEmpty(inventory.getItem(slot - 36))) { + BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, itemStack)); + return; + } + if (emptySlot != -1) { + inventory.setHeldItemSlot(emptySlot); + inventory.setItem(emptySlot, itemStack); + } else { + BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, itemStack)); + } + } + } + } + } + + public static class LoginListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + player.setConnectionState(ConnectionState.PLAY); + Object dimensionKey; + try { + if (!VersionHelper.isOrAbove1_20_2()) { + dimensionKey = NetworkReflections.methodHandle$ClientboundLoginPacket$dimensionGetter.invokeExact(packet); + } else { + Object commonInfo = NetworkReflections.methodHandle$ClientboundLoginPacket$commonPlayerSpawnInfoGetter.invokeExact(packet); + dimensionKey = NetworkReflections.methodHandle$CommonPlayerSpawnInfo$dimensionGetter.invokeExact(commonInfo); + } + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get dimensionKey from ClientboundLoginPacket", t); + return; + } + Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); + World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); + if (world != null) { + int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; + player.setClientSideSectionCount(sectionCount); + player.setClientSideDimension(Key.of(location.toString())); + } else { + CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist"); + } + } + } + + public static class RespawnListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + player.clearView(); + Object dimensionKey; + try { + if (!VersionHelper.isOrAbove1_20_2()) { + dimensionKey = NetworkReflections.methodHandle$ClientboundRespawnPacket$dimensionGetter.invokeExact(packet); + } else { + Object commonInfo = NetworkReflections.methodHandle$ClientboundRespawnPacket$commonPlayerSpawnInfoGetter.invokeExact(packet); + dimensionKey = NetworkReflections.methodHandle$CommonPlayerSpawnInfo$dimensionGetter.invokeExact(commonInfo); + } + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get dimensionKey from ClientboundRespawnPacket", t); + return; + } + Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); + World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); + if (world != null) { + int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; + player.setClientSideSectionCount(sectionCount); + player.setClientSideDimension(Key.of(location.toString())); + player.clearTrackedChunks(); + } else { + CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist"); + } + } + } + + // 1.21.2+ + public static class SyncEntityPositionListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + int entityId = FastNMS.INSTANCE.method$ClientboundEntityPositionSyncPacket$id(packet); + EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); + if (handler != null) { + handler.handleSyncEntityPosition(user, event, packet); + } + } + } + + public static class RenameItemListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!Config.filterAnvil()) return; + if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_ANVIL)) { + return; + } + String message; + try { + message = (String) NetworkReflections.methodHandle$ServerboundRenameItemPacket$nameGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get message from ServerboundRenameItemPacket", t); + return; + } + if (message != null && !message.isEmpty()) { + // check bypass + FontManager manager = CraftEngine.instance().fontManager(); + IllegalCharacterProcessResult result = manager.processIllegalCharacters(message); + if (result.has()) { + try { + NetworkReflections.methodHandle$ServerboundRenameItemPacket$nameSetter.invokeExact(packet, result.text()); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to set field 'name' for ServerboundRenameItemPacket", e); + } + } + } + } + } + + public static class SignUpdateListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!Config.filterSign()) return; + // check bypass + if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_SIGN)) { + return; + } + String[] lines; + try { + lines = (String[]) NetworkReflections.methodHandle$ServerboundSignUpdatePacket$linesGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get lines from ServerboundSignUpdatePacket", t); + return; + } + FontManager manager = CraftEngine.instance().fontManager(); + if (!manager.isDefaultFontInUse()) return; + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (line != null && !line.isEmpty()) { + IllegalCharacterProcessResult result = manager.processIllegalCharacters(line); + if (result.has()) { + lines[i] = result.text(); + } + } + } + } + } + + public static class EditBookListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!Config.filterBook()) return; + FontManager manager = CraftEngine.instance().fontManager(); + if (!manager.isDefaultFontInUse()) return; + // check bypass + if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_BOOK)) { + return; + } + + boolean changed = false; + + List pages; + try { + // noinspection unchecked + pages = (List) NetworkReflections.methodHandle$ServerboundEditBookPacket$pagesGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get pages from ServerboundEditBookPacket", t); + return; + } + List newPages = new ArrayList<>(pages.size()); + Optional title; + try { + // noinspection unchecked + title = (Optional) NetworkReflections.methodHandle$ServerboundEditBookPacket$titleGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get title from ServerboundEditBookPacket", t); + return; + } + Optional newTitle; + + if (title.isPresent()) { + String titleStr = title.get(); + Pair result = processClientString(titleStr, manager); + newTitle = Optional.of(result.right()); + if (result.left()) { + changed = true; + } + } else { + newTitle = Optional.empty(); + } + + for (String page : pages) { + Pair result = processClientString(page, manager); + newPages.add(result.right()); + if (result.left()) { + changed = true; + } + } + + if (changed) { + try { + Object newPacket = NetworkReflections.constructor$ServerboundEditBookPacket.newInstance( + (int) NetworkReflections.methodHandle$ServerboundEditBookPacket$slotGetter.invokeExact(packet), + newPages, + newTitle + ); + event.replacePacket(newPacket); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to construct ServerboundEditBookPacket", t); + } + } + } + + private static Pair processClientString(String original, FontManager manager) { + if (original.isEmpty()) { + return Pair.of(false, original); + } + int[] codepoints = CharacterUtils.charsToCodePoints(original.toCharArray()); + int[] newCodepoints = new int[codepoints.length]; + boolean hasIllegal = false; + for (int i = 0; i < codepoints.length; i++) { + int codepoint = codepoints[i]; + if (manager.isIllegalCodepoint(codepoint)) { + newCodepoints[i] = '*'; + hasIllegal = true; + } else { + newCodepoints[i] = codepoint; + } + } + return hasIllegal ? Pair.of(true, new String(newCodepoints, 0, newCodepoints.length)) : Pair.of(false, original); + } + } + + public static class CustomPayloadListener1_20_2 implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!VersionHelper.isOrAbove1_20_2()) return; + Object payload; + try { + payload = NetworkReflections.methodHandle$ServerboundCustomPayloadPacket$payloadGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get payload from ServerboundCustomPayloadPacket", t); + return; + } + Payload clientPayload; + if (VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) { + clientPayload = DiscardedPayload.from(payload); + } else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$ServerboundCustomPayloadPacket$UnknownPayload.isInstance(payload)) { + clientPayload = UnknownPayload.from(payload); + } else { + return; + } + if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY)) return; + PayloadHelper.handleReceiver(clientPayload, user); + } + } + + public static class ResourcePackResponseListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + Object action = FastNMS.INSTANCE.field$ServerboundResourcePackPacket$action(packet); + + if (VersionHelper.isOrAbove1_20_3()) { + UUID uuid = FastNMS.INSTANCE.field$ServerboundResourcePackPacket$id(packet); + if (!user.isResourcePackLoading(uuid)) { + // 不是CraftEngine发送的资源包,不管 + return; + } + } + + if (action == null) { + user.kick(Component.text("Corrupted ResourcePackResponse Packet")); + return; + } + + // 检查是否是拒绝 + if (Config.kickOnDeclined()) { + if (action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$DECLINED || action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$DISCARDED) { + user.kick(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); + return; + } + } + + // 检查是否失败 + if (Config.kickOnFailedApply()) { + if (action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$FAILED_DOWNLOAD + || (VersionHelper.isOrAbove1_20_3() && action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$INVALID_URL)) { + user.kick(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); + return; + } + } + + boolean isTerminal = action != NetworkReflections.instance$ServerboundResourcePackPacket$Action$ACCEPTED && action != NetworkReflections.instance$ServerboundResourcePackPacket$Action$DOWNLOADED; + if (isTerminal && VersionHelper.isOrAbove1_20_2()) { + event.setCancelled(true); + Object packetListener = FastNMS.INSTANCE.method$Connection$getPacketListener(user.connection()); + if (!CoreReflections.clazz$ServerConfigurationPacketListenerImpl.isInstance(packetListener)) return; + // 主线程上处理这个包 + CraftEngine.instance().scheduler().executeSync(() -> { + try { + // 当客户端发出多次成功包的时候,finish会报错,我们忽略他 + NetworkReflections.methodHandle$ServerCommonPacketListener$handleResourcePackResponse.invokeExact(packetListener, packet); + CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask.invokeExact(packetListener, CoreReflections.instance$ServerResourcePackConfigurationTask$TYPE); + } catch (Throwable e) { + Debugger.RESOURCE_PACK.warn(() -> "Cannot finish current task", e); + } + }); + } + } + } + + public static class EntityEventListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + Object player = user.serverPlayer(); + if (player == null) return; + int entityId; + try { + entityId = (int) NetworkReflections.methodHandle$ClientboundEntityEventPacket$entityIdGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundEntityEventPacket", t); + return; + } + if (entityId != FastNMS.INSTANCE.method$Entity$getId(player)) return; + byte eventId; + try { + eventId = (byte) NetworkReflections.methodHandle$ClientboundEntityEventPacket$eventIdGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get event id from ClientboundEntityEventPacket", t); + return; + } + if (eventId >= 24 && eventId <= 28) { + CraftEngine.instance().fontManager().refreshEmojiSuggestions(user.uuid()); + } + } + } + + public static class MovePosAndRotateEntityListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet); + if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) { + event.setCancelled(true); + } + EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); + if (handler != null) { + handler.handleMoveAndRotate(user, event, packet); + } + } + } + + public static class MovePosEntityListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet); + EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); + if (handler != null) { + handler.handleMove(user, event, packet); + } + } + } + + public static class RotateHeadListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + int entityId; + try { + entityId = (int) NetworkReflections.methodHandle$ClientboundRotateHeadPacket$entityIdGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundRotateHeadPacket", t); + return; + } + if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) { + event.setCancelled(true); + } + } + } + + public static class SetEntityMotionListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!VersionHelper.isOrAbove1_21_6()) return; + int entityId; + try { + entityId = (int) NetworkReflections.methodHandle$ClientboundSetEntityMotionPacket$idGetter.invokeExact(packet); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundSetEntityMotionPacket", t); + return; + } + if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) { + event.setCancelled(true); + } + } + } + + public static class FinishConfigurationListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (!VersionHelper.isOrAbove1_20_2() || !Config.sendPackOnJoin()) { + // 防止后期调试进配置阶段造成问题 + user.setShouldProcessFinishConfiguration(false); + return; + } + + if (!user.shouldProcessFinishConfiguration()) return; + Object packetListener = FastNMS.INSTANCE.method$Connection$getPacketListener(user.connection()); + if (!CoreReflections.clazz$ServerConfigurationPacketListenerImpl.isInstance(packetListener)) { + return; + } + + // 防止后续加入的JoinWorldTask再次处理 + user.setShouldProcessFinishConfiguration(false); + + // 检查用户UUID是否已经校验 + if (!user.isUUIDVerified()) { + if (Config.strictPlayerUuidValidation()) { + TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); + user.kick(Component.translatable("disconnect.loginFailedInfo").arguments(Component.translatable("argument.uuid.invalid"))); + return; + } + if (Config.debugResourcePack()) { + TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); + } + } + + // 取消 ClientboundFinishConfigurationPacket,让客户端发呆,并结束掉当前的进入世界任务 + event.setCancelled(true); + try { + CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask.invokeExact(packetListener, CoreReflections.instance$JoinWorldTask$TYPE); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to finish current task for " + user.name(), e); + } + + if (VersionHelper.isOrAbove1_20_5()) { + // 1.20.5+开始会检查是否结束需要重新设置回去,不然不会发keepAlive包 + try { + CoreReflections.methodHandle$ServerCommonPacketListenerImpl$closedSetter.invokeExact(packetListener, false); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to set the 'closed' field of ServerCommonPacketListenerImpl for" + user.name(), e); + } + } + + // 请求资源包 + ResourcePackHost host = CraftEngine.instance().packManager().resourcePackHost(); + host.requestResourcePackDownloadLink(user.uuid()).whenComplete((dataList, t) -> { + if (t != null) { + CraftEngine.instance().logger().warn("Failed to get pack data for player " + user.name(), t); + FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); + return; + } + if (dataList.isEmpty()) { + FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); + return; + } + Queue configurationTasks; + try { + // noinspection unchecked + configurationTasks = (Queue) CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$configurationTasksGetter.invokeExact(packetListener); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Failed to get configuration tasks for player " + user.name(), e); + FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); + return; + } + // 向配置阶段连接的任务重加入资源包的任务 + for (ResourcePackDownloadData data : dataList) { + configurationTasks.add(FastNMS.INSTANCE.constructor$ServerResourcePackConfigurationTask(ResourcePackUtils.createServerResourcePackInfo(data.uuid(), data.url(), data.sha1()))); + user.addResourcePackUUID(data.uuid()); + } + // 最后再加入一个 JoinWorldTask 并开始资源包任务 + FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); + }); + } + } + + public static class LoginFinishedListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + GameProfile gameProfile = FastNMS.INSTANCE.field$ClientboundLoginFinishedPacket$gameProfile(packet); + user.setVerifiedName(gameProfile.getName()); + user.setVerifiedUUID(gameProfile.getId()); + } + } + + public static class UpdateTagsListener implements NMSPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + Object modifiedPacket = BukkitBlockManager.instance().cachedUpdateTagsPacket(); + if (packet.equals(modifiedPacket) || modifiedPacket == null) return; + event.replacePacket(modifiedPacket); + } + } + + public static class ContainerClickListener1_21_5 implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + int containerId = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$containerId(packet); + int stateId = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$stateId(packet); + short slotNum = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$slotNum(packet); + byte buttonNum = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$buttonNum(packet); + Object clickType = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$clickType(packet); + @SuppressWarnings("unchecked") + Int2ObjectMap changedSlots = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$changedSlots(packet); + Int2ObjectMap newChangedSlots = new Int2ObjectOpenHashMap<>(changedSlots.size()); + for (Int2ObjectMap.Entry entry : changedSlots.int2ObjectEntrySet()) { + newChangedSlots.put(entry.getIntKey(), FastNMS.INSTANCE.constructor$InjectedHashedStack(entry.getValue(), player)); + } + Object carriedItem = FastNMS.INSTANCE.constructor$InjectedHashedStack(FastNMS.INSTANCE.field$ServerboundContainerClickPacket$carriedItem(packet), player); + event.replacePacket(FastNMS.INSTANCE.constructor$ServerboundContainerClickPacket(containerId, stateId, slotNum, buttonNum, clickType, Int2ObjectMaps.unmodifiable(newChangedSlots), carriedItem)); + } + } + + public static class ForgetLevelChunkListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + FriendlyByteBuf buf = event.getBuffer(); + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); + if (VersionHelper.isOrAbove1_20_2()) { + long chunkPos = buf.readLong(); + user.removeTrackedChunk(chunkPos); + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos); + if (ceChunk != null) { + ceChunk.despawnBlockEntities(player); + } + } else { + int x = buf.readInt(); + int y = buf.readInt(); + user.removeTrackedChunk(ChunkPos.asLong(x, y)); + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(x, y); + if (ceChunk != null) { + ceChunk.despawnBlockEntities(player); + } + } + } + } + + public static class LevelChunkWithLightListener implements ByteBufferPacketListener { + private static BiConsumer> biomeRemapper = null; + + public static void setBiomeRemapper(BiConsumer> remapper) { + biomeRemapper = remapper; + } + + public static void remapBiomes(NetWorkUser user, PalettedContainer biomes) { + if (biomeRemapper != null) { + biomeRemapper.accept(user, biomes); + } + } + + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + private final IntIdentityList biomeList; + private final IntIdentityList blockList; + + public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + this.biomeList = new IntIdentityList(biomeRegistrySize); + this.blockList = new IntIdentityList(blockRegistrySize); + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + BukkitServerPlayer player = (BukkitServerPlayer) user; + FriendlyByteBuf buf = event.getBuffer(); + int chunkX = buf.readInt(); + int chunkZ = buf.readInt(); + boolean named = !VersionHelper.isOrAbove1_20_2(); + // ClientboundLevelChunkPacketData + int heightmapsCount = 0; + Map heightmapsMap = null; + net.momirealms.sparrow.nbt.Tag heightmaps = null; + if (VersionHelper.isOrAbove1_21_5()) { + heightmapsMap = new HashMap<>(); + heightmapsCount = buf.readVarInt(); + for (int i = 0; i < heightmapsCount; i++) { + int key = buf.readVarInt(); + long[] value = buf.readLongArray(); + heightmapsMap.put(key, value); + } + } else { + heightmaps = buf.readNbt(named); + } + + int varInt = buf.readVarInt(); + byte[] buffer = new byte[varInt]; + buf.readBytes(buffer); + int blockEntitiesDataCount = buf.readVarInt(); + List blockEntitiesData = new ArrayList<>(); + for (int i = 0; i < blockEntitiesDataCount; i++) { + byte packedXZ = buf.readByte(); + short y = buf.readShort(); + int type = buf.readVarInt(); + Tag tag = buf.readNbt(named); + BlockEntityData blockEntityData = new BlockEntityData(packedXZ, y, type, tag); + blockEntitiesData.add(blockEntityData); + } + // ClientboundLightUpdatePacketData + BitSet skyYMask = buf.readBitSet(); + BitSet blockYMask = buf.readBitSet(); + BitSet emptySkyYMask = buf.readBitSet(); + BitSet emptyBlockYMask = buf.readBitSet(); + List skyUpdates = buf.readByteArrayList(2048); + List blockUpdates = buf.readByteArrayList(2048); + // 开始处理 + if (user.clientModEnabled()) { + ByteBuf byteBuf = Unpooled.copiedBuffer(buffer); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf); + FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); + for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) { + MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); + mcSection.readPacket(friendlyByteBuf); + PalettedContainer container = mcSection.blockStateContainer(); + remapBiomes(user, mcSection.biomeContainer()); + Palette palette = container.data().palette(); + if (palette.canRemap()) { + palette.remap(s -> this.modBlockStateMapper[s]); + } else { + for (int j = 0; j < 4096; j++) { + int state = container.get(j); + int newState = this.modBlockStateMapper[state]; + if (newState != state) { + container.set(j, newState); + } + } + } + mcSection.writePacket(newBuf); + } + buffer = newBuf.array(); + } else { + ByteBuf byteBuf = Unpooled.copiedBuffer(buffer); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf); + FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); + for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) { + MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); + mcSection.readPacket(friendlyByteBuf); + PalettedContainer container = mcSection.blockStateContainer(); + remapBiomes(user, mcSection.biomeContainer()); + Palette palette = container.data().palette(); + if (palette.canRemap()) { + palette.remap(s -> this.blockStateMapper[s]); + } else { + for (int j = 0; j < 4096; j++) { + int state = container.get(j); + int newState = this.blockStateMapper[state]; + if (newState != state) { + container.set(j, newState); + } + } + } + mcSection.writePacket(newBuf); + } + buffer = newBuf.array(); + } + + // 开始修改 + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeInt(chunkX); + buf.writeInt(chunkZ); + if (VersionHelper.isOrAbove1_21_5()) { + buf.writeVarInt(heightmapsCount); + for (Map.Entry entry : heightmapsMap.entrySet()) { + buf.writeVarInt(entry.getKey()); + buf.writeLongArray(entry.getValue()); + } + } else { + buf.writeNbt(heightmaps, named); + } + buf.writeVarInt(buffer.length); + buf.writeBytes(buffer); + buf.writeVarInt(blockEntitiesDataCount); + for (BlockEntityData blockEntityData : blockEntitiesData) { + buf.writeByte(blockEntityData.packedXZ()); + buf.writeShort(blockEntityData.y()); + buf.writeVarInt(blockEntityData.type()); + buf.writeNbt(blockEntityData.tag(), named); + } + buf.writeBitSet(skyYMask); + buf.writeBitSet(blockYMask); + buf.writeBitSet(emptySkyYMask); + buf.writeBitSet(emptyBlockYMask); + buf.writeByteArrayList(skyUpdates); + buf.writeByteArrayList(blockUpdates); + + ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + // 记录加载的区块 + player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); + + CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); + if (ceChunk != null) { + ceChunk.spawnBlockEntities(player); + } + } + } + + public static class SectionBlockUpdateListener implements ByteBufferPacketListener { + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + + public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (user.clientModEnabled()) { + FriendlyByteBuf buf = event.getBuffer(); + long pos = buf.readLong(); + int blocks = buf.readVarInt(); + short[] positions = new short[blocks]; + int[] states = new int[blocks]; + for (int i = 0; i < blocks; i++) { + long k = buf.readVarLong(); + positions[i] = (short) ((int) (k & 4095L)); + states[i] = modBlockStateMapper[((int) (k >>> 12))]; + } + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeLong(pos); + buf.writeVarInt(blocks); + for (int i = 0; i < blocks; i++) { + buf.writeVarLong((long) states[i] << 12 | positions[i]); + } + event.setChanged(true); + } else { + FriendlyByteBuf buf = event.getBuffer(); + long pos = buf.readLong(); + int blocks = buf.readVarInt(); + short[] positions = new short[blocks]; + int[] states = new int[blocks]; + for (int i = 0; i < blocks; i++) { + long k = buf.readVarLong(); + positions[i] = (short) ((int) (k & 4095L)); + states[i] = blockStateMapper[((int) (k >>> 12))]; + } + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeLong(pos); + buf.writeVarInt(blocks); + for (int i = 0; i < blocks; i++) { + buf.writeVarLong((long) states[i] << 12 | positions[i]); + } + event.setChanged(true); + } + } + } + + public static class BlockUpdateListener implements ByteBufferPacketListener { + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + + public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + BlockPos pos = buf.readBlockPos(); + int before = buf.readVarInt(); + if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) { + return; + } + int state = user.clientModEnabled() ? modBlockStateMapper[before] : blockStateMapper[before]; + if (state == before) { + return; + } + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeBlockPos(pos); + buf.writeVarInt(state); + } + } + + public static class LevelParticleListener1_21_4 implements ByteBufferPacketListener { + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + + public LevelParticleListener1_21_4(int[] blockStateMapper, int[] modBlockStateMapper) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + boolean overrideLimiter = buf.readBoolean(); + boolean alwaysShow = buf.readBoolean(); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + float xDist = buf.readFloat(); + float yDist = buf.readFloat(); + float zDist = buf.readFloat(); + float maxSpeed = buf.readFloat(); + int count = buf.readInt(); + Object option = FastNMS.INSTANCE.method$StreamCodec$decode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf); + if (option == null) return; + if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return; + Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option); + int id = BlockStateUtils.blockStateToId(blockState); + int remapped = user.clientModEnabled() ? modBlockStateMapper[id] : blockStateMapper[id]; + if (remapped == id) return; + Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option); + Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped)); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeBoolean(overrideLimiter); + buf.writeBoolean(alwaysShow); + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeFloat(xDist); + buf.writeFloat(yDist); + buf.writeFloat(zDist); + buf.writeFloat(maxSpeed); + buf.writeInt(count); + FastNMS.INSTANCE.method$StreamCodec$encode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf, remappedOption); + } + } + + public static class LevelParticleListener1_20_5 implements ByteBufferPacketListener { + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + + public LevelParticleListener1_20_5(int[] blockStateMapper, int[] modBlockStateMapper) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + boolean overrideLimiter = buf.readBoolean(); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + float xDist = buf.readFloat(); + float yDist = buf.readFloat(); + float zDist = buf.readFloat(); + float maxSpeed = buf.readFloat(); + int count = buf.readInt(); + Object option = FastNMS.INSTANCE.method$StreamCodec$decode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf); + if (option == null) return; + if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return; + Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option); + int id = BlockStateUtils.blockStateToId(blockState); + int remapped = user.clientModEnabled() ? modBlockStateMapper[id] : blockStateMapper[id]; + if (remapped == id) return; + Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option); + Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped)); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeBoolean(overrideLimiter); + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeFloat(xDist); + buf.writeFloat(yDist); + buf.writeFloat(zDist); + buf.writeFloat(maxSpeed); + buf.writeInt(count); + FastNMS.INSTANCE.method$StreamCodec$encode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf, remappedOption); + } + } + + public static class LevelParticleListener1_20 implements ByteBufferPacketListener { + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + + public LevelParticleListener1_20(int[] blockStateMapper, int[] modBlockStateMapper) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + Object particleType = FastNMS.INSTANCE.method$FriendlyByteBuf$readById(buf, MBuiltInRegistries.PARTICLE_TYPE); + boolean overrideLimiter = buf.readBoolean(); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + float xDist = buf.readFloat(); + float yDist = buf.readFloat(); + float zDist = buf.readFloat(); + float maxSpeed = buf.readFloat(); + int count = buf.readInt(); + Object option = FastNMS.INSTANCE.method$ClientboundLevelParticlesPacket$readParticle(buf, particleType); + if (option == null) return; + if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return; + Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option); + int id = BlockStateUtils.blockStateToId(blockState); + int remapped = user.clientModEnabled() ? modBlockStateMapper[id] : blockStateMapper[id]; + if (remapped == id) return; + Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option); + Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped)); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeId(buf, remappedOption, MBuiltInRegistries.PARTICLE_TYPE); + buf.writeBoolean(overrideLimiter); + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeFloat(xDist); + buf.writeFloat(yDist); + buf.writeFloat(zDist); + buf.writeFloat(maxSpeed); + buf.writeInt(count); + FastNMS.INSTANCE.method$ParticleOptions$writeToNetwork(remappedOption, buf); + } + } + + public static class LevelEventListener implements ByteBufferPacketListener { + private final int[] blockStateMapper; + private final int[] modBlockStateMapper; + + public LevelEventListener(int[] blockStateMapper, int[] modBlockStateMapper) { + this.blockStateMapper = blockStateMapper; + this.modBlockStateMapper = modBlockStateMapper; + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + int eventId = buf.readInt(); + if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; + BlockPos blockPos = buf.readBlockPos(); + int state = buf.readInt(); + boolean global = buf.readBoolean(); + int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; + Object blockState = BlockStateUtils.idToBlockState(newState); + Object block = BlockStateUtils.getBlockOwner(blockState); + if (BukkitBlockManager.instance().isBlockSoundRemoved(block) && !FastNMS.INSTANCE.method$BlockStateBase$isAir(blockState)) { + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); + Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); + Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); + if (mappedSoundId != null) { + Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty()); + Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); + Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( + mappedBreakSoundHolder, + CoreReflections.instance$SoundSource$BLOCKS, + blockPos.x() + 0.5, + blockPos.y() + 0.5, + blockPos.z() + 0.5, + (FastNMS.INSTANCE.field$SoundType$volume(soundType) + 1.0F) / 2.0F, + FastNMS.INSTANCE.field$SoundType$pitch(soundType) * 0.8F, + RandomUtils.generateRandomLong() + ); + user.sendPacket(packet, true); + } + } + if (newState == state) { + return; + } + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeInt(eventId); + buf.writeBlockPos(blockPos); + buf.writeInt(newState); + buf.writeBoolean(global); + } + } + + public static class OpenScreenListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptContainer()) return; + FriendlyByteBuf buf = event.getBuffer(); + int containerId = buf.readVarInt(); + int type = buf.readVarInt(); + String json = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(containerId); + buf.writeVarInt(type); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + } + } + + public static class OpenScreenListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptContainer()) return; + FriendlyByteBuf buf = event.getBuffer(); + int containerId = buf.readVarInt(); + int type = buf.readVarInt(); + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(containerId); + buf.writeVarInt(type); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + } + } + + public static class SystemChatListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptSystemChat()) return; + FriendlyByteBuf buf = event.getBuffer(); + String jsonOrPlainString = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(jsonOrPlainString); + if (tokens.isEmpty()) return; + boolean overlay = buf.readBoolean(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(jsonOrPlainString), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + buf.writeBoolean(overlay); + } + } + + public static class SystemChatListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptSystemChat()) return; + FriendlyByteBuf buf = event.getBuffer(); + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + boolean overlay = buf.readBoolean(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + buf.writeBoolean(overlay); + } + } + + public static class TabListListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTabList()) return; + FriendlyByteBuf buf = event.getBuffer(); + String json1 = buf.readUtf(); + String json2 = buf.readUtf(); + Map tokens1 = CraftEngine.instance().fontManager().matchTags(json1); + Map tokens2 = CraftEngine.instance().fontManager().matchTags(json2); + if (tokens1.isEmpty() && tokens2.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); + buf.writeUtf(tokens1.isEmpty() ? json1 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json1), tokens1, context))); + buf.writeUtf(tokens2.isEmpty() ? json2 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json2), tokens2, context))); + } + } + + public static class TabListListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTabList()) return; + FriendlyByteBuf buf = event.getBuffer(); + Tag nbt1 = buf.readNbt(false); + if (nbt1 == null) return; + Tag nbt2 = buf.readNbt(false); + if (nbt2 == null) return; + Map tokens1 = CraftEngine.instance().fontManager().matchTags(nbt1.getAsString()); + Map tokens2 = CraftEngine.instance().fontManager().matchTags(nbt2.getAsString()); + if (tokens1.isEmpty() && tokens2.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); + buf.writeNbt(tokens1.isEmpty() ? nbt1 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt1), tokens1, context)), false); + buf.writeNbt(tokens2.isEmpty() ? nbt2 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt2), tokens2, context)), false); + } + } + + public static class SetActionBarListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptActionBar()) return; + FriendlyByteBuf buf = event.getBuffer(); + String json = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + } + } + + public static class SetActionBarListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptActionBar()) return; + FriendlyByteBuf buf = event.getBuffer(); + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + } + } + + public static class SetTitleListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTitle()) return; + FriendlyByteBuf buf = event.getBuffer(); + String json = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + } + } + + public static class SetTitleListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTitle()) return; + FriendlyByteBuf buf = event.getBuffer(); + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + } + } + + public static class SetSubtitleListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTitle()) return; + FriendlyByteBuf buf = event.getBuffer(); + String json = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + } + } + + public static class SetSubtitleListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTitle()) return; + FriendlyByteBuf buf = event.getBuffer(); + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + } + } + + public static class BossEventListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptBossBar()) return; + FriendlyByteBuf buf = event.getBuffer(); + UUID uuid = buf.readUUID(); + int actionType = buf.readVarInt(); + if (actionType == 0) { + String json = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) return; + float health = buf.readFloat(); + int color = buf.readVarInt(); + int division = buf.readVarInt(); + byte flag = buf.readByte(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUUID(uuid); + buf.writeVarInt(actionType); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + buf.writeFloat(health); + buf.writeVarInt(color); + buf.writeVarInt(division); + buf.writeByte(flag); + } else if (actionType == 3) { + String json = buf.readUtf(); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUUID(uuid); + buf.writeVarInt(actionType); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + } + } + } + + public static class BossEventListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptBossBar()) return; + FriendlyByteBuf buf = event.getBuffer(); + UUID uuid = buf.readUUID(); + int actionType = buf.readVarInt(); + if (actionType == 0) { + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + float health = buf.readFloat(); + int color = buf.readVarInt(); + int division = buf.readVarInt(); + byte flag = buf.readByte(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUUID(uuid); + buf.writeVarInt(actionType); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + buf.writeFloat(health); + buf.writeVarInt(color); + buf.writeVarInt(division); + buf.writeByte(flag); + } else if (actionType == 3) { + Tag nbt = buf.readNbt(false); + if (nbt == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUUID(uuid); + buf.writeVarInt(actionType); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + } + } + } + + public static class TeamListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTeam()) return; + FriendlyByteBuf buf = event.getBuffer(); + String name = buf.readUtf(); + byte method = buf.readByte(); + if (method != 2 && method != 0) + return; + String displayName = buf.readUtf(); + byte friendlyFlags = buf.readByte(); + String nameTagVisibility = buf.readUtf(40); + String collisionRule = buf.readUtf(40); + int color = buf.readVarInt(); + String prefix = buf.readUtf(); + String suffix = buf.readUtf(); + + Map tokens1 = CraftEngine.instance().fontManager().matchTags(displayName); + Map tokens2 = CraftEngine.instance().fontManager().matchTags(prefix); + Map tokens3 = CraftEngine.instance().fontManager().matchTags(suffix); + if (tokens1.isEmpty() && tokens2.isEmpty() && tokens3.isEmpty()) return; + event.setChanged(true); + NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); + + List entities = method == 0 ? buf.readStringList() : null; + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(name); + buf.writeByte(method); + buf.writeUtf(tokens1.isEmpty() ? displayName : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(displayName), tokens1, context))); + buf.writeByte(friendlyFlags); + buf.writeUtf(nameTagVisibility); + buf.writeUtf(collisionRule); + buf.writeVarInt(color); + buf.writeUtf(tokens2.isEmpty() ? prefix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(prefix), tokens2, context))); + buf.writeUtf(tokens3.isEmpty() ? suffix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(suffix), tokens3, context))); + if (entities != null) { + buf.writeStringList(entities); + } + } + } + + public static class TeamListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptTeam()) return; + FriendlyByteBuf buf = event.getBuffer(); + String name = buf.readUtf(); + byte method = buf.readByte(); + if (method != 2 && method != 0) return; + Tag displayName = buf.readNbt(false); + if (displayName == null) return; + byte friendlyFlags = buf.readByte(); + Either eitherVisibility = VersionHelper.isOrAbove1_21_5() ? Either.right(buf.readVarInt()) : Either.left(buf.readUtf(40)); + Either eitherCollisionRule = VersionHelper.isOrAbove1_21_5() ? Either.right(buf.readVarInt()) : Either.left(buf.readUtf(40)); + int color = buf.readVarInt(); + Tag prefix = buf.readNbt(false); + if (prefix == null) return; + Tag suffix = buf.readNbt(false); + if (suffix == null) return; + Map tokens1 = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + Map tokens2 = CraftEngine.instance().fontManager().matchTags(prefix.getAsString()); + Map tokens3 = CraftEngine.instance().fontManager().matchTags(suffix.getAsString()); + if (tokens1.isEmpty() && tokens2.isEmpty() && tokens3.isEmpty()) return; + NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); + List entities = method == 0 ? buf.readStringList() : null; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(name); + buf.writeByte(method); + buf.writeNbt(tokens1.isEmpty() ? displayName : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens1, context)), false); + buf.writeByte(friendlyFlags); + eitherVisibility.ifLeft(buf::writeUtf).ifRight(buf::writeVarInt); + eitherCollisionRule.ifLeft(buf::writeUtf).ifRight(buf::writeVarInt); + buf.writeVarInt(color); + buf.writeNbt(tokens2.isEmpty() ? prefix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(prefix), tokens2, context)), false); + buf.writeNbt(tokens3.isEmpty() ? suffix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(suffix), tokens3, context)), false); + if (entities != null) { + buf.writeStringList(entities); + } + } + } + + public static class SetObjectiveListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptScoreboard()) return; + FriendlyByteBuf buf = event.getBuffer(); + String objective = buf.readUtf(); + byte mode = buf.readByte(); + if (mode != 0 && mode != 2) return; + String displayName = buf.readUtf(); + int renderType = buf.readVarInt(); + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(objective); + buf.writeByte(mode); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); + buf.writeVarInt(renderType); + } + } + + public static class SetObjectiveListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptScoreboard()) return; + FriendlyByteBuf buf = event.getBuffer(); + String objective = buf.readUtf(); + byte mode = buf.readByte(); + if (mode != 0 && mode != 2) return; + Tag displayName = buf.readNbt(false); + if (displayName == null) return; + int renderType = buf.readVarInt(); + boolean optionalNumberFormat = buf.readBoolean(); + if (optionalNumberFormat) { + int format = buf.readVarInt(); + if (format == 0) { + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(objective); + buf.writeByte(mode); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + buf.writeVarInt(renderType); + buf.writeBoolean(true); + buf.writeVarInt(0); + } else if (format == 1) { + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + if (tokens.isEmpty()) return; + Tag style = buf.readNbt(false); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(objective); + buf.writeByte(mode); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + buf.writeVarInt(renderType); + buf.writeBoolean(true); + buf.writeVarInt(1); + buf.writeNbt(style, false); + } else if (format == 2) { + Tag fixed = buf.readNbt(false); + if (fixed == null) return; + Map tokens1 = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + Map tokens2 = CraftEngine.instance().fontManager().matchTags(fixed.getAsString()); + if (tokens1.isEmpty() && tokens2.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(objective); + buf.writeByte(mode); + buf.writeNbt(tokens1.isEmpty() ? displayName : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens1, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + buf.writeVarInt(renderType); + buf.writeBoolean(true); + buf.writeVarInt(2); + buf.writeNbt(tokens2.isEmpty() ? fixed : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(fixed), tokens2, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + } + } else { + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + if (tokens.isEmpty()) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(objective); + buf.writeByte(mode); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); + buf.writeVarInt(renderType); + buf.writeBoolean(false); + } + } + } + + public static class SetScoreListener1_20_3 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!Config.interceptSetScore()) return; + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + boolean isChanged = false; + FriendlyByteBuf buf = event.getBuffer(); + String owner = buf.readUtf(); + String objectiveName = buf.readUtf(); + int score = buf.readVarInt(); + boolean hasDisplay = buf.readBoolean(); + Tag displayName = null; + if (hasDisplay) { + displayName = buf.readNbt(false); + } + outside: + if (displayName != null) { + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + if (tokens.isEmpty()) break outside; + Component component = AdventureHelper.tagToComponent(displayName); + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); + displayName = AdventureHelper.componentToTag(component); + isChanged = true; + } + boolean hasNumberFormat = buf.readBoolean(); + int format = -1; + Tag style = null; + Tag fixed = null; + if (hasNumberFormat) { + format = buf.readVarInt(); + if (format == 0) { + if (displayName == null) return; + } else if (format == 1) { + if (displayName == null) return; + style = buf.readNbt(false); + } else if (format == 2) { + fixed = buf.readNbt(false); + if (fixed == null) return; + Map tokens = CraftEngine.instance().fontManager().matchTags(fixed.getAsString()); + if (tokens.isEmpty() && !isChanged) return; + if (!tokens.isEmpty()) { + Component component = AdventureHelper.tagToComponent(fixed); + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); + fixed = AdventureHelper.componentToTag(component); + isChanged = true; + } + } + } + if (isChanged) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeUtf(owner); + buf.writeUtf(objectiveName); + buf.writeVarInt(score); + if (hasDisplay) { + buf.writeBoolean(true); + buf.writeNbt(displayName, false); + } else { + buf.writeBoolean(false); + } + if (hasNumberFormat) { + buf.writeBoolean(true); + buf.writeVarInt(format); + if (format == 1) { + buf.writeNbt(style, false); + } else if (format == 2) { + buf.writeNbt(fixed, false); + } + } else { + buf.writeBoolean(false); + } + } + } + } + + public static class AddRecipeBookListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + List entries = buf.readCollection(ArrayList::new, byteBuf -> { + RecipeBookEntry entry = RecipeBookEntry.read(byteBuf); + entry.applyClientboundData((BukkitServerPlayer) user); + return entry; + }); + boolean replace = buf.readBoolean(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeCollection(entries, ((byteBuf, recipeBookEntry) -> recipeBookEntry.write(byteBuf))); + buf.writeBoolean(replace); + } + } + + public static class PlaceGhostRecipeListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!VersionHelper.isOrAbove1_21_2()) return; + FriendlyByteBuf buf = event.getBuffer(); + int containerId = buf.readContainerId(); + RecipeDisplay display = RecipeDisplay.read(buf); + display.applyClientboundData((BukkitServerPlayer) user); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + display.write(buf); + } + } + + public static class UpdateRecipesListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (VersionHelper.isOrAbove1_21_2()) return; + FriendlyByteBuf buf = event.getBuffer(); + List holders = buf.readCollection(ArrayList::new, byteBuf -> { + LegacyRecipeHolder holder = LegacyRecipeHolder.read(byteBuf); + holder.recipe().applyClientboundData((BukkitServerPlayer) user); + return holder; + }); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeCollection(holders, ((byteBuf, recipeHolder) -> recipeHolder.write(byteBuf))); + } + } + + public static class UpdateAdvancementsListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + boolean reset = buf.readBoolean(); + List added = buf.readCollection(ArrayList::new, byteBuf -> { + AdvancementHolder holder = AdvancementHolder.read(byteBuf); + holder.applyClientboundData(serverPlayer); + return holder; + }); + Set removed = buf.readCollection(Sets::newLinkedHashSetWithExpectedSize, FriendlyByteBuf::readKey); + Map progress = buf.readMap(FriendlyByteBuf::readKey, AdvancementProgress::read); + + boolean showAdvancement = false; + if (VersionHelper.isOrAbove1_21_5()) { + showAdvancement = buf.readBoolean(); + } + + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + + buf.writeBoolean(reset); + buf.writeCollection(added, (byteBuf, advancementHolder) -> advancementHolder.write(byteBuf)); + buf.writeCollection(removed, FriendlyByteBuf::writeKey); + buf.writeMap(progress, FriendlyByteBuf::writeKey, (byteBuf, advancementProgress) -> advancementProgress.write(byteBuf)); + if (VersionHelper.isOrAbove1_21_5()) { + buf.writeBoolean(showAdvancement); + } + } + } + + public static class RemoveEntityListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + boolean isChange = false; + IntList intList = buf.readIntIdList(); + for (int i = 0, size = intList.size(); i < size; i++) { + int entityId = intList.getInt(i); + EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId); + if (handler != null && handler.handleEntitiesRemove(intList)) { + isChange = true; + } + } + if (isChange) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeIntIdList(intList); + } + } + } + + public static class SoundListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + if (id == 0) { + Key soundId = buf.readKey(); + Float range = null; + if (buf.readBoolean()) { + range = buf.readFloat(); + } + int source = buf.readVarInt(); + int x = buf.readInt(); + int y = buf.readInt(); + int z = buf.readInt(); + float volume = buf.readFloat(); + float pitch = buf.readFloat(); + long seed = buf.readLong(); + Key mapped = BukkitBlockManager.instance().replaceSoundIfExist(soundId); + if (mapped != null) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(0); + buf.writeKey(mapped); + if (range != null) { + buf.writeBoolean(true); + buf.writeFloat(range); + } else { + buf.writeBoolean(false); + } + buf.writeVarInt(source); + buf.writeInt(x); + buf.writeInt(y); + buf.writeInt(z); + buf.writeFloat(volume); + buf.writeFloat(pitch); + buf.writeLong(seed); + } + } else { + Optional optionalSound = FastNMS.INSTANCE.method$IdMap$byId(MBuiltInRegistries.SOUND_EVENT, id - 1); + if (optionalSound.isEmpty()) return; + Object soundEvent = optionalSound.get(); + Key soundId = KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.method$SoundEvent$location(soundEvent)); + int source = buf.readVarInt(); + int x = buf.readInt(); + int y = buf.readInt(); + int z = buf.readInt(); + float volume = buf.readFloat(); + float pitch = buf.readFloat(); + long seed = buf.readLong(); + Key mapped = BukkitBlockManager.instance().replaceSoundIfExist(soundId); + if (mapped != null) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(0); + Object newId = KeyUtils.toResourceLocation(mapped); + Object newSoundEvent = FastNMS.INSTANCE.constructor$SoundEvent(newId, FastNMS.INSTANCE.method$SoundEvent$fixedRange(soundEvent)); + FastNMS.INSTANCE.method$SoundEvent$directEncode(buf, newSoundEvent); + buf.writeVarInt(source); + buf.writeInt(x); + buf.writeInt(y); + buf.writeInt(z); + buf.writeFloat(volume); + buf.writeFloat(pitch); + buf.writeLong(seed); + } + } + } + } + + public static class ContainerSetContentListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + int containerId = buf.readContainerId(); + int stateId = buf.readVarInt(); + int listSize = buf.readVarInt(); + List items = new ArrayList<>(listSize); + boolean changed = false; + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + for (int i = 0; i < listSize; i++) { + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().s2c(itemStack, serverPlayer); + if (optional.isPresent()) { + items.add(optional.get()); + changed = true; + } else { + items.add(itemStack); + } + } + ItemStack carriedItem = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + ItemStack newCarriedItem = carriedItem; + Optional optional = BukkitItemManager.instance().s2c(carriedItem, serverPlayer); + if (optional.isPresent()) { + changed = true; + newCarriedItem = optional.get(); + } + if (!changed) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + buf.writeVarInt(stateId); + buf.writeVarInt(listSize); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + for (ItemStack itemStack : items) { + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, itemStack); + } + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newCarriedItem); + } + } + + public static class ContainerSetSlotListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + int containerId = buf.readContainerId(); + int stateId = buf.readVarInt(); + int slot = buf.readShort(); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + ItemStack itemStack; + try { + itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + } catch (Exception e) { + // 其他插件干的,比如某ty*****er,不要赖到ce头上 + return; + } + BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + buf.writeVarInt(stateId); + buf.writeShort(slot); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + }); + } + } + + public static class SetCursorItemListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + }); + } + } + + public static class SetEquipmentListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + boolean changed = false; + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + int entity = buf.readVarInt(); + List> slots = Lists.newArrayList(); + int slotMask; + do { + slotMask = buf.readByte(); + Object equipmentSlot = CoreReflections.instance$EquipmentSlot$values[slotMask & 127]; + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().s2c(itemStack, serverPlayer); + if (optional.isPresent()) { + changed = true; + itemStack = optional.get(); + } + slots.add(com.mojang.datafixers.util.Pair.of(equipmentSlot, itemStack)); + } while ((slotMask & -128) != 0); + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(entity); + int i = slots.size(); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + for (int j = 0; j < i; ++j) { + com.mojang.datafixers.util.Pair pair = slots.get(j); + Enum equipmentSlot = (Enum) pair.getFirst(); + boolean bl = j != i - 1; + int k = equipmentSlot.ordinal(); + buf.writeByte(bl ? k | -128 : k); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, pair.getSecond()); + } + } + } + } + + public static class SetPlayerInventoryListener1_21_2 implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + int slot = buf.readVarInt(); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(slot); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + }); + } + } + + public static class SetCreativeModeSlotListener implements ByteBufferPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + if (!serverPlayer.isCreativeMode()) return; + FriendlyByteBuf buf = event.getBuffer(); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + short slotNum = buf.readShort(); + ItemStack itemStack; + try { + itemStack = VersionHelper.isOrAbove1_20_5() ? + FastNMS.INSTANCE.method$FriendlyByteBuf$readUntrustedItem(friendlyBuf) : FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + } catch (Exception e) { + return; + } + BukkitItemManager.instance().c2s(itemStack).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeShort(slotNum); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + if (VersionHelper.isOrAbove1_20_5()) { + FastNMS.INSTANCE.method$FriendlyByteBuf$writeUntrustedItem(newFriendlyBuf, newItemStack); + } else { + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + } + }); + } + } + + public static class ContainerClick1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + boolean changed = false; + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + int containerId = buf.readContainerId(); + int stateId = buf.readVarInt(); + short slotNum = buf.readShort(); + byte buttonNum = buf.readByte(); + int clickType = buf.readVarInt(); + int i = buf.readVarInt(); + Int2ObjectMap changedSlots = new Int2ObjectOpenHashMap<>(i); + for (int j = 0; j < i; ++j) { + int k = buf.readShort(); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().c2s(itemStack); + if (optional.isPresent()) { + changed = true; + itemStack = optional.get(); + } + changedSlots.put(k, itemStack); + } + ItemStack carriedItem = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().c2s(carriedItem); + if (optional.isPresent()) { + changed = true; + carriedItem = optional.get(); + } + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + buf.writeVarInt(stateId); + buf.writeShort(slotNum); + buf.writeByte(buttonNum); + buf.writeVarInt(clickType); + buf.writeVarInt(changedSlots.size()); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + changedSlots.forEach((k, v) -> { + buf.writeShort(k); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, v); + }); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, carriedItem); + } + } + } + + public class InteractEntityListener implements ByteBufferPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + int entityId = hasModelEngine() ? plugin.compatibilityManager().interactionToBaseEntity(buf.readVarInt()) : buf.readVarInt(); + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); + if (furniture == null) return; + int actionType = buf.readVarInt(); + BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user; + if (serverPlayer.isSpectatorMode()) return; + Player platformPlayer = serverPlayer.platformPlayer(); + Location location = furniture.baseEntity().getLocation(); + + Runnable mainThreadTask; + if (actionType == 1) { + // ATTACK + boolean usingSecondaryAction = buf.readBoolean(); + if (entityId != furniture.baseEntityId()) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(furniture.baseEntityId()); + buf.writeVarInt(actionType); + buf.writeBoolean(usingSecondaryAction); + } + + mainThreadTask = () -> { + // todo 冒险模式破坏工具白名单 + if (serverPlayer.isAdventureMode() || + !furniture.isValid()) return; + + // todo 重构家具时候注意,需要准备加载好的hitbox类,以获取hitbox坐标 + if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { + return; + } + + FurnitureAttemptBreakEvent preBreakEvent = new FurnitureAttemptBreakEvent(serverPlayer.platformPlayer(), furniture); + if (EventUtils.fireAndCheckCancel(preBreakEvent)) + return; + + if (!BukkitCraftEngine.instance().antiGriefProvider().canBreak(platformPlayer, location)) + return; + + FurnitureBreakEvent breakEvent = new FurnitureBreakEvent(serverPlayer.platformPlayer(), furniture); + if (EventUtils.fireAndCheckCancel(breakEvent)) + return; + + Cancellable cancellable = Cancellable.of(breakEvent::isCancelled, breakEvent::setCancelled); + // execute functions + PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() + .withParameter(DirectContextParameters.FURNITURE, furniture) + .withParameter(DirectContextParameters.EVENT, cancellable) + .withParameter(DirectContextParameters.HAND, InteractionHand.MAIN_HAND) + .withParameter(DirectContextParameters.ITEM_IN_HAND, serverPlayer.getItemInHand(InteractionHand.MAIN_HAND)) + .withParameter(DirectContextParameters.POSITION, furniture.position()) + ); + furniture.config().execute(context, EventTrigger.LEFT_CLICK); + furniture.config().execute(context, EventTrigger.BREAK); + if (cancellable.isCancelled()) { + return; + } + + CraftEngineFurniture.remove(furniture, serverPlayer, !serverPlayer.isCreativeMode(), true); + }; + } else if (actionType == 2) { + // INTERACT_AT + float x = buf.readFloat(); + float y = buf.readFloat(); + float z = buf.readFloat(); + // todo 这个是错误的,这是实体的相对位置而非绝对位置 + Location interactionPoint = new Location(platformPlayer.getWorld(), x, y, z); + InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; + boolean usingSecondaryAction = buf.readBoolean(); + if (entityId != furniture.baseEntityId()) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(furniture.baseEntityId()); + buf.writeVarInt(actionType); + buf.writeFloat(x).writeFloat(y).writeFloat(z); + buf.writeVarInt(hand == InteractionHand.MAIN_HAND ? 0 : 1); + buf.writeBoolean(usingSecondaryAction); + } + + mainThreadTask = () -> { + if (!furniture.isValid()) { + return; + } + + // todo 重构家具时候注意,需要准备加载好的hitbox类,以获取hitbox坐标 + if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { + return; + } + + FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint); + if (EventUtils.fireAndCheckCancel(interactEvent)) { + return; + } + + Item itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); + Cancellable cancellable = Cancellable.of(interactEvent::isCancelled, interactEvent::setCancelled); + // execute functions + PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() + .withParameter(DirectContextParameters.EVENT, cancellable) + .withParameter(DirectContextParameters.FURNITURE, furniture) + .withParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) + .withParameter(DirectContextParameters.HAND, hand) + .withParameter(DirectContextParameters.POSITION, furniture.position()) + ); + furniture.config().execute(context, EventTrigger.RIGHT_CLICK); + if (cancellable.isCancelled()) { + return; + } + + // 必须从网络包层面处理,否则无法获取交互的具体实体 + if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty()) { + // try placing another furniture above it + AABB hitBox = furniture.aabbByEntityId(entityId); + if (hitBox == null) return; + Optional> optionalCustomItem = itemInHand.getCustomItem(); + Location eyeLocation = platformPlayer.getEyeLocation(); + Vector direction = eyeLocation.getDirection(); + Location endLocation = eyeLocation.clone(); + endLocation.add(direction.multiply(serverPlayer.getCachedInteractionRange())); + Optional result = hitBox.clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation)); + if (result.isEmpty()) { + return; + } + EntityHitResult hitResult = result.get(); + if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) { + for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) { + if (behavior instanceof FurnitureItemBehavior) { + behavior.useOnBlock(new UseOnContext(serverPlayer, InteractionHand.MAIN_HAND, new BlockHitResult(hitResult.hitLocation(), hitResult.direction(), BlockPos.fromVec3d(hitResult.hitLocation()), false))); + return; + } + } + } + // now simulate vanilla item behavior + serverPlayer.setResendSound(); + FastNMS.INSTANCE.simulateInteraction( + serverPlayer.serverPlayer(), + DirectionUtils.toNMSDirection(hitResult.direction()), + hitResult.hitLocation().x, hitResult.hitLocation().y, hitResult.hitLocation().z, + LocationUtils.toBlockPos(hitResult.blockPos()) + ); + } else { + if (!serverPlayer.isSecondaryUseActive()) { + furniture.findFirstAvailableSeat(entityId).ifPresent(seatPos -> { + if (furniture.tryOccupySeat(seatPos)) { + furniture.spawnSeatEntityForPlayer(serverPlayer, seatPos); + } + }); + } + } + }; + } else if (actionType == 0) { + int hand = buf.readVarInt(); + boolean usingSecondaryAction = buf.readBoolean(); + if (entityId != furniture.baseEntityId()) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(furniture.baseEntityId()); + buf.writeVarInt(actionType); + buf.writeVarInt(hand); + buf.writeBoolean(usingSecondaryAction); + } + return; + } else { + return; + } + + if (VersionHelper.isFolia()) { + platformPlayer.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), t -> mainThreadTask.run(), () -> {}); + } else { + BukkitCraftEngine.instance().scheduler().executeSync(mainThreadTask); + } + } + } + + public static class CustomPayloadListener1_20 implements ByteBufferPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) { + if (VersionHelper.isOrAbove1_20_2()) return; + FriendlyByteBuf byteBuf = event.getBuffer(); + Key key = byteBuf.readKey(); + if (!key.equals(NetworkManager.MOD_CHANNEL_KEY)) return; + PayloadHelper.handleReceiver(new UnknownPayload(key, byteBuf.readBytes(byteBuf.readableBytes())), user); + } + } + + public class AddEntityListener implements ByteBufferPacketListener { + private final EntityTypeHandler[] handlers; + + public AddEntityListener(int entityTypes) { + this.handlers = new EntityTypeHandler[entityTypes]; + Arrays.fill(this.handlers, EntityTypeHandler.DoNothing.INSTANCE); + this.handlers[MEntityTypes.BLOCK_DISPLAY$registryId] = simpleAddEntityHandler(BlockDisplayPacketHandler.INSTANCE); + this.handlers[MEntityTypes.TEXT_DISPLAY$registryId] = simpleAddEntityHandler(TextDisplayPacketHandler.INSTANCE); + this.handlers[MEntityTypes.ARMOR_STAND$registryId] = simpleAddEntityHandler(ArmorStandPacketHandler.INSTANCE); + this.handlers[MEntityTypes.ITEM$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + this.handlers[MEntityTypes.ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE); + this.handlers[MEntityTypes.GLOW_ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE); + this.handlers[MEntityTypes.ENDERMAN$registryId] = simpleAddEntityHandler(EndermanPacketHandler.INSTANCE); + this.handlers[MEntityTypes.CHEST_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.COMMAND_BLOCK_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.FURNACE_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.HOPPER_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.SPAWNER_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.TNT_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); + this.handlers[MEntityTypes.FIREBALL$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.EYE_OF_ENDER$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.FIREWORK_ROCKET$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.SMALL_FIREBALL$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.EGG$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.ENDER_PEARL$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.EXPERIENCE_BOTTLE$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.SNOWBALL$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.POTION$registryId] = createOptionalCustomProjectileEntityHandler(true); + this.handlers[MEntityTypes.TRIDENT$registryId] = createOptionalCustomProjectileEntityHandler(false); + this.handlers[MEntityTypes.ARROW$registryId] = createOptionalCustomProjectileEntityHandler(false); + this.handlers[MEntityTypes.SPECTRAL_ARROW$registryId] = createOptionalCustomProjectileEntityHandler(false); + if (VersionHelper.isOrAbove1_20_3()) { + this.handlers[MEntityTypes.TNT$registryId] = simpleAddEntityHandler(PrimedTNTPacketHandler.INSTANCE); + } + if (VersionHelper.isOrAbove1_20_5()) { + this.handlers[MEntityTypes.OMINOUS_ITEM_SPAWNER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + } + this.handlers[MEntityTypes.FALLING_BLOCK$registryId] = (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + UUID uuid = buf.readUUID(); + int type = buf.readVarInt(); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + byte xRot = buf.readByte(); + byte yRot = buf.readByte(); + byte yHeadRot = buf.readByte(); + int data = buf.readVarInt(); + // Falling blocks + int remapped = remapBlockState(data, user.clientModEnabled()); + if (remapped != data) { + int xa = buf.readShort(); + int ya = buf.readShort(); + int za = buf.readShort(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(id); + buf.writeUUID(uuid); + buf.writeVarInt(type); + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeByte(xRot); + buf.writeByte(yRot); + buf.writeByte(yHeadRot); + buf.writeVarInt(remapped); + buf.writeShort(xa); + buf.writeShort(ya); + buf.writeShort(za); + } }; + this.handlers[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); + if (furniture != null) { + user.entityPacketHandlers().put(id, new FurniturePacketHandler(furniture.fakeEntityIds())); + user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false); + if (Config.hideBaseEntity() && !furniture.hasExternalModel()) { + event.setCancelled(true); + } + } else { + user.entityPacketHandlers().put(id, ItemDisplayPacketHandler.INSTANCE); + } + }; + this.handlers[MEntityTypes.INTERACTION$registryId] = (user, event) -> { + if (BukkitFurnitureManager.NMS_COLLISION_ENTITY_TYPE != MEntityTypes.INTERACTION) return; + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + // Cancel collider entity packet + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); + if (furniture != null) { + event.setCancelled(true); + user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); + } + }; + this.handlers[MEntityTypes.OAK_BOAT$registryId] = (user, event) -> { + if (BukkitFurnitureManager.NMS_COLLISION_ENTITY_TYPE != MEntityTypes.OAK_BOAT) return; + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + // Cancel collider entity packet + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); + if (furniture != null) { + event.setCancelled(true); + user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); + } + }; + } + + private static EntityTypeHandler simpleAddEntityHandler(EntityPacketHandler handler) { + return (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + user.entityPacketHandlers().put(buf.readVarInt(), handler); + }; + } + + private static EntityTypeHandler createOptionalCustomProjectileEntityHandler(boolean fallback) { + return (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + BukkitProjectileManager.instance().projectileByEntityId(id).ifPresentOrElse(customProjectile -> { + ProjectilePacketHandler handler = new ProjectilePacketHandler(customProjectile, id); + handler.convertAddCustomProjectilePacket(buf, event); + user.entityPacketHandlers().put(id, handler); + }, () -> { + if (fallback) { + user.entityPacketHandlers().put(id, CommonItemPacketHandler.INSTANCE); + } + }); + }; + } + + public interface EntityTypeHandler { + + void handle(NetWorkUser user, ByteBufPacketEvent event); + + class DoNothing implements EntityTypeHandler { + public static final DoNothing INSTANCE = new DoNothing(); + + @Override + public void handle(NetWorkUser user, ByteBufPacketEvent event) { + } + } + } + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + buf.readVarInt(); + buf.readUUID(); + int type = buf.readVarInt(); + this.handlers[type].handle(user, event); + } + } + + public static class SetEntityDataListener implements ByteBufferPacketListener { + + @Override + public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + if (!(user instanceof BukkitServerPlayer serverPlayer)) return; + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + EntityPacketHandler handler = user.entityPacketHandlers().get(id); + if (handler != null) { + handler.handleSetEntityData(serverPlayer, event); + return; + } + if (Config.interceptEntityName()) { + boolean isChanged = false; + List packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf); + for (int i = 0; i < packedItems.size(); i++) { + Object packedItem = packedItems.get(i); + int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); + if (entityDataId != BaseEntityData.CustomName.id()) continue; + // noinspection unchecked + Optional optionalTextComponent = (Optional) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); + if (optionalTextComponent.isEmpty()) continue; + Object textComponent = optionalTextComponent.get(); + String json = ComponentUtils.minecraftToJson(textComponent); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) continue; + Component component = AdventureHelper.jsonToComponent(json); + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); + Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); + packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component)))); + isChanged = true; + break; + } + if (isChanged) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(id); + FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf); + } + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java deleted file mode 100644 index 9a1c5bafa..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ /dev/null @@ -1,2703 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network; - -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.mojang.authlib.GameProfile; -import com.mojang.datafixers.util.Either; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntList; -import net.kyori.adventure.text.Component; -import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; -import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; -import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; -import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent; -import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; -import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; -import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager; -import net.momirealms.craftengine.bukkit.item.BukkitItemManager; -import net.momirealms.craftengine.bukkit.item.behavior.FurnitureItemBehavior; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.injector.ProtectedFieldVisitor; -import net.momirealms.craftengine.bukkit.plugin.network.handler.*; -import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; -import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload; -import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper; -import net.momirealms.craftengine.bukkit.plugin.network.payload.UnknownPayload; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; -import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.bukkit.util.*; -import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; -import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; -import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.entity.player.InteractionHand; -import net.momirealms.craftengine.core.font.FontManager; -import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; -import net.momirealms.craftengine.core.item.CustomItem; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.item.context.UseOnContext; -import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipeHolder; -import net.momirealms.craftengine.core.item.recipe.network.modern.RecipeBookEntry; -import net.momirealms.craftengine.core.item.recipe.network.modern.display.RecipeDisplay; -import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; -import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; -import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; -import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; -import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.locale.TranslationManager; -import net.momirealms.craftengine.core.plugin.logger.Debugger; -import net.momirealms.craftengine.core.plugin.network.*; -import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; -import net.momirealms.craftengine.core.util.*; -import net.momirealms.craftengine.core.world.*; -import net.momirealms.craftengine.core.world.chunk.CEChunk; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; -import net.momirealms.craftengine.core.world.chunk.Palette; -import net.momirealms.craftengine.core.world.chunk.PalettedContainer; -import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; -import net.momirealms.craftengine.core.world.chunk.packet.MCSection; -import net.momirealms.craftengine.core.world.collision.AABB; -import net.momirealms.sparrow.nbt.Tag; -import org.bukkit.*; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.util.RayTraceResult; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -public class PacketConsumers { - private static BukkitNetworkManager.Handlers[] ADD_ENTITY_HANDLERS; - private static int[] BLOCK_STATE_MAPPINGS; - private static int[] MOD_BLOCK_STATE_MAPPINGS; - private static IntIdentityList SERVER_BLOCK_LIST; - private static IntIdentityList BIOME_LIST; - private static Consumer> BIOME_MAPPER; - - public static void initEntities(int registrySize) { - ADD_ENTITY_HANDLERS = new BukkitNetworkManager.Handlers[registrySize]; - Arrays.fill(ADD_ENTITY_HANDLERS, BukkitNetworkManager.Handlers.DO_NOTHING); - ADD_ENTITY_HANDLERS[MEntityTypes.FALLING_BLOCK$registryId] = (user, event) -> { - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - UUID uuid = buf.readUUID(); - int type = buf.readVarInt(); - double x = buf.readDouble(); - double y = buf.readDouble(); - double z = buf.readDouble(); - byte xRot = buf.readByte(); - byte yRot = buf.readByte(); - byte yHeadRot = buf.readByte(); - int data = buf.readVarInt(); - // Falling blocks - int remapped = user.clientModEnabled() ? remapMOD(data) : remap(data); - if (remapped != data) { - int xa = buf.readShort(); - int ya = buf.readShort(); - int za = buf.readShort(); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(id); - buf.writeUUID(uuid); - buf.writeVarInt(type); - buf.writeDouble(x); - buf.writeDouble(y); - buf.writeDouble(z); - buf.writeByte(xRot); - buf.writeByte(yRot); - buf.writeByte(yHeadRot); - buf.writeVarInt(remapped); - buf.writeShort(xa); - buf.writeShort(ya); - buf.writeShort(za); - } - }; - - ADD_ENTITY_HANDLERS[MEntityTypes.BLOCK_DISPLAY$registryId] = simpleAddEntityHandler(BlockDisplayPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.TEXT_DISPLAY$registryId] = simpleAddEntityHandler(TextDisplayPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.ARMOR_STAND$registryId] = simpleAddEntityHandler(ArmorStandPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.ITEM$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.GLOW_ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.ENDERMAN$registryId] = simpleAddEntityHandler(EndermanPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.CHEST_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.COMMAND_BLOCK_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.FURNACE_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.HOPPER_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.SPAWNER_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.TNT_MINECART$registryId] = simpleAddEntityHandler(MinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.FIREBALL$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.EYE_OF_ENDER$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.FIREWORK_ROCKET$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.SMALL_FIREBALL$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.EGG$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.ENDER_PEARL$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.EXPERIENCE_BOTTLE$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.SNOWBALL$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.POTION$registryId] = createOptionalCustomProjectileEntityHandler(true); - ADD_ENTITY_HANDLERS[MEntityTypes.TRIDENT$registryId] = createOptionalCustomProjectileEntityHandler(false); - ADD_ENTITY_HANDLERS[MEntityTypes.ARROW$registryId] = createOptionalCustomProjectileEntityHandler(false); - ADD_ENTITY_HANDLERS[MEntityTypes.SPECTRAL_ARROW$registryId] = createOptionalCustomProjectileEntityHandler(false); - if (VersionHelper.isOrAbove1_20_3()) { - ADD_ENTITY_HANDLERS[MEntityTypes.TNT$registryId] = simpleAddEntityHandler(PrimedTNTPacketHandler.INSTANCE); - } - if (VersionHelper.isOrAbove1_20_5()) { - ADD_ENTITY_HANDLERS[MEntityTypes.OMINOUS_ITEM_SPAWNER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); - } - - ADD_ENTITY_HANDLERS[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> { - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); - if (furniture != null) { - user.entityPacketHandlers().put(id, new FurniturePacketHandler(furniture.fakeEntityIds())); - user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false); - if (Config.hideBaseEntity() && !furniture.hasExternalModel()) { - event.setCancelled(true); - } - } else { - user.entityPacketHandlers().put(id, ItemDisplayPacketHandler.INSTANCE); - } - }; - ADD_ENTITY_HANDLERS[MEntityTypes.INTERACTION$registryId] = (user, event) -> { - if (BukkitFurnitureManager.NMS_COLLISION_ENTITY_TYPE != MEntityTypes.INTERACTION) return; - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - // Cancel collider entity packet - BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); - if (furniture != null) { - event.setCancelled(true); - user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); - } - }; - ADD_ENTITY_HANDLERS[MEntityTypes.OAK_BOAT$registryId] = (user, event) -> { - if (BukkitFurnitureManager.NMS_COLLISION_ENTITY_TYPE != MEntityTypes.OAK_BOAT) return; - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - // Cancel collider entity packet - BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id); - if (furniture != null) { - event.setCancelled(true); - user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); - } - }; - } - - private static BukkitNetworkManager.Handlers simpleAddEntityHandler(EntityPacketHandler handler) { - return (user, event) -> { - FriendlyByteBuf buf = event.getBuffer(); - user.entityPacketHandlers().put(buf.readVarInt(), handler); - }; - } - - private static BukkitNetworkManager.Handlers createOptionalCustomProjectileEntityHandler(boolean fallback) { - return (user, event) -> { - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - BukkitProjectileManager.instance().projectileByEntityId(id).ifPresentOrElse(customProjectile -> { - ProjectilePacketHandler handler = new ProjectilePacketHandler(customProjectile, id); - handler.convertAddCustomProjectilePacket(buf, event); - user.entityPacketHandlers().put(id, handler); - }, () -> { - if (fallback) { - user.entityPacketHandlers().put(id, CommonItemPacketHandler.INSTANCE); - } - }); - }; - } - - public static void setBiomeMapper(Consumer> mapper) { - BIOME_MAPPER = mapper; - } - - public static void remapBiomes(PalettedContainer biomes) { - if (BIOME_MAPPER != null) { - BIOME_MAPPER.accept(biomes); - } - } - - public static void initBlocks(Map map, int registrySize) { - int[] newMappings = new int[registrySize]; - for (int i = 0; i < registrySize; i++) { - newMappings[i] = i; - } - int[] newMappingsMOD = Arrays.copyOf(newMappings, registrySize); - for (Map.Entry entry : map.entrySet()) { - newMappings[entry.getKey()] = entry.getValue(); - if (BlockStateUtils.isVanillaBlock((int) entry.getKey())) { - newMappingsMOD[entry.getKey()] = entry.getValue(); - } - } - for (int i = 0; i < newMappingsMOD.length; i++) { - if (BlockStateUtils.isVanillaBlock(i)) { - newMappingsMOD[i] = newMappings[i]; - } - } - BLOCK_STATE_MAPPINGS = newMappings; - MOD_BLOCK_STATE_MAPPINGS = newMappingsMOD; - SERVER_BLOCK_LIST = new IntIdentityList(registrySize); - BIOME_LIST = new IntIdentityList(RegistryUtils.currentBiomeRegistrySize()); - } - - public static int remap(int stateId) { - return BLOCK_STATE_MAPPINGS[stateId]; - } - - public static int remapMOD(int stateId) { - return MOD_BLOCK_STATE_MAPPINGS[stateId]; - } - - public static final BiConsumer FORGET_LEVEL_CHUNK = (user, event) -> { - try { - BukkitServerPlayer player = (BukkitServerPlayer) user; - FriendlyByteBuf buf = event.getBuffer(); - CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); - if (VersionHelper.isOrAbove1_20_2()) { - long chunkPos = buf.readLong(); - user.removeTrackedChunk(chunkPos); - CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos); - if (ceChunk != null) { - ceChunk.despawnBlockEntities(player); - } - } else { - int x = buf.readInt(); - int y = buf.readInt(); - user.removeTrackedChunk(ChunkPos.asLong(x, y)); - CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(x, y); - if (ceChunk != null) { - ceChunk.despawnBlockEntities(player); - } - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundForgetLevelChunkPacket", e); - } - }; - - public static final BiConsumer LEVEL_CHUNK_WITH_LIGHT = (user, event) -> { - try { - BukkitServerPlayer player = (BukkitServerPlayer) user; - FriendlyByteBuf buf = event.getBuffer(); - int chunkX = buf.readInt(); - int chunkZ = buf.readInt(); - boolean named = !VersionHelper.isOrAbove1_20_2(); - // ClientboundLevelChunkPacketData - int heightmapsCount = 0; - Map heightmapsMap = null; - Tag heightmaps = null; - if (VersionHelper.isOrAbove1_21_5()) { - heightmapsMap = new HashMap<>(); - heightmapsCount = buf.readVarInt(); - for (int i = 0; i < heightmapsCount; i++) { - int key = buf.readVarInt(); - long[] value = buf.readLongArray(); - heightmapsMap.put(key, value); - } - } else { - heightmaps = buf.readNbt(named); - } - - int varInt = buf.readVarInt(); - byte[] buffer = new byte[varInt]; - buf.readBytes(buffer); - int blockEntitiesDataCount = buf.readVarInt(); - List blockEntitiesData = new ArrayList<>(); - for (int i = 0; i < blockEntitiesDataCount; i++) { - byte packedXZ = buf.readByte(); - short y = buf.readShort(); - int type = buf.readVarInt(); - Tag tag = buf.readNbt(named); - BlockEntityData blockEntityData = new BlockEntityData(packedXZ, y, type, tag); - blockEntitiesData.add(blockEntityData); - } - // ClientboundLightUpdatePacketData - BitSet skyYMask = buf.readBitSet(); - BitSet blockYMask = buf.readBitSet(); - BitSet emptySkyYMask = buf.readBitSet(); - BitSet emptyBlockYMask = buf.readBitSet(); - List skyUpdates = buf.readByteArrayList(2048); - List blockUpdates = buf.readByteArrayList(2048); - // 开始处理 - if (user.clientModEnabled()) { - ByteBuf byteBuf = Unpooled.copiedBuffer(buffer); - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf); - FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); - for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) { - MCSection mcSection = new MCSection(user.clientBlockList(), SERVER_BLOCK_LIST, BIOME_LIST); - mcSection.readPacket(friendlyByteBuf); - PalettedContainer container = mcSection.blockStateContainer(); - remapBiomes(mcSection.biomeContainer()); - Palette palette = container.data().palette(); - if (palette.canRemap()) { - palette.remap(PacketConsumers::remapMOD); - } else { - for (int j = 0; j < 4096; j++) { - int state = container.get(j); - int newState = remapMOD(state); - if (newState != state) { - container.set(j, newState); - } - } - } - mcSection.writePacket(newBuf); - } - buffer = newBuf.array(); - } else { - ByteBuf byteBuf = Unpooled.copiedBuffer(buffer); - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf); - FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); - for (int i = 0, count = player.clientSideSectionCount(); i < count; i++) { - MCSection mcSection = new MCSection(user.clientBlockList(), SERVER_BLOCK_LIST, BIOME_LIST); - mcSection.readPacket(friendlyByteBuf); - PalettedContainer container = mcSection.blockStateContainer(); - remapBiomes(mcSection.biomeContainer()); - Palette palette = container.data().palette(); - if (palette.canRemap()) { - palette.remap(PacketConsumers::remap); - } else { - for (int j = 0; j < 4096; j++) { - int state = container.get(j); - int newState = remap(state); - if (newState != state) { - container.set(j, newState); - } - } - } - mcSection.writePacket(newBuf); - } - buffer = newBuf.array(); - } - - // 开始修改 - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeInt(chunkX); - buf.writeInt(chunkZ); - if (VersionHelper.isOrAbove1_21_5()) { - buf.writeVarInt(heightmapsCount); - for (Map.Entry entry : heightmapsMap.entrySet()) { - buf.writeVarInt(entry.getKey()); - buf.writeLongArray(entry.getValue()); - } - } else { - buf.writeNbt(heightmaps, named); - } - buf.writeVarInt(buffer.length); - buf.writeBytes(buffer); - buf.writeVarInt(blockEntitiesDataCount); - for (BlockEntityData blockEntityData : blockEntitiesData) { - buf.writeByte(blockEntityData.packedXZ()); - buf.writeShort(blockEntityData.y()); - buf.writeVarInt(blockEntityData.type()); - buf.writeNbt(blockEntityData.tag(), named); - } - buf.writeBitSet(skyYMask); - buf.writeBitSet(blockYMask); - buf.writeBitSet(emptySkyYMask); - buf.writeBitSet(emptyBlockYMask); - buf.writeByteArrayList(skyUpdates); - buf.writeByteArrayList(blockUpdates); - - ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); - // 记录加载的区块 - player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); - - CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); - CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); - if (ceChunk != null) { - ceChunk.spawnBlockEntities(player); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelChunkWithLightPacket", e); - } - }; - - public static final BiConsumer SECTION_BLOCK_UPDATE = (user, event) -> { - try { - if (user.clientModEnabled()) { - FriendlyByteBuf buf = event.getBuffer(); - long pos = buf.readLong(); - int blocks = buf.readVarInt(); - short[] positions = new short[blocks]; - int[] states = new int[blocks]; - for (int i = 0; i < blocks; i++) { - long k = buf.readVarLong(); - positions[i] = (short) ((int) (k & 4095L)); - states[i] = remapMOD((int) (k >>> 12)); - } - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeLong(pos); - buf.writeVarInt(blocks); - for (int i = 0; i < blocks; i++) { - buf.writeVarLong((long) states[i] << 12 | positions[i]); - } - event.setChanged(true); - } else { - FriendlyByteBuf buf = event.getBuffer(); - long pos = buf.readLong(); - int blocks = buf.readVarInt(); - short[] positions = new short[blocks]; - int[] states = new int[blocks]; - for (int i = 0; i < blocks; i++) { - long k = buf.readVarLong(); - positions[i] = (short) ((int) (k & 4095L)); - states[i] = remap((int) (k >>> 12)); - } - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeLong(pos); - buf.writeVarInt(blocks); - for (int i = 0; i < blocks; i++) { - buf.writeVarLong((long) states[i] << 12 | positions[i]); - } - event.setChanged(true); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSectionBlocksUpdatePacket", e); - } - }; - - public static final BiConsumer BLOCK_UPDATE = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - BlockPos pos = buf.readBlockPos(); - int before = buf.readVarInt(); - if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) { - return; - } - int state = user.clientModEnabled() ? remapMOD(before) : remap(before); - if (state == before) { - return; - } - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeBlockPos(pos); - buf.writeVarInt(state); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundBlockUpdatePacket", e); - } - }; - - public static final BiConsumer LEVEL_EVENT = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - int eventId = buf.readInt(); - if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; - BlockPos blockPos = buf.readBlockPos(); - int state = buf.readInt(); - boolean global = buf.readBoolean(); - int newState = user.clientModEnabled() ? remapMOD(state) : remap(state); - Object blockState = BlockStateUtils.idToBlockState(newState); - Object block = BlockStateUtils.getBlockOwner(blockState); - if (BukkitBlockManager.instance().isBlockSoundRemoved(block) && !FastNMS.INSTANCE.method$BlockStateBase$isAir(blockState)) { - Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); - Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); - Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); - Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); - if (mappedSoundId != null) { - Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty()); - Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); - Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( - mappedBreakSoundHolder, - CoreReflections.instance$SoundSource$BLOCKS, - blockPos.x() + 0.5, - blockPos.y() + 0.5, - blockPos.z() + 0.5, - (FastNMS.INSTANCE.field$SoundType$volume(soundType) + 1.0F) / 2.0F, - FastNMS.INSTANCE.field$SoundType$pitch(soundType) * 0.8F, - RandomUtils.generateRandomLong() - ); - user.sendPacket(packet, true); - } - } - if (newState == state) { - return; - } - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeInt(eventId); - buf.writeBlockPos(blockPos); - buf.writeInt(newState); - buf.writeBoolean(global); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelEventPacket", e); - } - }; - - public static final BiConsumer TEAM_1_20_3 = (user, event) -> { - if (!Config.interceptTeam()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String name = buf.readUtf(); - byte method = buf.readByte(); - if (method != 2 && method != 0) return; - Tag displayName = buf.readNbt(false); - if (displayName == null) return; - byte friendlyFlags = buf.readByte(); - Either eitherVisibility = VersionHelper.isOrAbove1_21_5() ? Either.right(buf.readVarInt()) : Either.left(buf.readUtf(40)); - Either eitherCollisionRule = VersionHelper.isOrAbove1_21_5() ? Either.right(buf.readVarInt()) : Either.left(buf.readUtf(40)); - int color = buf.readVarInt(); - Tag prefix = buf.readNbt(false); - if (prefix == null) return; - Tag suffix = buf.readNbt(false); - if (suffix == null) return; - Map tokens1 = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); - Map tokens2 = CraftEngine.instance().fontManager().matchTags(prefix.getAsString()); - Map tokens3 = CraftEngine.instance().fontManager().matchTags(suffix.getAsString()); - if (tokens1.isEmpty() && tokens2.isEmpty() && tokens3.isEmpty()) return; - NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); - List entities = method == 0 ? buf.readStringList() : null; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(name); - buf.writeByte(method); - buf.writeNbt(tokens1.isEmpty() ? displayName : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens1, context)), false); - buf.writeByte(friendlyFlags); - eitherVisibility.ifLeft(buf::writeUtf).ifRight(buf::writeVarInt); - eitherCollisionRule.ifLeft(buf::writeUtf).ifRight(buf::writeVarInt); - buf.writeVarInt(color); - buf.writeNbt(tokens2.isEmpty() ? prefix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(prefix), tokens2, context)), false); - buf.writeNbt(tokens3.isEmpty() ? suffix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(suffix), tokens3, context)), false); - if (entities != null) { - buf.writeStringList(entities); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetPlayerTeamPacket", e); - } - }; - - public static final TriConsumer PLAYER_INFO_UPDATE = (user, event, packet) -> { - try { - if (!user.isOnline()) return; - if (!Config.interceptPlayerInfo()) return; - List entries = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$entries(packet); - if (entries instanceof MarkedArrayList) { - return; - } - EnumSet> enums = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$actions(packet); - outer: { - for (Object entry : enums) { - if (entry == NetworkReflections.instance$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_DISPLAY_NAME) { - break outer; - } - } - return; - } - boolean isChanged = false; - List newEntries = new MarkedArrayList<>(); - for (Object entry : entries) { - Object mcComponent = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$Entry$displayName(entry); - if (mcComponent == null) { - newEntries.add(entry); - } else { - String json = ComponentUtils.minecraftToJson(mcComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) { - newEntries.add(entry); - } else { - Object newEntry = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket$Entry(entry, - ComponentUtils.adventureToMinecraft(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - newEntries.add(newEntry); - isChanged = true; - } - } - } - if (isChanged) { - event.replacePacket(FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket(enums, newEntries)); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundPlayerInfoUpdatePacket", e); - } - }; - - public static final BiConsumer TEAM_1_20 = (user, event) -> { - if (!Config.interceptTeam()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String name = buf.readUtf(); - byte method = buf.readByte(); - if (method != 2 && method != 0) - return; - String displayName = buf.readUtf(); - byte friendlyFlags = buf.readByte(); - String nameTagVisibility = buf.readUtf(40); - String collisionRule = buf.readUtf(40); - int color = buf.readVarInt(); - String prefix = buf.readUtf(); - String suffix = buf.readUtf(); - - Map tokens1 = CraftEngine.instance().fontManager().matchTags(displayName); - Map tokens2 = CraftEngine.instance().fontManager().matchTags(prefix); - Map tokens3 = CraftEngine.instance().fontManager().matchTags(suffix); - if (tokens1.isEmpty() && tokens2.isEmpty() && tokens3.isEmpty()) return; - event.setChanged(true); - NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); - - List entities = method == 0 ? buf.readStringList() : null; - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(name); - buf.writeByte(method); - buf.writeUtf(tokens1.isEmpty() ? displayName : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(displayName), tokens1, context))); - buf.writeByte(friendlyFlags); - buf.writeUtf(nameTagVisibility); - buf.writeUtf(collisionRule); - buf.writeVarInt(color); - buf.writeUtf(tokens2.isEmpty() ? prefix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(prefix), tokens2, context))); - buf.writeUtf(tokens3.isEmpty() ? suffix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(suffix), tokens3, context))); - if (entities != null) { - buf.writeStringList(entities); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetPlayerTeamPacket", e); - } - }; - - public static final BiConsumer BOSS_EVENT_1_20 = (user, event) -> { - if (!Config.interceptBossBar()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - UUID uuid = buf.readUUID(); - int actionType = buf.readVarInt(); - if (actionType == 0) { - String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) return; - float health = buf.readFloat(); - int color = buf.readVarInt(); - int division = buf.readVarInt(); - byte flag = buf.readByte(); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUUID(uuid); - buf.writeVarInt(actionType); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - buf.writeFloat(health); - buf.writeVarInt(color); - buf.writeVarInt(division); - buf.writeByte(flag); - } else if (actionType == 3) { - String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUUID(uuid); - buf.writeVarInt(actionType); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundBossEventPacket", e); - } - }; - - public static final BiConsumer BOSS_EVENT_1_20_3 = (user, event) -> { - if (!Config.interceptBossBar()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - UUID uuid = buf.readUUID(); - int actionType = buf.readVarInt(); - if (actionType == 0) { - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - float health = buf.readFloat(); - int color = buf.readVarInt(); - int division = buf.readVarInt(); - byte flag = buf.readByte(); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUUID(uuid); - buf.writeVarInt(actionType); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - buf.writeFloat(health); - buf.writeVarInt(color); - buf.writeVarInt(division); - buf.writeByte(flag); - } else if (actionType == 3) { - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUUID(uuid); - buf.writeVarInt(actionType); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundBossEventPacket", e); - } - }; - - public static final BiConsumer SET_OBJECTIVE_1_20 = (user, event) -> { - if (!Config.interceptScoreboard()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String objective = buf.readUtf(); - byte mode = buf.readByte(); - if (mode != 0 && mode != 2) return; - String displayName = buf.readUtf(); - int renderType = buf.readVarInt(); - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(objective); - buf.writeByte(mode); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - buf.writeVarInt(renderType); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetObjectivePacket", e); - } - }; - - public static final BiConsumer SET_OBJECTIVE_1_20_3 = (user, event) -> { - if (!Config.interceptScoreboard()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String objective = buf.readUtf(); - byte mode = buf.readByte(); - if (mode != 0 && mode != 2) return; - Tag displayName = buf.readNbt(false); - if (displayName == null) return; - int renderType = buf.readVarInt(); - boolean optionalNumberFormat = buf.readBoolean(); - if (optionalNumberFormat) { - int format = buf.readVarInt(); - if (format == 0) { - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(objective); - buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - buf.writeVarInt(renderType); - buf.writeBoolean(true); - buf.writeVarInt(0); - } else if (format == 1) { - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); - if (tokens.isEmpty()) return; - Tag style = buf.readNbt(false); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(objective); - buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - buf.writeVarInt(renderType); - buf.writeBoolean(true); - buf.writeVarInt(1); - buf.writeNbt(style, false); - } else if (format == 2) { - Tag fixed = buf.readNbt(false); - if (fixed == null) return; - Map tokens1 = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); - Map tokens2 = CraftEngine.instance().fontManager().matchTags(fixed.getAsString()); - if (tokens1.isEmpty() && tokens2.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(objective); - buf.writeByte(mode); - buf.writeNbt(tokens1.isEmpty() ? displayName : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens1, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - buf.writeVarInt(renderType); - buf.writeBoolean(true); - buf.writeVarInt(2); - buf.writeNbt(tokens2.isEmpty() ? fixed : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(fixed), tokens2, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - } - } else { - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(objective); - buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - buf.writeVarInt(renderType); - buf.writeBoolean(false); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetObjectivePacket", e); - } - }; - - public static final BiConsumer SYSTEM_CHAT_1_20 = (user, event) -> { - if (!Config.interceptSystemChat()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String jsonOrPlainString = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(jsonOrPlainString); - if (tokens.isEmpty()) return; - boolean overlay = buf.readBoolean(); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(jsonOrPlainString), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - buf.writeBoolean(overlay); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSystemChatPacket", e); - } - }; - - public static final BiConsumer SYSTEM_CHAT_1_20_3 = (user, event) -> { - if (!Config.interceptSystemChat()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - boolean overlay = buf.readBoolean(); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - buf.writeBoolean(overlay); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSystemChatPacket", e); - } - }; - - public static final BiConsumer SET_SUBTITLE_TEXT_1_20 = (user, event) -> { - if (!Config.interceptTitle()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetSubtitleTextPacket", e); - } - }; - - public static final BiConsumer SET_SUBTITLE_TEXT_1_20_3 = (user, event) -> { - if (!Config.interceptTitle()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetSubtitleTextPacket", e); - } - }; - - public static final BiConsumer SET_TITLE_TEXT_1_20 = (user, event) -> { - if (!Config.interceptTitle()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetTitleTextPacket", e); - } - }; - - public static final BiConsumer SET_TITLE_TEXT_1_20_3 = (user, event) -> { - if (!Config.interceptTitle()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetTitleTextPacket", e); - } - }; - - public static final BiConsumer SET_ACTIONBAR_TEXT_1_20 = (user, event) -> { - if (!Config.interceptActionBar()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetActionBarTextPacket", e); - } - }; - - public static final BiConsumer SET_ACTIONBAR_TEXT_1_20_3 = (user, event) -> { - if (!Config.interceptActionBar()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetActionBarTextPacket", e); - } - }; - - public static final BiConsumer TAB_LIST_1_20 = (user, event) -> { - if (!Config.interceptTabList()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - String json1 = buf.readUtf(); - String json2 = buf.readUtf(); - Map tokens1 = CraftEngine.instance().fontManager().matchTags(json1); - Map tokens2 = CraftEngine.instance().fontManager().matchTags(json2); - if (tokens1.isEmpty() && tokens2.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); - buf.writeUtf(tokens1.isEmpty() ? json1 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json1), tokens1, context))); - buf.writeUtf(tokens2.isEmpty() ? json2 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json2), tokens2, context))); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundTabListPacket", e); - } - }; - - public static final BiConsumer TAB_LIST_1_20_3 = (user, event) -> { - if (!Config.interceptTabList()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - Tag nbt1 = buf.readNbt(false); - if (nbt1 == null) return; - Tag nbt2 = buf.readNbt(false); - if (nbt2 == null) return; - Map tokens1 = CraftEngine.instance().fontManager().matchTags(nbt1.getAsString()); - Map tokens2 = CraftEngine.instance().fontManager().matchTags(nbt2.getAsString()); - if (tokens1.isEmpty() && tokens2.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - NetworkTextReplaceContext context = NetworkTextReplaceContext.of((BukkitServerPlayer) user); - buf.writeNbt(tokens1.isEmpty() ? nbt1 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt1), tokens1, context)), false); - buf.writeNbt(tokens2.isEmpty() ? nbt2 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt2), tokens2, context)), false); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundTabListPacket", e); - } - }; - - public static final BiConsumer OPEN_SCREEN_1_20 = (user, event) -> { - if (!Config.interceptContainer()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - int containerId = buf.readVarInt(); - int type = buf.readVarInt(); - String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(containerId); - buf.writeVarInt(type); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user)))); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundOpenScreenPacket", e); - } - }; - - public static final BiConsumer OPEN_SCREEN_1_20_3 = (user, event) -> { - if (!Config.interceptContainer()) return; - try { - FriendlyByteBuf buf = event.getBuffer(); - int containerId = buf.readVarInt(); - int type = buf.readVarInt(); - Tag nbt = buf.readNbt(false); - if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - if (tokens.isEmpty()) return; - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(containerId); - buf.writeVarInt(type); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundOpenScreenPacket", e); - } - }; - - public static final BiConsumer LEVEL_PARTICLE_1_21_4 = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - boolean overrideLimiter = buf.readBoolean(); - boolean alwaysShow = buf.readBoolean(); - double x = buf.readDouble(); - double y = buf.readDouble(); - double z = buf.readDouble(); - float xDist = buf.readFloat(); - float yDist = buf.readFloat(); - float zDist = buf.readFloat(); - float maxSpeed = buf.readFloat(); - int count = buf.readInt(); - Object option = FastNMS.INSTANCE.method$StreamCodec$decode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf); - if (option == null) return; - if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return; - Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option); - int id = BlockStateUtils.blockStateToId(blockState); - int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id); - if (remapped == id) return; - Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option); - Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped)); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeBoolean(overrideLimiter); - buf.writeBoolean(alwaysShow); - buf.writeDouble(x); - buf.writeDouble(y); - buf.writeDouble(z); - buf.writeFloat(xDist); - buf.writeFloat(yDist); - buf.writeFloat(zDist); - buf.writeFloat(maxSpeed); - buf.writeInt(count); - FastNMS.INSTANCE.method$StreamCodec$encode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf, remappedOption); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelParticlesPacket", e); - } - }; - - public static final BiConsumer LEVEL_PARTICLE_1_20_5 = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - boolean overrideLimiter = buf.readBoolean(); - double x = buf.readDouble(); - double y = buf.readDouble(); - double z = buf.readDouble(); - float xDist = buf.readFloat(); - float yDist = buf.readFloat(); - float zDist = buf.readFloat(); - float maxSpeed = buf.readFloat(); - int count = buf.readInt(); - Object option = FastNMS.INSTANCE.method$StreamCodec$decode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf); - if (option == null) return; - if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return; - Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option); - int id = BlockStateUtils.blockStateToId(blockState); - int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id); - if (remapped == id) return; - Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option); - Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped)); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeBoolean(overrideLimiter); - buf.writeDouble(x); - buf.writeDouble(y); - buf.writeDouble(z); - buf.writeFloat(xDist); - buf.writeFloat(yDist); - buf.writeFloat(zDist); - buf.writeFloat(maxSpeed); - buf.writeInt(count); - FastNMS.INSTANCE.method$StreamCodec$encode(NetworkReflections.instance$ParticleTypes$STREAM_CODEC, buf, remappedOption); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelParticlesPacket", e); - } - }; - - public static final BiConsumer LEVEL_PARTICLE_1_20 = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - Object particleType = FastNMS.INSTANCE.method$FriendlyByteBuf$readById(buf, MBuiltInRegistries.PARTICLE_TYPE); - boolean overrideLimiter = buf.readBoolean(); - double x = buf.readDouble(); - double y = buf.readDouble(); - double z = buf.readDouble(); - float xDist = buf.readFloat(); - float yDist = buf.readFloat(); - float zDist = buf.readFloat(); - float maxSpeed = buf.readFloat(); - int count = buf.readInt(); - Object option = FastNMS.INSTANCE.method$ClientboundLevelParticlesPacket$readParticle(buf, particleType); - if (option == null) return; - if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return; - Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option); - int id = BlockStateUtils.blockStateToId(blockState); - int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id); - if (remapped == id) return; - Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option); - Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped)); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeId(buf, remappedOption, MBuiltInRegistries.PARTICLE_TYPE); - buf.writeBoolean(overrideLimiter); - buf.writeDouble(x); - buf.writeDouble(y); - buf.writeDouble(z); - buf.writeFloat(xDist); - buf.writeFloat(yDist); - buf.writeFloat(zDist); - buf.writeFloat(maxSpeed); - buf.writeInt(count); - FastNMS.INSTANCE.method$ParticleOptions$writeToNetwork(remappedOption, buf); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLevelParticlesPacket", e); - } - }; - - public static final TriConsumer PLAYER_ACTION = (user, event, packet) -> { - try { - if (!user.isOnline()) return; - BukkitServerPlayer player = (BukkitServerPlayer) user; - Player platformPlayer = player.platformPlayer(); - World world = platformPlayer.getWorld(); - Object blockPos = FastNMS.INSTANCE.field$ServerboundPlayerActionPacket$pos(packet); - BlockPos pos = LocationUtils.fromBlockPos(blockPos); - if (VersionHelper.isFolia()) { - platformPlayer.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), (t) -> { - try { - handlePlayerActionPacketOnMainThread(player, world, pos, packet); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPlayerActionPacket", e); - } - }, () -> {}); - } else { - handlePlayerActionPacketOnMainThread(player, world, pos, packet); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPlayerActionPacket", e); - } - }; - - private static void handlePlayerActionPacketOnMainThread(BukkitServerPlayer player, World world, BlockPos pos, Object packet) { - Object action = FastNMS.INSTANCE.field$ServerboundPlayerActionPacket$action(packet); - if (action == NetworkReflections.instance$ServerboundPlayerActionPacket$Action$START_DESTROY_BLOCK) { - Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world); - Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(serverLevel, LocationUtils.toBlockPos(pos)); - int stateId = BlockStateUtils.blockStateToId(blockState); - // not a custom block - if (BlockStateUtils.isVanillaBlock(stateId)) { - if (Config.enableSoundSystem()) { - Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(blockState); - if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) { - player.startMiningBlock(pos, blockState, null); - return; - } - } - if (player.isMiningBlock()) { - player.stopMiningBlock(); - } else { - player.setClientSideCanBreakBlock(true); - } - return; - } - if (player.isAdventureMode()) { - if (Config.simplifyAdventureBreakCheck()) { - ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); - if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) { - player.preventMiningBlock(); - return; - } - } else { - if (!player.canBreak(pos, null)) { - player.preventMiningBlock(); - return; - } - } - } - player.startMiningBlock(pos, blockState, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId)); - } else if (action == NetworkReflections.instance$ServerboundPlayerActionPacket$Action$ABORT_DESTROY_BLOCK) { - if (player.isMiningBlock()) { - player.abortMiningBlock(); - } - } else if (action == NetworkReflections.instance$ServerboundPlayerActionPacket$Action$STOP_DESTROY_BLOCK) { - if (player.isMiningBlock()) { - player.stopMiningBlock(); - } - } - } - - public static final TriConsumer HELLO_C2S = (user, event, packet) -> { - try { - BukkitServerPlayer player = (BukkitServerPlayer) user; - String name = (String) NetworkReflections.methodHandle$ServerboundHelloPacket$nameGetter.invokeExact(packet); - player.setUnverifiedName(name); - if (VersionHelper.isOrAbove1_20_2()) { - UUID uuid = (UUID) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); - player.setUnverifiedUUID(uuid); - } else { - @SuppressWarnings("unchecked") - Optional uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); - if (uuid.isPresent()) { - player.setUnverifiedUUID(uuid.get()); - } else { - player.setUnverifiedUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); - } - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundHelloPacket", e); - } - }; - - public static final TriConsumer SWING_HAND = (user, event, packet) -> { - try { - if (!user.isOnline()) return; - BukkitServerPlayer player = (BukkitServerPlayer) user; - if (!player.isMiningBlock()) return; - Object hand = FastNMS.INSTANCE.field$ServerboundSwingPacket$hand(packet); - if (hand == CoreReflections.instance$InteractionHand$MAIN_HAND) { - player.onSwingHand(); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundSwingPacket", e); - } - }; - - public static final TriConsumer USE_ITEM_ON = (user, event, packet) -> { - try { - if (!user.isOnline()) return; - BukkitServerPlayer player = (BukkitServerPlayer) user; - if (player.isMiningBlock()) { - player.stopMiningBlock(); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundUseItemOnPacket", e); - } - }; - - public static final TriConsumer RESPAWN = (user, event, packet) -> { - try { - BukkitServerPlayer player = (BukkitServerPlayer) user; - player.clearView(); - Object dimensionKey; - if (!VersionHelper.isOrAbove1_20_2()) { - dimensionKey = NetworkReflections.methodHandle$ClientboundRespawnPacket$dimensionGetter.invokeExact(packet); - } else { - Object commonInfo = NetworkReflections.methodHandle$ClientboundRespawnPacket$commonPlayerSpawnInfoGetter.invokeExact(packet); - dimensionKey = NetworkReflections.methodHandle$CommonPlayerSpawnInfo$dimensionGetter.invokeExact(commonInfo); - } - Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); - World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); - if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); - player.clearTrackedChunks(); - } else { - CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist"); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket", e); - } - }; - - public static final TriConsumer LOGIN = (user, event, packet) -> { - try { - BukkitServerPlayer player = (BukkitServerPlayer) user; - player.setConnectionState(ConnectionState.PLAY); - Object dimensionKey; - if (!VersionHelper.isOrAbove1_20_2()) { - dimensionKey = NetworkReflections.methodHandle$ClientboundLoginPacket$dimensionGetter.invokeExact(packet); - } else { - Object commonInfo = NetworkReflections.methodHandle$ClientboundLoginPacket$commonPlayerSpawnInfoGetter.invokeExact(packet); - dimensionKey = NetworkReflections.methodHandle$CommonPlayerSpawnInfo$dimensionGetter.invokeExact(commonInfo); - } - Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); - World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); - if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); - } else { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist"); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket", e); - } - }; - - // 1.21.4- - // We can't find the best solution, we can only keep the feel as good as possible - // When the hotbar is full, the latest creative mode inventory can only be accessed when the player opens the inventory screen. Currently, it is not worth further handling this issue. - public static final TriConsumer SET_CREATIVE_SLOT = (user, event, packet) -> { - try { - if (VersionHelper.isOrAbove1_21_4()) return; - if (!user.isOnline()) return; - BukkitServerPlayer player = (BukkitServerPlayer) user; - if (VersionHelper.isFolia()) { - player.platformPlayer().getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), (t) -> { - try { - handleSetCreativeSlotPacketOnMainThread(player, packet); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket", e); - } - }, () -> {}); - } else { - handleSetCreativeSlotPacketOnMainThread(player, packet); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket", e); - } - }; - - private static void handleSetCreativeSlotPacketOnMainThread(BukkitServerPlayer player, Object packet) throws Throwable { - Player bukkitPlayer = player.platformPlayer(); - if (bukkitPlayer == null) return; - if (bukkitPlayer.getGameMode() != GameMode.CREATIVE) return; - int slot = VersionHelper.isOrAbove1_20_5() ? (short) NetworkReflections.methodHandle$ServerboundSetCreativeModeSlotPacket$slotNumGetter.invokeExact(packet) : (int) NetworkReflections.methodHandle$ServerboundSetCreativeModeSlotPacket$slotNumGetter.invokeExact(packet); - if (slot < 36 || slot > 44) return; - ItemStack item = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(NetworkReflections.methodHandle$ServerboundSetCreativeModeSlotPacket$itemStackGetter.invokeExact(packet)); - if (ItemStackUtils.isEmpty(item)) return; - if (slot - 36 != bukkitPlayer.getInventory().getHeldItemSlot()) { - return; - } - double interactionRange = player.getCachedInteractionRange(); - // do ray trace to get current block - RayTraceResult result = bukkitPlayer.rayTraceBlocks(interactionRange, FluidCollisionMode.NEVER); - if (result == null) return; - Block hitBlock = result.getHitBlock(); - if (hitBlock == null) return; - ImmutableBlockState state = CraftEngineBlocks.getCustomBlockState(hitBlock); - // not a custom block - if (state == null || state.isEmpty()) return; - Key itemId = state.settings().itemId(); - // no item available - if (itemId == null) return; - Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.vanillaBlockState().literalObject()); - Object vanillaBlockItem = FastNMS.INSTANCE.method$Block$asItem(vanillaBlock); - if (vanillaBlockItem == null) return; - Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey()); - Key blockItemId = KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.method$Registry$getKey(MBuiltInRegistries.ITEM, vanillaBlockItem)); - if (!addItemId.equals(blockItemId)) return; - ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, player); - if (ItemStackUtils.isEmpty(itemStack)) { - CraftEngine.instance().logger().warn("Item: " + itemId + " is not a valid item"); - return; - } - PlayerInventory inventory = bukkitPlayer.getInventory(); - int sameItemSlot = -1; - int emptySlot = -1; - for (int i = 0; i < 9 + 27; i++) { - ItemStack invItem = inventory.getItem(i); - if (ItemStackUtils.isEmpty(invItem)) { - if (emptySlot == -1 && i < 9) emptySlot = i; - continue; - } - if (invItem.getType().equals(itemStack.getType()) && invItem.getItemMeta().equals(itemStack.getItemMeta())) { - if (sameItemSlot == -1) sameItemSlot = i; - } - } - if (sameItemSlot != -1) { - if (sameItemSlot < 9) { - inventory.setHeldItemSlot(sameItemSlot); - ItemStack previousItem = inventory.getItem(slot - 36); - BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, previousItem)); - } else { - ItemStack sameItem = inventory.getItem(sameItemSlot); - int finalSameItemSlot = sameItemSlot; - BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> { - inventory.setItem(finalSameItemSlot, new ItemStack(Material.AIR)); - inventory.setItem(slot - 36, sameItem); - }); - } - } else { - if (item.getAmount() == 1) { - if (ItemStackUtils.isEmpty(inventory.getItem(slot - 36))) { - BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, itemStack)); - return; - } - if (emptySlot != -1) { - inventory.setHeldItemSlot(emptySlot); - inventory.setItem(emptySlot, itemStack); - } else { - BukkitCraftEngine.instance().scheduler().sync().runDelayed(() -> inventory.setItem(slot - 36, itemStack)); - } - } - } - } - - // 1.21.4+ - public static final TriConsumer PICK_ITEM_FROM_BLOCK = (user, event, packet) -> { - try { - if (!user.isOnline()) return; - Player player = (Player) user.platformPlayer(); - if (player == null) return; - Object pos = NetworkReflections.methodHandle$ServerboundPickItemFromBlockPacket$posGetter.invokeExact(packet); - if (VersionHelper.isFolia()) { - int x = FastNMS.INSTANCE.field$Vec3i$x(pos); - int z = FastNMS.INSTANCE.field$Vec3i$z(pos); - BukkitCraftEngine.instance().scheduler().sync().run(() -> { - try { - handlePickItemFromBlockPacketOnMainThread(player, pos); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket", e); - } - }, player.getWorld(), x >> 4, z >> 4); - } else { - BukkitCraftEngine.instance().scheduler().sync().run(() -> { - try { - handlePickItemFromBlockPacketOnMainThread(player, pos); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket", e); - } - }); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromBlockPacket", e); - } - }; - - private static void handlePickItemFromBlockPacketOnMainThread(Player player, Object pos) throws Throwable { - Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()); - Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(serverLevel, pos); - ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); - if (state == null) return; - Key itemId = state.settings().itemId(); - if (itemId == null) return; - pickItem(player, itemId, pos, null); - } - - // 1.21.4+ - public static final TriConsumer PICK_ITEM_FROM_ENTITY = (user, event, packet) -> { - try { - int entityId = (int) NetworkReflections.methodHandle$ServerboundPickItemFromEntityPacket$idGetter.invokeExact(packet); - BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); - if (furniture == null) return; - Player player = (Player) user.platformPlayer(); - if (player == null) return; - if (VersionHelper.isFolia()) { - player.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), (t) -> { - try { - handlePickItemFromEntityOnMainThread(player, furniture); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket", e); - } - }, () -> {}); - } else { - BukkitCraftEngine.instance().scheduler().sync().run(() -> { - try { - handlePickItemFromEntityOnMainThread(player, furniture); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket", e); - } - }); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundPickItemFromEntityPacket", e); - } - }; - - private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Throwable { - Key itemId = furniture.config().settings().itemId(); - if (itemId == null) return; - pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.baseEntity())); - } - - private static void pickItem(Player player, Key itemId, @Nullable Object blockPos, @Nullable Object entity) throws Throwable { - ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, BukkitCraftEngine.instance().adapt(player)); - if (itemStack == null) { - CraftEngine.instance().logger().warn("Item: " + itemId + " is not a valid item"); - return; - } - assert CoreReflections.method$ServerGamePacketListenerImpl$tryPickItem != null; - if (VersionHelper.isOrAbove1_21_5()) { - CoreReflections.method$ServerGamePacketListenerImpl$tryPickItem.invoke( - CoreReflections.methodHandle$ServerPlayer$connectionGetter.invokeExact(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)), - FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack), blockPos, entity, true); - } else { - CoreReflections.method$ServerGamePacketListenerImpl$tryPickItem.invoke( - CoreReflections.methodHandle$ServerPlayer$connectionGetter.invokeExact(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)), FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)); - } - } - - public static final BiConsumer ADD_ENTITY = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - buf.readVarInt(); - buf.readUUID(); - int type = buf.readVarInt(); - ADD_ENTITY_HANDLERS[type].accept(user, event); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundAddEntityPacket", e); - } - }; - - // 1.21.2+ - public static final TriConsumer SYNC_ENTITY_POSITION = (user, event, packet) -> { - try { - int entityId = FastNMS.INSTANCE.method$ClientboundEntityPositionSyncPacket$id(packet); - EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); - if (handler != null) { - handler.handleSyncEntityPosition(user, event, packet); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundEntityPositionSyncPacket", e); - } - }; - - public static final BiConsumer REMOVE_ENTITY = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - boolean isChange = false; - IntList intList = buf.readIntIdList(); - for (int i = 0, size = intList.size(); i < size; i++) { - int entityId = intList.getInt(i); - EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId); - if (handler != null && handler.handleEntitiesRemove(intList)) { - isChange = true; - } - } - if (isChange) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeIntIdList(intList); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundRemoveEntitiesPacket", e); - } - }; - - public static final BiConsumer INTERACT_ENTITY = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - int entityId = BukkitNetworkManager.hasModelEngine() ? - CraftEngine.instance().compatibilityManager().interactionToBaseEntity(buf.readVarInt()) : - buf.readVarInt(); - BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); - if (furniture == null) return; - int actionType = buf.readVarInt(); - BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user; - if (serverPlayer.isSpectatorMode()) return; - Player platformPlayer = serverPlayer.platformPlayer(); - Location location = furniture.baseEntity().getLocation(); - - Runnable mainThreadTask; - if (actionType == 1) { - // ATTACK - boolean usingSecondaryAction = buf.readBoolean(); - if (entityId != furniture.baseEntityId()) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(furniture.baseEntityId()); - buf.writeVarInt(actionType); - buf.writeBoolean(usingSecondaryAction); - } - - mainThreadTask = () -> { - // todo 冒险模式破坏工具白名单 - if (serverPlayer.isAdventureMode() || - !furniture.isValid()) return; - - // todo 重构家具时候注意,需要准备加载好的hitbox类,以获取hitbox坐标 - if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { - return; - } - - FurnitureAttemptBreakEvent preBreakEvent = new FurnitureAttemptBreakEvent(serverPlayer.platformPlayer(), furniture); - if (EventUtils.fireAndCheckCancel(preBreakEvent)) - return; - - if (!BukkitCraftEngine.instance().antiGriefProvider().canBreak(platformPlayer, location)) - return; - - FurnitureBreakEvent breakEvent = new FurnitureBreakEvent(serverPlayer.platformPlayer(), furniture); - if (EventUtils.fireAndCheckCancel(breakEvent)) - return; - - Cancellable cancellable = Cancellable.of(breakEvent::isCancelled, breakEvent::setCancelled); - // execute functions - PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.FURNITURE, furniture) - .withParameter(DirectContextParameters.EVENT, cancellable) - .withParameter(DirectContextParameters.HAND, InteractionHand.MAIN_HAND) - .withParameter(DirectContextParameters.ITEM_IN_HAND, serverPlayer.getItemInHand(InteractionHand.MAIN_HAND)) - .withParameter(DirectContextParameters.POSITION, furniture.position()) - ); - furniture.config().execute(context, EventTrigger.LEFT_CLICK); - furniture.config().execute(context, EventTrigger.BREAK); - if (cancellable.isCancelled()) { - return; - } - - CraftEngineFurniture.remove(furniture, serverPlayer, !serverPlayer.isCreativeMode(), true); - }; - } else if (actionType == 2) { - // INTERACT_AT - float x = buf.readFloat(); - float y = buf.readFloat(); - float z = buf.readFloat(); - // todo 这个是错误的,这是实体的相对位置而非绝对位置 - Location interactionPoint = new Location(platformPlayer.getWorld(), x, y, z); - InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; - boolean usingSecondaryAction = buf.readBoolean(); - if (entityId != furniture.baseEntityId()) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(furniture.baseEntityId()); - buf.writeVarInt(actionType); - buf.writeFloat(x).writeFloat(y).writeFloat(z); - buf.writeVarInt(hand == InteractionHand.MAIN_HAND ? 0 : 1); - buf.writeBoolean(usingSecondaryAction); - } - - mainThreadTask = () -> { - if (!furniture.isValid()) { - return; - } - - // todo 重构家具时候注意,需要准备加载好的hitbox类,以获取hitbox坐标 - if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) { - return; - } - - FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint); - if (EventUtils.fireAndCheckCancel(interactEvent)) { - return; - } - - Item itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); - Cancellable cancellable = Cancellable.of(interactEvent::isCancelled, interactEvent::setCancelled); - // execute functions - PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.EVENT, cancellable) - .withParameter(DirectContextParameters.FURNITURE, furniture) - .withParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) - .withParameter(DirectContextParameters.HAND, hand) - .withParameter(DirectContextParameters.POSITION, furniture.position()) - ); - furniture.config().execute(context, EventTrigger.RIGHT_CLICK); - if (cancellable.isCancelled()) { - return; - } - - // 必须从网络包层面处理,否则无法获取交互的具体实体 - if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty()) { - // try placing another furniture above it - AABB hitBox = furniture.aabbByEntityId(entityId); - if (hitBox == null) return; - Optional> optionalCustomItem = itemInHand.getCustomItem(); - Location eyeLocation = platformPlayer.getEyeLocation(); - Vector direction = eyeLocation.getDirection(); - Location endLocation = eyeLocation.clone(); - endLocation.add(direction.multiply(serverPlayer.getCachedInteractionRange())); - Optional result = hitBox.clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation)); - if (result.isEmpty()) { - return; - } - EntityHitResult hitResult = result.get(); - if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) { - for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) { - if (behavior instanceof FurnitureItemBehavior) { - behavior.useOnBlock(new UseOnContext(serverPlayer, InteractionHand.MAIN_HAND, new BlockHitResult(hitResult.hitLocation(), hitResult.direction(), BlockPos.fromVec3d(hitResult.hitLocation()), false))); - return; - } - } - } - // now simulate vanilla item behavior - serverPlayer.setResendSound(); - FastNMS.INSTANCE.simulateInteraction( - serverPlayer.serverPlayer(), - DirectionUtils.toNMSDirection(hitResult.direction()), - hitResult.hitLocation().x, hitResult.hitLocation().y, hitResult.hitLocation().z, - LocationUtils.toBlockPos(hitResult.blockPos()) - ); - } else { - if (!serverPlayer.isSecondaryUseActive()) { - furniture.findFirstAvailableSeat(entityId).ifPresent(seatPos -> { - if (furniture.tryOccupySeat(seatPos)) { - furniture.spawnSeatEntityForPlayer(serverPlayer, seatPos); - } - }); - } - } - }; - } else if (actionType == 0) { - int hand = buf.readVarInt(); - boolean usingSecondaryAction = buf.readBoolean(); - if (entityId != furniture.baseEntityId()) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(furniture.baseEntityId()); - buf.writeVarInt(actionType); - buf.writeVarInt(hand); - buf.writeBoolean(usingSecondaryAction); - } - return; - } else { - return; - } - - if (VersionHelper.isFolia()) { - platformPlayer.getScheduler().run(BukkitCraftEngine.instance().javaPlugin(), t -> mainThreadTask.run(), () -> {}); - } else { - BukkitCraftEngine.instance().scheduler().executeSync(mainThreadTask); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundInteractPacket", e); - } - }; - - public static final BiConsumer SOUND = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - if (id == 0) { - Key soundId = buf.readKey(); - Float range = null; - if (buf.readBoolean()) { - range = buf.readFloat(); - } - int source = buf.readVarInt(); - int x = buf.readInt(); - int y = buf.readInt(); - int z = buf.readInt(); - float volume = buf.readFloat(); - float pitch = buf.readFloat(); - long seed = buf.readLong(); - Key mapped = BukkitBlockManager.instance().replaceSoundIfExist(soundId); - if (mapped != null) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(0); - buf.writeKey(mapped); - if (range != null) { - buf.writeBoolean(true); - buf.writeFloat(range); - } else { - buf.writeBoolean(false); - } - buf.writeVarInt(source); - buf.writeInt(x); - buf.writeInt(y); - buf.writeInt(z); - buf.writeFloat(volume); - buf.writeFloat(pitch); - buf.writeLong(seed); - } - } else { - Optional optionalSound = FastNMS.INSTANCE.method$IdMap$byId(MBuiltInRegistries.SOUND_EVENT, id - 1); - if (optionalSound.isEmpty()) return; - Object soundEvent = optionalSound.get(); - Key soundId = KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.method$SoundEvent$location(soundEvent)); - int source = buf.readVarInt(); - int x = buf.readInt(); - int y = buf.readInt(); - int z = buf.readInt(); - float volume = buf.readFloat(); - float pitch = buf.readFloat(); - long seed = buf.readLong(); - Key mapped = BukkitBlockManager.instance().replaceSoundIfExist(soundId); - if (mapped != null) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(0); - Object newId = KeyUtils.toResourceLocation(mapped); - Object newSoundEvent = FastNMS.INSTANCE.constructor$SoundEvent(newId, FastNMS.INSTANCE.method$SoundEvent$fixedRange(soundEvent)); - FastNMS.INSTANCE.method$SoundEvent$directEncode(buf, newSoundEvent); - buf.writeVarInt(source); - buf.writeInt(x); - buf.writeInt(y); - buf.writeInt(z); - buf.writeFloat(volume); - buf.writeFloat(pitch); - buf.writeLong(seed); - } - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSoundPacket", e); - } - }; - - // we handle it on packet level to prevent it from being captured by plugins - public static final TriConsumer RENAME_ITEM = (user, event, packet) -> { - try { - if (!Config.filterAnvil()) return; - if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_ANVIL)) { - return; - } - String message = (String) NetworkReflections.methodHandle$ServerboundRenameItemPacket$nameGetter.invokeExact(packet); - if (message != null && !message.isEmpty()) { - // check bypass - FontManager manager = CraftEngine.instance().fontManager(); - IllegalCharacterProcessResult result = manager.processIllegalCharacters(message); - if (result.has()) { - try { - NetworkReflections.methodHandle$ServerboundRenameItemPacket$nameSetter.invokeExact(packet, result.text()); - } catch (ReflectiveOperationException e) { - CraftEngine.instance().logger().warn("Failed to replace chat", e); - } - } - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundRenameItemPacket", e); - } - }; - - // we handle it on packet level to prevent it from being captured by plugins - public static final TriConsumer SIGN_UPDATE = (user, event, packet) -> { - try { - if (!Config.filterSign()) return; - // check bypass - if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_SIGN)) { - return; - } - String[] lines = (String[]) NetworkReflections.methodHandle$ServerboundSignUpdatePacket$linesGetter.invokeExact(packet); - FontManager manager = CraftEngine.instance().fontManager(); - if (!manager.isDefaultFontInUse()) return; - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; - if (line != null && !line.isEmpty()) { - IllegalCharacterProcessResult result = manager.processIllegalCharacters(line); - if (result.has()) { - lines[i] = result.text(); - } - } - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundSignUpdatePacket", e); - } - }; - - // we handle it on packet level to prevent it from being captured by plugins - @SuppressWarnings("unchecked") - public static final TriConsumer EDIT_BOOK = (user, event, packet) -> { - try { - if (!Config.filterBook()) return; - FontManager manager = CraftEngine.instance().fontManager(); - if (!manager.isDefaultFontInUse()) return; - // check bypass - if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_BOOK)) { - return; - } - - boolean changed = false; - - List pages = (List) NetworkReflections.methodHandle$ServerboundEditBookPacket$pagesGetter.invokeExact(packet); - List newPages = new ArrayList<>(pages.size()); - Optional title = (Optional) NetworkReflections.methodHandle$ServerboundEditBookPacket$titleGetter.invokeExact(packet); - Optional newTitle; - - if (title.isPresent()) { - String titleStr = title.get(); - Pair result = processClientString(titleStr, manager); - newTitle = Optional.of(result.right()); - if (result.left()) { - changed = true; - } - } else { - newTitle = Optional.empty(); - } - - for (String page : pages) { - Pair result = processClientString(page, manager); - newPages.add(result.right()); - if (result.left()) { - changed = true; - } - } - - if (changed) { - Object newPacket = NetworkReflections.constructor$ServerboundEditBookPacket.newInstance( - (int) NetworkReflections.methodHandle$ServerboundEditBookPacket$slotGetter.invokeExact(packet), - newPages, - newTitle - ); - event.replacePacket(newPacket); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundEditBookPacket", e); - } - }; - - private static Pair processClientString(String original, FontManager manager) { - if (original.isEmpty()) { - return Pair.of(false, original); - } - int[] codepoints = CharacterUtils.charsToCodePoints(original.toCharArray()); - int[] newCodepoints = new int[codepoints.length]; - boolean hasIllegal = false; - for (int i = 0; i < codepoints.length; i++) { - int codepoint = codepoints[i]; - if (manager.isIllegalCodepoint(codepoint)) { - newCodepoints[i] = '*'; - hasIllegal = true; - } else { - newCodepoints[i] = codepoint; - } - } - return hasIllegal ? Pair.of(true, new String(newCodepoints, 0, newCodepoints.length)) : Pair.of(false, original); - } - - public static final TriConsumer CUSTOM_PAYLOAD_1_20_2 = (user, event, packet) -> { - try { - if (!VersionHelper.isOrAbove1_20_2()) return; - Object payload = NetworkReflections.methodHandle$ServerboundCustomPayloadPacket$payloadGetter.invokeExact(packet); - Payload clientPayload; - if (VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) { - clientPayload = DiscardedPayload.from(payload); - } else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$ServerboundCustomPayloadPacket$UnknownPayload.isInstance(payload)) { - clientPayload = UnknownPayload.from(payload); - } else { - return; - } - if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY)) return; - PayloadHelper.handleReceiver(clientPayload, user); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e); - } - }; - - public static final BiConsumer CUSTOM_PAYLOAD_1_20 = (user, event) -> { - try { - if (VersionHelper.isOrAbove1_20_2()) return; - FriendlyByteBuf byteBuf = event.getBuffer(); - Key key = byteBuf.readKey(); - if (!key.equals(NetworkManager.MOD_CHANNEL_KEY)) return; - PayloadHelper.handleReceiver(new UnknownPayload(key, byteBuf.readBytes(byteBuf.readableBytes())), user); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e); - } - }; - - @SuppressWarnings("unchecked") - public static final BiConsumer SET_ENTITY_DATA = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - EntityPacketHandler handler = user.entityPacketHandlers().get(id); - if (handler != null) { - handler.handleSetEntityData(serverPlayer, event); - return; - } - if (Config.interceptEntityName()) { - boolean isChanged = false; - List packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf); - for (int i = 0; i < packedItems.size(); i++) { - Object packedItem = packedItems.get(i); - int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); - if (entityDataId != BaseEntityData.CustomName.id()) continue; - Optional optionalTextComponent = (Optional) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); - if (optionalTextComponent.isEmpty()) continue; - Object textComponent = optionalTextComponent.get(); - String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); - Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); - packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component)))); - isChanged = true; - break; - } - if (isChanged) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(id); - FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf); - } - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEntityDataPacket", e); - } - }; - - public static final BiConsumer SET_SCORE_1_20_3 = (user, event) -> { - try { - if (!Config.interceptSetScore()) return; - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - boolean isChanged = false; - FriendlyByteBuf buf = event.getBuffer(); - String owner = buf.readUtf(); - String objectiveName = buf.readUtf(); - int score = buf.readVarInt(); - boolean hasDisplay = buf.readBoolean(); - Tag displayName = null; - if (hasDisplay) { - displayName = buf.readNbt(false); - } - outside: - if (displayName != null) { - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); - if (tokens.isEmpty()) break outside; - Component component = AdventureHelper.tagToComponent(displayName); - component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); - displayName = AdventureHelper.componentToTag(component); - isChanged = true; - } - boolean hasNumberFormat = buf.readBoolean(); - int format = -1; - Tag style = null; - Tag fixed = null; - if (hasNumberFormat) { - format = buf.readVarInt(); - if (format == 0) { - if (displayName == null) return; - } else if (format == 1) { - if (displayName == null) return; - style = buf.readNbt(false); - } else if (format == 2) { - fixed = buf.readNbt(false); - if (fixed == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(fixed.getAsString()); - if (tokens.isEmpty() && !isChanged) return; - if (!tokens.isEmpty()) { - Component component = AdventureHelper.tagToComponent(fixed); - component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); - fixed = AdventureHelper.componentToTag(component); - isChanged = true; - } - } - } - if (isChanged) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeUtf(owner); - buf.writeUtf(objectiveName); - buf.writeVarInt(score); - if (hasDisplay) { - buf.writeBoolean(true); - buf.writeNbt(displayName, false); - } else { - buf.writeBoolean(false); - } - if (hasNumberFormat) { - buf.writeBoolean(true); - buf.writeVarInt(format); - if (format == 1) { - buf.writeNbt(style, false); - } else if (format == 2) { - buf.writeNbt(fixed, false); - } - } else { - buf.writeBoolean(false); - } - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetScorePacket", e); - } - }; - - public static final BiConsumer CONTAINER_SET_CONTENT = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - int containerId = buf.readContainerId(); - int stateId = buf.readVarInt(); - int listSize = buf.readVarInt(); - List items = new ArrayList<>(listSize); - boolean changed = false; - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - for (int i = 0; i < listSize; i++) { - ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - Optional optional = BukkitItemManager.instance().s2c(itemStack, serverPlayer); - if (optional.isPresent()) { - items.add(optional.get()); - changed = true; - } else { - items.add(itemStack); - } - } - ItemStack carriedItem = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - ItemStack newCarriedItem = carriedItem; - Optional optional = BukkitItemManager.instance().s2c(carriedItem, serverPlayer); - if (optional.isPresent()) { - changed = true; - newCarriedItem = optional.get(); - } - if (!changed) return; - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeContainerId(containerId); - buf.writeVarInt(stateId); - buf.writeVarInt(listSize); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - for (ItemStack itemStack : items) { - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, itemStack); - } - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newCarriedItem); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundContainerSetContentPacket", e); - } - }; - - public static final BiConsumer CONTAINER_SET_SLOT = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - int containerId = buf.readContainerId(); - int stateId = buf.readVarInt(); - int slot = buf.readShort(); - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - ItemStack itemStack; - try { - itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - } catch (Exception e) { - // 其他插件干的,比如某ty*****er,不要赖到ce头上 - return; - } - BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeContainerId(containerId); - buf.writeVarInt(stateId); - buf.writeShort(slot); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); - }); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundContainerSetSlotPacket", e); - } - }; - - public static final BiConsumer SET_CURSOR_ITEM = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); - }); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetCursorItemPacket", e); - } - }; - - public static final BiConsumer SET_EQUIPMENT = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - boolean changed = false; - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - int entity = buf.readVarInt(); - List> slots = Lists.newArrayList(); - int slotMask; - do { - slotMask = buf.readByte(); - Object equipmentSlot = CoreReflections.instance$EquipmentSlot$values[slotMask & 127]; - ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - Optional optional = BukkitItemManager.instance().s2c(itemStack, serverPlayer); - if (optional.isPresent()) { - changed = true; - itemStack = optional.get(); - } - slots.add(com.mojang.datafixers.util.Pair.of(equipmentSlot, itemStack)); - } while ((slotMask & -128) != 0); - if (changed) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(entity); - int i = slots.size(); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - for (int j = 0; j < i; ++j) { - com.mojang.datafixers.util.Pair pair = slots.get(j); - Enum equipmentSlot = (Enum) pair.getFirst(); - boolean bl = j != i - 1; - int k = equipmentSlot.ordinal(); - buf.writeByte(bl ? k | -128 : k); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, pair.getSecond()); - } - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEquipmentPacket", e); - } - }; - - public static final BiConsumer SET_PLAYER_INVENTORY_1_21_2 = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - int slot = buf.readVarInt(); - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(slot); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); - }); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetPlayerInventoryPacket", e); - } - }; - - public static final BiConsumer SET_CREATIVE_MODE_SLOT = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - if (!serverPlayer.isCreativeMode()) return; - FriendlyByteBuf buf = event.getBuffer(); - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - short slotNum = buf.readShort(); - ItemStack itemStack; - try { - itemStack = VersionHelper.isOrAbove1_20_5() ? - FastNMS.INSTANCE.method$FriendlyByteBuf$readUntrustedItem(friendlyBuf) : FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - } catch (Exception e) { - return; - } - BukkitItemManager.instance().c2s(itemStack).ifPresent((newItemStack) -> { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeShort(slotNum); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - if (VersionHelper.isOrAbove1_20_5()) { - FastNMS.INSTANCE.method$FriendlyByteBuf$writeUntrustedItem(newFriendlyBuf, newItemStack); - } else { - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); - } - }); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket", e); - } - }; - - // 因为不能走编码器只能替换对象 - public static final TriConsumer CONTAINER_CLICK_1_21_5 = (user, event, packet) -> { - try { - BukkitServerPlayer player = (BukkitServerPlayer) user; - int containerId = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$containerId(packet); - int stateId = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$stateId(packet); - short slotNum = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$slotNum(packet); - byte buttonNum = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$buttonNum(packet); - Object clickType = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$clickType(packet); - @SuppressWarnings("unchecked") - Int2ObjectMap changedSlots = FastNMS.INSTANCE.field$ServerboundContainerClickPacket$changedSlots(packet); - Int2ObjectMap newChangedSlots = new Int2ObjectOpenHashMap<>(changedSlots.size()); - for (Int2ObjectMap.Entry entry : changedSlots.int2ObjectEntrySet()) { - newChangedSlots.put(entry.getIntKey(), FastNMS.INSTANCE.constructor$InjectedHashedStack(entry.getValue(), player)); - } - Object carriedItem = FastNMS.INSTANCE.constructor$InjectedHashedStack(FastNMS.INSTANCE.field$ServerboundContainerClickPacket$carriedItem(packet), player); - event.replacePacket(FastNMS.INSTANCE.constructor$ServerboundContainerClickPacket(containerId, stateId, slotNum, buttonNum, clickType, Int2ObjectMaps.unmodifiable(newChangedSlots), carriedItem)); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundContainerClickPacket", e); - } - }; - - public static final BiConsumer CONTAINER_CLICK_1_20 = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - boolean changed = false; - Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - int containerId = buf.readContainerId(); - int stateId = buf.readVarInt(); - short slotNum = buf.readShort(); - byte buttonNum = buf.readByte(); - int clickType = buf.readVarInt(); - int i = buf.readVarInt(); - Int2ObjectMap changedSlots = new Int2ObjectOpenHashMap<>(i); - for (int j = 0; j < i; ++j) { - int k = buf.readShort(); - ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - Optional optional = BukkitItemManager.instance().c2s(itemStack); - if (optional.isPresent()) { - changed = true; - itemStack = optional.get(); - } - changedSlots.put(k, itemStack); - } - ItemStack carriedItem = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); - Optional optional = BukkitItemManager.instance().c2s(carriedItem); - if (optional.isPresent()) { - changed = true; - carriedItem = optional.get(); - } - if (changed) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeContainerId(containerId); - buf.writeVarInt(stateId); - buf.writeShort(slotNum); - buf.writeByte(buttonNum); - buf.writeVarInt(clickType); - buf.writeVarInt(changedSlots.size()); - Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); - changedSlots.forEach((k, v) -> { - buf.writeShort(k); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, v); - }); - FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, carriedItem); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundContainerClickPacket", e); - } - }; - - public static final TriConsumer RESOURCE_PACK_RESPONSE = (user, event, packet) -> { - try { - Object action = FastNMS.INSTANCE.field$ServerboundResourcePackPacket$action(packet); - - if (VersionHelper.isOrAbove1_20_3()) { - UUID uuid = FastNMS.INSTANCE.field$ServerboundResourcePackPacket$id(packet); - if (!user.isResourcePackLoading(uuid)) { - // 不是CraftEngine发送的资源包,不管 - return; - } - } - - if (action == null) { - user.kick(Component.text("Corrupted ResourcePackResponse Packet")); - return; - } - - // 检查是否是拒绝 - if (Config.kickOnDeclined()) { - if (action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$DECLINED || action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$DISCARDED) { - user.kick(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); - return; - } - } - - // 检查是否失败 - if (Config.kickOnFailedApply()) { - if (action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$FAILED_DOWNLOAD - || (VersionHelper.isOrAbove1_20_3() && action == NetworkReflections.instance$ServerboundResourcePackPacket$Action$INVALID_URL)) { - user.kick(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); - return; - } - } - - boolean isTerminal = action != NetworkReflections.instance$ServerboundResourcePackPacket$Action$ACCEPTED && action != NetworkReflections.instance$ServerboundResourcePackPacket$Action$DOWNLOADED; - if (isTerminal && VersionHelper.isOrAbove1_20_2()) { - event.setCancelled(true); - Object packetListener = FastNMS.INSTANCE.method$Connection$getPacketListener(user.connection()); - if (!CoreReflections.clazz$ServerConfigurationPacketListenerImpl.isInstance(packetListener)) return; - // 主线程上处理这个包 - CraftEngine.instance().scheduler().executeSync(() -> { - try { - // 当客户端发出多次成功包的时候,finish会报错,我们忽略他 - NetworkReflections.methodHandle$ServerCommonPacketListener$handleResourcePackResponse.invokeExact(packetListener, packet); - CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask.invokeExact(packetListener, CoreReflections.instance$ServerResourcePackConfigurationTask$TYPE); - } catch (Throwable e) { - Debugger.RESOURCE_PACK.warn(() -> "Cannot finish current task", e); - } - }); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ServerboundResourcePackPacket", e); - } - }; - - public static final TriConsumer ENTITY_EVENT = (user, event, packet) -> { - try { - Object player = user.serverPlayer(); - if (player == null) return; - int entityId = (int) NetworkReflections.methodHandle$ClientboundEntityEventPacket$entityIdGetter.invokeExact(packet); - if (entityId != FastNMS.INSTANCE.method$Entity$getId(player)) return; - byte eventId = (byte) NetworkReflections.methodHandle$ClientboundEntityEventPacket$eventIdGetter.invokeExact(packet); - if (eventId >= 24 && eventId <= 28) { - CraftEngine.instance().fontManager().refreshEmojiSuggestions(user.uuid()); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundEntityEventPacket", e); - } - }; - - public static final TriConsumer MOVE_POS_AND_ROTATE_ENTITY = (user, event, packet) -> { - try { - int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet); - if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) { - event.setCancelled(true); - } - EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); - if (handler != null) { - handler.handleMoveAndRotate(user, event, packet); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundMoveEntityPacket$PosRot", e); - } - }; - - public static final TriConsumer MOVE_POS_ENTITY = (user, event, packet) -> { - try { - int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet); - EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); - if (handler != null) { - handler.handleMove(user, event, packet); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundMoveEntityPacket", e); - } - }; - - public static final TriConsumer ROTATE_HEAD = (user, event, packet) -> { - try { - int entityId = (int) NetworkReflections.methodHandle$ClientboundRotateHeadPacket$entityIdGetter.invokeExact(packet); - if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) { - event.setCancelled(true); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundRotateHeadPacket", e); - } - }; - - public static final TriConsumer SET_ENTITY_MOTION = (user, event, packet) -> { - try { - if (!VersionHelper.isOrAbove1_21_6()) return; - int entityId = (int) NetworkReflections.methodHandle$ClientboundSetEntityMotionPacket$idGetter.invokeExact(packet); - if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) { - event.setCancelled(true); - } - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEntityMotionPacket", e); - } - }; - - // 这个包是由 JoinWorldTask 发出的,客户端收到后会返回 ServerboundFinishConfigurationPacket - @SuppressWarnings("unchecked") - public static final TriConsumer FINISH_CONFIGURATION = (user, event, packet) -> { - try { - if (!VersionHelper.isOrAbove1_20_2() || !Config.sendPackOnJoin()) { - // 防止后期调试进配置阶段造成问题 - user.setShouldProcessFinishConfiguration(false); - return; - } - - if (!user.shouldProcessFinishConfiguration()) return; - Object packetListener = FastNMS.INSTANCE.method$Connection$getPacketListener(user.connection()); - if (!CoreReflections.clazz$ServerConfigurationPacketListenerImpl.isInstance(packetListener)) { - return; - } - - // 防止后续加入的JoinWorldTask再次处理 - user.setShouldProcessFinishConfiguration(false); - - // 检查用户UUID是否已经校验 - if (!user.isUUIDVerified()) { - if (Config.strictPlayerUuidValidation()) { - TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); - user.kick(Component.translatable("disconnect.loginFailedInfo").arguments(Component.translatable("argument.uuid.invalid"))); - return; - } - if (Config.debugResourcePack()) { - TranslationManager.instance().log("warning.network.resource_pack.unverified_uuid", user.name(), user.uuid().toString()); - } - } - - // 取消 ClientboundFinishConfigurationPacket,让客户端发呆,并结束掉当前的进入世界任务 - event.setCancelled(true); - try { - CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask.invokeExact(packetListener, CoreReflections.instance$JoinWorldTask$TYPE); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to finish current task for " + user.name(), e); - } - - if (VersionHelper.isOrAbove1_20_5()) { - // 1.20.5+开始会检查是否结束需要重新设置回去,不然不会发keepAlive包 - CoreReflections.methodHandle$ServerCommonPacketListenerImpl$closedSetter.invokeExact(packetListener, false); - } - - // 请求资源包 - ResourcePackHost host = CraftEngine.instance().packManager().resourcePackHost(); - host.requestResourcePackDownloadLink(user.uuid()).whenComplete((dataList, t) -> { - if (t != null) { - CraftEngine.instance().logger().warn("Failed to get pack data for player " + user.name(), t); - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); - return; - } - if (dataList.isEmpty()) { - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); - return; - } - Queue configurationTasks; - try { - configurationTasks = (Queue) CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$configurationTasksGetter.invokeExact(packetListener); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to get configuration tasks for player " + user.name(), e); - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); - return; - } - // 向配置阶段连接的任务重加入资源包的任务 - for (ResourcePackDownloadData data : dataList) { - configurationTasks.add(FastNMS.INSTANCE.constructor$ServerResourcePackConfigurationTask(ResourcePackUtils.createServerResourcePackInfo(data.uuid(), data.url(), data.sha1()))); - user.addResourcePackUUID(data.uuid()); - } - // 最后再加入一个 JoinWorldTask 并开始资源包任务 - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); - }); - } catch (Throwable e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundFinishConfigurationPacket", e); - } - }; - - public static final TriConsumer LOGIN_FINISHED = (user, event, packet) -> { - try { - GameProfile gameProfile = FastNMS.INSTANCE.field$ClientboundLoginFinishedPacket$gameProfile(packet); - user.setVerifiedName(gameProfile.getName()); - user.setVerifiedUUID(gameProfile.getId()); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginFinishedPacket", e); - } - }; - - public static final BiConsumer ADD_RECIPE_BOOK = (user, event) -> { - try { - FriendlyByteBuf buf = event.getBuffer(); - List entries = buf.readCollection(ArrayList::new, byteBuf -> { - RecipeBookEntry entry = RecipeBookEntry.read(byteBuf); - entry.applyClientboundData((BukkitServerPlayer) user); - return entry; - }); - boolean replace = buf.readBoolean(); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeCollection(entries, ((byteBuf, recipeBookEntry) -> recipeBookEntry.write(byteBuf))); - buf.writeBoolean(replace); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundRecipeBookAddPacket", e); - } - }; - - public static final BiConsumer PLACE_GHOST_RECIPE = (user, event) -> { - try { - if (!VersionHelper.isOrAbove1_21_2()) return; - FriendlyByteBuf buf = event.getBuffer(); - int containerId = buf.readContainerId(); - RecipeDisplay display = RecipeDisplay.read(buf); - display.applyClientboundData((BukkitServerPlayer) user); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeContainerId(containerId); - display.write(buf); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundPlaceGhostRecipePacket", e); - } - }; - - public static final BiConsumer UPDATE_RECIPES = (user, event) -> { - try { - if (VersionHelper.isOrAbove1_21_2()) return; - FriendlyByteBuf buf = event.getBuffer(); - List holders = buf.readCollection(ArrayList::new, byteBuf -> { - LegacyRecipeHolder holder = LegacyRecipeHolder.read(byteBuf); - holder.recipe().applyClientboundData((BukkitServerPlayer) user); - return holder; - }); - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeCollection(holders, ((byteBuf, recipeHolder) -> recipeHolder.write(byteBuf))); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundUpdateRecipesPacket", e); - } - }; - - public static final BiConsumer UPDATE_ADVANCEMENTS = (user, event) -> { - try { - if (!(user instanceof BukkitServerPlayer serverPlayer)) return; - FriendlyByteBuf buf = event.getBuffer(); - boolean reset = buf.readBoolean(); - List added = buf.readCollection(ArrayList::new, byteBuf -> { - AdvancementHolder holder = AdvancementHolder.read(byteBuf); - holder.applyClientboundData(serverPlayer); - return holder; - }); - Set removed = buf.readCollection(Sets::newLinkedHashSetWithExpectedSize, FriendlyByteBuf::readKey); - Map progress = buf.readMap(FriendlyByteBuf::readKey, AdvancementProgress::read); - - boolean showAdvancement = false; - if (VersionHelper.isOrAbove1_21_5()) { - showAdvancement = buf.readBoolean(); - } - - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - - buf.writeBoolean(reset); - buf.writeCollection(added, (byteBuf, advancementHolder) -> advancementHolder.write(byteBuf)); - buf.writeCollection(removed, FriendlyByteBuf::writeKey); - buf.writeMap(progress, FriendlyByteBuf::writeKey, (byteBuf, advancementProgress) -> advancementProgress.write(byteBuf)); - if (VersionHelper.isOrAbove1_21_5()) { - buf.writeBoolean(showAdvancement); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundUpdateAdvancementsPacket", e); - } - }; - - public static final TriConsumer UPDATE_TAGS = (user, event, packet) -> { - try { - Object modifiedPacket = BukkitBlockManager.instance().cachedUpdateTagsPacket(); - if (packet.equals(modifiedPacket) || modifiedPacket == null) return; - event.replacePacket(modifiedPacket); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundUpdateTagsPacket", e); - } - }; -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/BlockDisplayPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/BlockDisplayPacketHandler.java index 953f45bf5..fa86d8d8f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/BlockDisplayPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/BlockDisplayPacketHandler.java @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; import net.momirealms.craftengine.bukkit.entity.data.BlockDisplayEntityData; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.core.entity.player.Player; @@ -36,12 +36,7 @@ public class BlockDisplayPacketHandler implements EntityPacketHandler { if (entityDataId == BlockDisplayEntityData.DisplayedBlock.id()) { Object blockState = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); int stateId = BlockStateUtils.blockStateToId(blockState); - int newStateId; - if (!user.clientModEnabled()) { - newStateId = PacketConsumers.remap(stateId); - } else { - newStateId = PacketConsumers.remapMOD(stateId); - } + int newStateId= BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled()); if (newStateId == stateId) continue; Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java index 80a4c20cd..ecc0a03d5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java @@ -41,7 +41,7 @@ public class CommonItemPacketHandler implements EntityPacketHandler { continue; } ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack); - Optional optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user); + Optional optional = BukkitItemManager.instance().s2c(itemStack, user); if (optional.isEmpty()) continue; isChanged = true; itemStack = optional.get(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/EndermanPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/EndermanPacketHandler.java index 3d5561c01..760820024 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/EndermanPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/EndermanPacketHandler.java @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; import net.momirealms.craftengine.bukkit.entity.data.EnderManData; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.core.entity.player.Player; @@ -38,12 +38,7 @@ public class EndermanPacketHandler implements EntityPacketHandler { Optional blockState = (Optional) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); if (blockState.isEmpty()) continue; int stateId = BlockStateUtils.blockStateToId(blockState.get()); - int newStateId; - if (!user.clientModEnabled()) { - newStateId = PacketConsumers.remap(stateId); - } else { - newStateId = PacketConsumers.remapMOD(stateId); - } + int newStateId = BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled()); if (newStateId == stateId) continue; Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java index 653b1f18a..718946298 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.entity.data.AbstractMinecartData; import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.core.entity.player.Player; @@ -79,12 +79,7 @@ public class MinecartPacketHandler implements EntityPacketHandler { Optional blockState = (Optional) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); if (blockState.isEmpty()) return null; int stateId = BlockStateUtils.blockStateToId(blockState.get()); - int newStateId; - if (!user.clientModEnabled()) { - newStateId = PacketConsumers.remap(stateId); - } else { - newStateId = PacketConsumers.remapMOD(stateId); - } + int newStateId = BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled()); if (newStateId == stateId) return null; Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); return FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( @@ -100,12 +95,7 @@ public class MinecartPacketHandler implements EntityPacketHandler { public Object handle(NetWorkUser user, Object packedItem, int entityDataId) { if (entityDataId != AbstractMinecartData.DisplayBlock.id()) return null; int stateId = (int) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); - int newStateId; - if (!user.clientModEnabled()) { - newStateId = PacketConsumers.remap(stateId); - } else { - newStateId = PacketConsumers.remapMOD(stateId); - } + int newStateId = BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled()); if (newStateId == stateId) return null; Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); return FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, newStateId); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/PrimedTNTPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/PrimedTNTPacketHandler.java index ef3178e21..6fab87f02 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/PrimedTNTPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/PrimedTNTPacketHandler.java @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; import net.momirealms.craftengine.bukkit.entity.data.PrimedTntData; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.core.entity.player.Player; @@ -36,12 +36,7 @@ public class PrimedTNTPacketHandler implements EntityPacketHandler { if (entityDataId == PrimedTntData.BlockState.id()) { Object blockState = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); int stateId = BlockStateUtils.blockStateToId(blockState); - int newStateId; - if (!user.clientModEnabled()) { - newStateId = PacketConsumers.remap(stateId); - } else { - newStateId = PacketConsumers.remapMOD(stateId); - } + int newStateId = BukkitNetworkManager.instance().remapBlockState(stateId, user.clientModEnabled()); if (newStateId == stateId) continue; Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java deleted file mode 100644 index e8d383aec..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.momirealms.craftengine.bukkit.plugin.network.id; - -import com.google.gson.JsonElement; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.VersionHelper; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public class PacketIdFinder { - private static final Map> gamePacketIdsByName = new HashMap<>(); - private static final Map, Integer>> gamePacketIdsByClazz = new HashMap<>(); - private static final int maxC2SPacketId; - private static final int maxS2CPacketId; - - static { - try { - if (VersionHelper.isOrAbove1_21()) { - Object packetReport = CoreReflections.constructor$PacketReport.newInstance((Object) null); - JsonElement jsonElement = (JsonElement) CoreReflections.method$PacketReport$serializePackets.invoke(packetReport); - JsonElement play = jsonElement.getAsJsonObject().get("play"); - for (Map.Entry entry : play.getAsJsonObject().entrySet()) { - Map ids = new HashMap<>(); - gamePacketIdsByName.put(entry.getKey(), ids); - for (var entry2 : entry.getValue().getAsJsonObject().entrySet()) { - ids.put(entry2.getKey(), entry2.getValue().getAsJsonObject().get("protocol_id").getAsInt()); - } - } - } else if (VersionHelper.isOrAbove1_20_5()) { - gamePacketIdsByName.putAll(FastNMS.INSTANCE.gamePacketIdsByName()); - } else { - gamePacketIdsByClazz.putAll(FastNMS.INSTANCE.gamePacketIdsByClazz()); - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to get packets", e); - } - maxS2CPacketId = calculateMaxId("clientbound"); - maxC2SPacketId = calculateMaxId("serverbound"); - } - - private static int calculateMaxId(String direction) { - if (VersionHelper.isOrAbove1_20_5()) { - return gamePacketIdsByName.getOrDefault(direction, Collections.emptyMap()).size(); - } else { - return gamePacketIdsByClazz.getOrDefault(direction, Collections.emptyMap()).size(); - } - } - - public static int c2sGamePackets() { - return maxC2SPacketId; - } - - public static int s2cGamePackets() { - return maxS2CPacketId; - } - - public static int clientboundByName(String packetName) { - return gamePacketIdsByName.get("clientbound").getOrDefault(packetName, -1); - } - - public static int clientboundByClazz(Class clazz) { - return gamePacketIdsByClazz.get("clientbound").getOrDefault(clazz, -1); - } - - public static int serverboundByName(String packetName) { - return gamePacketIdsByName.get("serverbound").getOrDefault(packetName, -1); - } - - public static int serverboundByClazz(Class clazz) { - return gamePacketIdsByClazz.get("serverbound").getOrDefault(clazz, -1); - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java index 99eb1c449..8b33c8fef 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java @@ -2,181 +2,183 @@ package net.momirealms.craftengine.bukkit.plugin.network.id; import net.momirealms.craftengine.bukkit.plugin.network.PacketIds; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; +import net.momirealms.craftengine.core.plugin.network.PacketFlow; public class PacketIds1_20 implements PacketIds { @Override public int clientboundBlockUpdatePacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundBlockUpdatePacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundBlockUpdatePacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSectionBlocksUpdatePacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSectionBlocksUpdatePacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSectionBlocksUpdatePacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundLevelParticlesPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundLevelParticlesPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundLevelParticlesPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundLevelEventPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundLevelEventPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundLevelEventPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundAddEntityPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundAddEntityPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundAddEntityPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundOpenScreenPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundOpenScreenPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundOpenScreenPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSoundPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSoundPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSoundPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundRemoveEntitiesPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundRemoveEntitiesPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundRemoveEntitiesPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetEntityDataPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetEntityDataPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetEntityDataPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetTitleTextPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetTitleTextPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetTitleTextPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetSubtitleTextPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetSubtitleTextPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetSubtitleTextPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetActionBarTextPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetActionBarTextPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetActionBarTextPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundBossEventPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundBossEventPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundBossEventPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSystemChatPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSystemChatPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSystemChatPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundTabListPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundTabListPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundTabListPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetPlayerTeamPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetPlayerTeamPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetPlayerTeamPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetObjectivePacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetObjectivePacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetObjectivePacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundLevelChunkWithLightPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundLevelChunkWithLightPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundLevelChunkWithLightPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundPlayerInfoUpdatePacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundPlayerInfoUpdatePacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundPlayerInfoUpdatePacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetScorePacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetScorePacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetScorePacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundContainerSetContentPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundContainerSetContentPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundContainerSetContentPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundContainerSetSlotPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundContainerSetSlotPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundContainerSetSlotPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetCursorItemPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetCursorItemPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetCursorItemPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetEquipmentPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetEquipmentPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetEquipmentPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundSetPlayerInventoryPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundSetPlayerInventoryPacket); - } - - @Override - public int serverboundContainerClickPacket() { - return PacketIdFinder.serverboundByClazz(NetworkReflections.clazz$ServerboundContainerClickPacket); - } - - @Override - public int serverboundSetCreativeModeSlotPacket() { - return PacketIdFinder.serverboundByClazz(NetworkReflections.clazz$ServerboundSetCreativeModeSlotPacket); - } - - @Override - public int clientboundBlockEventPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundBlockEventPacket); - } - - @Override - public int serverboundInteractPacket() { - return PacketIdFinder.serverboundByClazz(NetworkReflections.clazz$ServerboundInteractPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundSetPlayerInventoryPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundRecipeBookAddPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundRecipeBookAddPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundRecipeBookAddPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundPlaceGhostRecipePacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundPlaceGhostRecipePacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundPlaceGhostRecipePacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundUpdateRecipesPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundUpdateRecipesPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundUpdateRecipesPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundUpdateAdvancementsPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundUpdateAdvancementsPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundUpdateAdvancementsPacket, PacketFlow.CLIENTBOUND); } @Override public int clientboundForgetLevelChunkPacket() { - return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundForgetLevelChunkPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundForgetLevelChunkPacket, PacketFlow.CLIENTBOUND); + } + + @Override + public int clientboundBlockEventPacket() { + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ClientboundBlockEventPacket, PacketFlow.CLIENTBOUND); + } + + @Override + public int serverboundContainerClickPacket() { + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ServerboundContainerClickPacket, PacketFlow.SERVERBOUND); + } + + @Override + public int serverboundSetCreativeModeSlotPacket() { + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ServerboundSetCreativeModeSlotPacket, PacketFlow.SERVERBOUND); + } + + + @Override + public int serverboundInteractPacket() { + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ServerboundInteractPacket, PacketFlow.SERVERBOUND); } @Override public int serverboundCustomPayloadPacket() { - return PacketIdFinder.serverboundByClazz(NetworkReflections.clazz$ServerboundCustomPayloadPacket); + return PlayPacketIdHelper.byClazz(NetworkReflections.clazz$ServerboundCustomPayloadPacket, PacketFlow.SERVERBOUND); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java index 70596861c..9aea4a3df 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java @@ -1,181 +1,182 @@ package net.momirealms.craftengine.bukkit.plugin.network.id; import net.momirealms.craftengine.bukkit.plugin.network.PacketIds; +import net.momirealms.craftengine.core.plugin.network.PacketFlow; public class PacketIds1_20_5 implements PacketIds { @Override public int clientboundBlockUpdatePacket() { - return PacketIdFinder.clientboundByName("minecraft:block_update"); + return PlayPacketIdHelper.byName("minecraft:block_update", PacketFlow.CLIENTBOUND); } @Override public int clientboundSectionBlocksUpdatePacket() { - return PacketIdFinder.clientboundByName("minecraft:section_blocks_update"); + return PlayPacketIdHelper.byName("minecraft:section_blocks_update", PacketFlow.CLIENTBOUND); } @Override public int clientboundLevelParticlesPacket() { - return PacketIdFinder.clientboundByName("minecraft:level_particles"); + return PlayPacketIdHelper.byName("minecraft:level_particles", PacketFlow.CLIENTBOUND); } @Override public int clientboundLevelEventPacket() { - return PacketIdFinder.clientboundByName("minecraft:level_event"); + return PlayPacketIdHelper.byName("minecraft:level_event", PacketFlow.CLIENTBOUND); } @Override public int clientboundAddEntityPacket() { - return PacketIdFinder.clientboundByName("minecraft:add_entity"); + return PlayPacketIdHelper.byName("minecraft:add_entity", PacketFlow.CLIENTBOUND); } @Override public int clientboundOpenScreenPacket() { - return PacketIdFinder.clientboundByName("minecraft:open_screen"); + return PlayPacketIdHelper.byName("minecraft:open_screen", PacketFlow.CLIENTBOUND); } @Override public int clientboundSoundPacket() { - return PacketIdFinder.clientboundByName("minecraft:sound"); + return PlayPacketIdHelper.byName("minecraft:sound", PacketFlow.CLIENTBOUND); } @Override public int clientboundRemoveEntitiesPacket() { - return PacketIdFinder.clientboundByName("minecraft:remove_entities"); + return PlayPacketIdHelper.byName("minecraft:remove_entities", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetEntityDataPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_entity_data"); + return PlayPacketIdHelper.byName("minecraft:set_entity_data", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetTitleTextPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_title_text"); + return PlayPacketIdHelper.byName("minecraft:set_title_text", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetSubtitleTextPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_subtitle_text"); + return PlayPacketIdHelper.byName("minecraft:set_subtitle_text", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetActionBarTextPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_action_bar_text"); + return PlayPacketIdHelper.byName("minecraft:set_action_bar_text", PacketFlow.CLIENTBOUND); } @Override public int clientboundBossEventPacket() { - return PacketIdFinder.clientboundByName("minecraft:boss_event"); + return PlayPacketIdHelper.byName("minecraft:boss_event", PacketFlow.CLIENTBOUND); } @Override public int clientboundSystemChatPacket() { - return PacketIdFinder.clientboundByName("minecraft:system_chat"); + return PlayPacketIdHelper.byName("minecraft:system_chat", PacketFlow.CLIENTBOUND); } @Override public int clientboundTabListPacket() { - return PacketIdFinder.clientboundByName("minecraft:tab_list"); + return PlayPacketIdHelper.byName("minecraft:tab_list", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetPlayerTeamPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_player_team"); + return PlayPacketIdHelper.byName("minecraft:set_player_team", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetObjectivePacket() { - return PacketIdFinder.clientboundByName("minecraft:set_objective"); + return PlayPacketIdHelper.byName("minecraft:set_objective", PacketFlow.CLIENTBOUND); } @Override public int clientboundLevelChunkWithLightPacket() { - return PacketIdFinder.clientboundByName("minecraft:level_chunk_with_light"); + return PlayPacketIdHelper.byName("minecraft:level_chunk_with_light", PacketFlow.CLIENTBOUND); } @Override public int clientboundPlayerInfoUpdatePacket() { - return PacketIdFinder.clientboundByName("minecraft:player_info_update"); + return PlayPacketIdHelper.byName("minecraft:player_info_update", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetScorePacket() { - return PacketIdFinder.clientboundByName("minecraft:set_score"); + return PlayPacketIdHelper.byName("minecraft:set_score", PacketFlow.CLIENTBOUND); } @Override public int clientboundContainerSetContentPacket() { - return PacketIdFinder.clientboundByName("minecraft:container_set_content"); + return PlayPacketIdHelper.byName("minecraft:container_set_content", PacketFlow.CLIENTBOUND); } @Override public int clientboundContainerSetSlotPacket() { - return PacketIdFinder.clientboundByName("minecraft:container_set_slot"); + return PlayPacketIdHelper.byName("minecraft:container_set_slot", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetCursorItemPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_cursor_item"); + return PlayPacketIdHelper.byName("minecraft:set_cursor_item", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetEquipmentPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_equipment"); + return PlayPacketIdHelper.byName("minecraft:set_equipment", PacketFlow.CLIENTBOUND); } @Override public int clientboundSetPlayerInventoryPacket() { - return PacketIdFinder.clientboundByName("minecraft:set_player_inventory"); + return PlayPacketIdHelper.byName("minecraft:set_player_inventory", PacketFlow.CLIENTBOUND); } @Override public int clientboundBlockEventPacket() { - return PacketIdFinder.clientboundByName("minecraft:block_event"); + return PlayPacketIdHelper.byName("minecraft:block_event", PacketFlow.CLIENTBOUND); } @Override public int clientboundRecipeBookAddPacket() { - return PacketIdFinder.clientboundByName("minecraft:recipe_book_add"); + return PlayPacketIdHelper.byName("minecraft:recipe_book_add", PacketFlow.CLIENTBOUND); } @Override public int clientboundPlaceGhostRecipePacket() { - return PacketIdFinder.clientboundByName("minecraft:place_ghost_recipe"); + return PlayPacketIdHelper.byName("minecraft:place_ghost_recipe", PacketFlow.CLIENTBOUND); } @Override public int clientboundUpdateRecipesPacket() { - return PacketIdFinder.clientboundByName("minecraft:update_recipes"); + return PlayPacketIdHelper.byName("minecraft:update_recipes", PacketFlow.CLIENTBOUND); } @Override public int clientboundUpdateAdvancementsPacket() { - return PacketIdFinder.clientboundByName("minecraft:update_advancements"); - } - - @Override - public int serverboundContainerClickPacket() { - return PacketIdFinder.serverboundByName("minecraft:container_click"); - } - - @Override - public int serverboundSetCreativeModeSlotPacket() { - return PacketIdFinder.serverboundByName("minecraft:set_creative_mode_slot"); - } - - @Override - public int serverboundInteractPacket() { - return PacketIdFinder.serverboundByName("minecraft:interact"); + return PlayPacketIdHelper.byName("minecraft:update_advancements", PacketFlow.CLIENTBOUND); } @Override public int clientboundForgetLevelChunkPacket() { - return PacketIdFinder.clientboundByName("minecraft:forget_level_chunk"); + return PlayPacketIdHelper.byName("minecraft:forget_level_chunk", PacketFlow.CLIENTBOUND); + } + + @Override + public int serverboundContainerClickPacket() { + return PlayPacketIdHelper.byName("minecraft:container_click", PacketFlow.SERVERBOUND); + } + + @Override + public int serverboundSetCreativeModeSlotPacket() { + return PlayPacketIdHelper.byName("minecraft:set_creative_mode_slot", PacketFlow.SERVERBOUND); + } + + @Override + public int serverboundInteractPacket() { + return PlayPacketIdHelper.byName("minecraft:interact", PacketFlow.SERVERBOUND); } @Override public int serverboundCustomPayloadPacket() { - return PacketIdFinder.serverboundByName("custom_payload"); + return PlayPacketIdHelper.byName("custom_payload", PacketFlow.SERVERBOUND); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java new file mode 100644 index 000000000..8d903bca8 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java @@ -0,0 +1,61 @@ +package net.momirealms.craftengine.bukkit.plugin.network.id; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.network.PacketFlow; +import net.momirealms.craftengine.core.util.VersionHelper; + +import java.util.*; + +public class PlayPacketIdHelper { + // 1.20.5-latest + private static final Map> byName = new EnumMap<>(PacketFlow.class); + // 1.20-1.20.4 + private static final Map, Integer>> byClazz = new EnumMap<>(PacketFlow.class); + + static { + try { + if (VersionHelper.isOrAbove1_21()) { + Object packetReport = CoreReflections.constructor$PacketReport.newInstance((Object) null); + JsonObject packetReportData = ((JsonElement) CoreReflections.method$PacketReport$serializePackets.invoke(packetReport)).getAsJsonObject(); + JsonObject playData = packetReportData.get("play").getAsJsonObject(); + for (Map.Entry entry : playData.entrySet()) { + Map ids = new HashMap<>(); + byName.put(PacketFlow.valueOf(entry.getKey().toUpperCase(Locale.ROOT)), ids); + for (var entry2 : entry.getValue().getAsJsonObject().entrySet()) { + ids.put(entry2.getKey(), entry2.getValue().getAsJsonObject().get("protocol_id").getAsInt()); + } + } + } else if (VersionHelper.isOrAbove1_20_5()) { + for (Map.Entry> entry : FastNMS.INSTANCE.gamePacketIdsByName().entrySet()) { + byName.put(PacketFlow.valueOf(entry.getKey().toUpperCase(Locale.ROOT)), entry.getValue()); + } + } else { + for (Map.Entry, Integer>> entry : FastNMS.INSTANCE.gamePacketIdsByClazz().entrySet()) { + byClazz.put(PacketFlow.valueOf(entry.getKey().toUpperCase(Locale.ROOT)), entry.getValue()); + } + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to init packet registry", e); + } + } + + public static int count(PacketFlow direction) { + if (VersionHelper.isOrAbove1_20_5()) { + return byName.getOrDefault(direction, Collections.emptyMap()).size(); + } else { + return byClazz.getOrDefault(direction, Collections.emptyMap()).size(); + } + } + + public static int byName(String packetName, PacketFlow direction) { + return byName.get(direction).getOrDefault(packetName, -1); + } + + public static int byClazz(Class clazz, PacketFlow direction) { + return byClazz.get(direction).getOrDefault(clazz, -1); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListener.java new file mode 100644 index 000000000..ebc7dbe34 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListener.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.bukkit.plugin.network.listener; + +import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; + +public interface ByteBufferPacketListener { + + default void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) { + } + + default void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/NMSPacketListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/NMSPacketListener.java new file mode 100644 index 000000000..b7471726b --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/NMSPacketListener.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.bukkit.plugin.network.listener; + +import net.momirealms.craftengine.core.plugin.network.NMSPacketEvent; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; + +public interface NMSPacketListener { + + default void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + } + + default void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java index 808dd56eb..2db50e789 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java @@ -22,6 +22,8 @@ public interface NetworkManager extends Manageable { Channel getChannel(Player player); + int remapBlockState(int stateId, boolean enableMod); + Player[] onlineUsers(); default void sendPacket(@NotNull NetWorkUser player, Object packet) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/PacketFlow.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/PacketFlow.java new file mode 100644 index 000000000..2311d2fad --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/PacketFlow.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.plugin.network; + +public enum PacketFlow { + SERVERBOUND, + CLIENTBOUND; +} diff --git a/gradle.properties b/gradle.properties index 3dfd4ae54..20d23a1d9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -60,9 +60,9 @@ authlib_version=6.0.58 concurrent_util_version=0.0.3 # Proxy settings -#systemProp.socks.proxyHost=127.0.0.1 -#systemProp.socks.proxyPort=7890 -#systemProp.http.proxyHost=127.0.0.1 -#systemProp.http.proxyPort=7890 -#systemProp.https.proxyHost=127.0.0.1 -#systemProp.https.proxyPort=7890 \ No newline at end of file +systemProp.socks.proxyHost=127.0.0.1 +systemProp.socks.proxyPort=7890 +systemProp.http.proxyHost=127.0.0.1 +systemProp.http.proxyPort=7890 +systemProp.https.proxyHost=127.0.0.1 +systemProp.https.proxyPort=7890 \ No newline at end of file From 52f2106067100c806ba087928569bbc764fa063d Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 00:16:57 +0800 Subject: [PATCH 146/226] =?UTF-8?q?=E6=94=B9=E5=96=84=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 101 +++++++++--------- .../plugin/network/id/PlayPacketIdHelper.java | 14 +++ .../ByteBufferPacketListenerHolder.java | 4 + 3 files changed, 69 insertions(+), 50 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListenerHolder.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 370b57d79..26bf40917 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -32,10 +32,11 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.ProtectedFieldVisitor; import net.momirealms.craftengine.bukkit.plugin.network.handler.*; -import net.momirealms.craftengine.bukkit.plugin.network.id.PlayPacketIdHelper; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5; +import net.momirealms.craftengine.bukkit.plugin.network.id.PlayPacketIdHelper; import net.momirealms.craftengine.bukkit.plugin.network.listener.ByteBufferPacketListener; +import net.momirealms.craftengine.bukkit.plugin.network.listener.ByteBufferPacketListenerHolder; import net.momirealms.craftengine.bukkit.plugin.network.listener.NMSPacketListener; import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload; @@ -114,8 +115,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private final BukkitCraftEngine plugin; private final Map, NMSPacketListener> nmsPacketListeners = new IdentityHashMap<>(128); - private final ByteBufferPacketListener[] s2cGamePacketListeners; - private final ByteBufferPacketListener[] c2sGamePacketListeners; + private final ByteBufferPacketListenerHolder[] s2cGamePacketListeners; + private final ByteBufferPacketListenerHolder[] c2sGamePacketListeners; private final TriConsumer packetConsumer; private final TriConsumer, Object> packetsConsumer; @@ -144,8 +145,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes @SuppressWarnings("unchecked") public BukkitNetworkManager(BukkitCraftEngine plugin) { instance = this; - this.s2cGamePacketListeners = new ByteBufferPacketListener[PlayPacketIdHelper.count(PacketFlow.CLIENTBOUND)]; - this.c2sGamePacketListeners = new ByteBufferPacketListener[PlayPacketIdHelper.count(PacketFlow.SERVERBOUND)]; + this.s2cGamePacketListeners = new ByteBufferPacketListenerHolder[PlayPacketIdHelper.count(PacketFlow.CLIENTBOUND)]; + this.c2sGamePacketListeners = new ByteBufferPacketListenerHolder[PlayPacketIdHelper.count(PacketFlow.SERVERBOUND)]; this.hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null; this.hasViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; this.plugin = plugin; @@ -213,20 +214,20 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes this.nmsPacketListeners.put(packet, listener); } - private void registerS2CGamePacketListener(final ByteBufferPacketListener function, int id) { + private void registerS2CGamePacketListener(final ByteBufferPacketListener listener, int id, String name) { if (id == -1) return; if (id < 0 || id >= this.s2cGamePacketListeners.length) { throw new IllegalArgumentException("Invalid packet id: " + id); } - this.s2cGamePacketListeners[id] = function; + this.s2cGamePacketListeners[id] = new ByteBufferPacketListenerHolder(name, listener); } - private void registerC2SGamePacketListener(final ByteBufferPacketListener function, int id) { + private void registerC2SGamePacketListener(final ByteBufferPacketListener listener, int id, String name) { if (id == -1) return; if (id < 0 || id >= this.c2sGamePacketListeners.length) { throw new IllegalArgumentException("Invalid packet id: " + id); } - this.c2sGamePacketListeners[id] = function; + this.c2sGamePacketListeners[id] = new ByteBufferPacketListenerHolder(name, listener); } public void addFakePlayer(Player player) { @@ -281,18 +282,18 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } this.blockStateRemapper = newMappings; this.modBlockStateRemapper = newMappingsMOD; - registerS2CGamePacketListener(new LevelChunkWithLightListener(newMappings, newMappingsMOD, registrySize, RegistryUtils.currentBiomeRegistrySize()), this.packetIds.clientboundLevelChunkWithLightPacket()); - registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket()); - registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket()); + registerS2CGamePacketListener(new LevelChunkWithLightListener(newMappings, newMappingsMOD, registrySize, RegistryUtils.currentBiomeRegistrySize()), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); + registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); + registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); registerS2CGamePacketListener( VersionHelper.isOrAbove1_21_4() ? new LevelParticleListener1_21_4(newMappings, newMappingsMOD) : (VersionHelper.isOrAbove1_20_5() ? new LevelParticleListener1_20_5(newMappings, newMappingsMOD) : new LevelParticleListener1_20(newMappings, newMappingsMOD)), - this.packetIds.clientboundLevelParticlesPacket() + this.packetIds.clientboundLevelParticlesPacket(), "ClientboundLevelParticlesPacket" ); - registerS2CGamePacketListener(new LevelEventListener(newMappings, newMappingsMOD), this.packetIds.clientboundLevelEventPacket()); + registerS2CGamePacketListener(new LevelEventListener(newMappings, newMappingsMOD), this.packetIds.clientboundLevelEventPacket(), "ClientboundLevelEventPacket"); } private void registerPacketListeners() { @@ -321,78 +322,78 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes registerNMSPacketConsumer(new LoginFinishedListener(), NetworkReflections.clazz$ClientboundLoginFinishedPacket); registerNMSPacketConsumer(new UpdateTagsListener(), NetworkReflections.clazz$ClientboundUpdateTagsPacket); registerNMSPacketConsumer(new ContainerClickListener1_21_5(), VersionHelper.isOrAbove1_21_5() ? NetworkReflections.clazz$ServerboundContainerClickPacket : null); - registerS2CGamePacketListener(new ForgetLevelChunkListener(), this.packetIds.clientboundForgetLevelChunkPacket()); - registerS2CGamePacketListener(new SetScoreListener1_20_3(), VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1); - registerS2CGamePacketListener(new AddRecipeBookListener(), this.packetIds.clientboundRecipeBookAddPacket()); - registerS2CGamePacketListener(new PlaceGhostRecipeListener(), this.packetIds.clientboundPlaceGhostRecipePacket()); - registerS2CGamePacketListener(new UpdateRecipesListener(), this.packetIds.clientboundUpdateRecipesPacket()); - registerS2CGamePacketListener(new UpdateAdvancementsListener(), this.packetIds.clientboundUpdateAdvancementsPacket()); - registerS2CGamePacketListener(new RemoveEntityListener(), this.packetIds.clientboundRemoveEntitiesPacket()); - registerS2CGamePacketListener(new SoundListener(), this.packetIds.clientboundSoundPacket()); - registerS2CGamePacketListener(new ContainerSetContentListener(), this.packetIds.clientboundContainerSetContentPacket()); - registerS2CGamePacketListener(new ContainerSetSlotListener(), this.packetIds.clientboundContainerSetSlotPacket()); - registerS2CGamePacketListener(new SetCursorItemListener(), this.packetIds.clientboundSetCursorItemPacket()); - registerS2CGamePacketListener(new SetEquipmentListener(), this.packetIds.clientboundSetEquipmentPacket()); - registerS2CGamePacketListener(new SetPlayerInventoryListener1_21_2(), VersionHelper.isOrAbove1_21_2() ? this.packetIds.clientboundSetPlayerInventoryPacket() : -1); - registerS2CGamePacketListener(new SetEntityDataListener(), this.packetIds.clientboundSetEntityDataPacket()); - registerC2SGamePacketListener(new SetCreativeModeSlotListener(), this.packetIds.serverboundSetCreativeModeSlotPacket()); - registerC2SGamePacketListener(new ContainerClick1_20(), VersionHelper.isOrAbove1_21_5() ? -1 : this.packetIds.serverboundContainerClickPacket()); - registerC2SGamePacketListener(new InteractEntityListener(), this.packetIds.serverboundInteractPacket()); - registerC2SGamePacketListener(new CustomPayloadListener1_20(), VersionHelper.isOrAbove1_20_2() ? -1 : this.packetIds.serverboundCustomPayloadPacket()); - registerS2CGamePacketListener(new AddEntityListener(RegistryUtils.currentEntityTypeRegistrySize()), this.packetIds.clientboundAddEntityPacket()); + registerS2CGamePacketListener(new ForgetLevelChunkListener(), this.packetIds.clientboundForgetLevelChunkPacket(), "ClientboundForgetLevelChunkPacket"); + registerS2CGamePacketListener(new SetScoreListener1_20_3(), VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1, "ClientboundSetScorePacket"); + registerS2CGamePacketListener(new AddRecipeBookListener(), this.packetIds.clientboundRecipeBookAddPacket(), "ClientboundRecipeBookAddPacket"); + registerS2CGamePacketListener(new PlaceGhostRecipeListener(), this.packetIds.clientboundPlaceGhostRecipePacket(), "ClientboundPlaceGhostRecipePacket"); + registerS2CGamePacketListener(new UpdateRecipesListener(), this.packetIds.clientboundUpdateRecipesPacket(), "ClientboundUpdateRecipesPacket"); + registerS2CGamePacketListener(new UpdateAdvancementsListener(), this.packetIds.clientboundUpdateAdvancementsPacket(), "ClientboundUpdateAdvancementsPacket"); + registerS2CGamePacketListener(new RemoveEntityListener(), this.packetIds.clientboundRemoveEntitiesPacket(), "ClientboundRemoveEntitiesPacket"); + registerS2CGamePacketListener(new SoundListener(), this.packetIds.clientboundSoundPacket(), "ClientboundSoundPacket"); + registerS2CGamePacketListener(new ContainerSetContentListener(), this.packetIds.clientboundContainerSetContentPacket(), "ClientboundContainerSetContentPacket"); + registerS2CGamePacketListener(new ContainerSetSlotListener(), this.packetIds.clientboundContainerSetSlotPacket(), "ClientboundContainerSetSlotPacket"); + registerS2CGamePacketListener(new SetCursorItemListener(), this.packetIds.clientboundSetCursorItemPacket(), "ClientboundSetCursorItemPacket"); + registerS2CGamePacketListener(new SetEquipmentListener(), this.packetIds.clientboundSetEquipmentPacket(), "ClientboundSetEquipmentPacket"); + registerS2CGamePacketListener(new SetPlayerInventoryListener1_21_2(), VersionHelper.isOrAbove1_21_2() ? this.packetIds.clientboundSetPlayerInventoryPacket() : -1, "ClientboundSetPlayerInventoryPacket"); + registerS2CGamePacketListener(new SetEntityDataListener(), this.packetIds.clientboundSetEntityDataPacket(), "ClientboundSetEntityDataPacket"); + registerC2SGamePacketListener(new SetCreativeModeSlotListener(), this.packetIds.serverboundSetCreativeModeSlotPacket(), "ServerboundSetCreativeModeSlotPacket"); + registerC2SGamePacketListener(new ContainerClick1_20(), VersionHelper.isOrAbove1_21_5() ? -1 : this.packetIds.serverboundContainerClickPacket(), "ServerboundContainerClickPacket"); + registerC2SGamePacketListener(new InteractEntityListener(), this.packetIds.serverboundInteractPacket(), "ServerboundInteractPacket"); + registerC2SGamePacketListener(new CustomPayloadListener1_20(), VersionHelper.isOrAbove1_20_2() ? -1 : this.packetIds.serverboundCustomPayloadPacket(), "ServerboundCustomPayloadPacket"); + registerS2CGamePacketListener(new AddEntityListener(RegistryUtils.currentEntityTypeRegistrySize()), this.packetIds.clientboundAddEntityPacket(), "ClientboundAddEntityPacket"); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new OpenScreenListener1_20_3() : new OpenScreenListener1_20(), - this.packetIds.clientboundOpenScreenPacket() + this.packetIds.clientboundOpenScreenPacket(), "ClientboundOpenScreenPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new SystemChatListener1_20_3() : new SystemChatListener1_20(), - this.packetIds.clientboundSystemChatPacket() + this.packetIds.clientboundSystemChatPacket(), "ClientboundSystemChatPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new SetActionBarListener1_20_3() : new SetActionBarListener1_20(), - this.packetIds.clientboundSetActionBarTextPacket() + this.packetIds.clientboundSetActionBarTextPacket(), "ClientboundSetActionBarTextPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new TabListListener1_20_3() : new TabListListener1_20(), - this.packetIds.clientboundTabListPacket() + this.packetIds.clientboundTabListPacket(), "ClientboundTabListPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new SetTitleListener1_20_3() : new SetTitleListener1_20(), - this.packetIds.clientboundSetTitleTextPacket() + this.packetIds.clientboundSetTitleTextPacket(), "ClientboundSetTitleTextPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new SetSubtitleListener1_20_3() : new SetSubtitleListener1_20(), - this.packetIds.clientboundSetSubtitleTextPacket() + this.packetIds.clientboundSetSubtitleTextPacket(), "ClientboundSetSubtitleTextPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new BossEventListener1_20_3() : new BossEventListener1_20(), - this.packetIds.clientboundBossEventPacket() + this.packetIds.clientboundBossEventPacket(), "ClientboundBossEventPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new TeamListener1_20_3() : new TeamListener1_20(), - this.packetIds.clientboundSetPlayerTeamPacket() + this.packetIds.clientboundSetPlayerTeamPacket(), "ClientboundSetPlayerTeamPacket" ); registerS2CGamePacketListener( VersionHelper.isOrAbove1_20_3() ? new SetObjectiveListener1_20_3() : new SetObjectiveListener1_20(), - this.packetIds.clientboundSetObjectivePacket() + this.packetIds.clientboundSetObjectivePacket(), "ClientboundSetObjectivePacket" ); } @@ -894,24 +895,24 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes protected void handleS2CByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { int packetID = event.packetID(); - ByteBufferPacketListener s2cGamePacketListener = this.s2cGamePacketListeners[packetID]; - if (s2cGamePacketListener != null) { + ByteBufferPacketListenerHolder holder = this.s2cGamePacketListeners[packetID]; + if (holder != null) { try { - s2cGamePacketListener.onPacketSend(user, event); + holder.listener().onPacketSend(user, event); } catch (Throwable t) { - this.plugin.logger().warn("An error occurred when handling sent packet id: " + packetID, t); + this.plugin.logger().warn("An error occurred when handling packet " + holder.id(), t); } } } protected void handleC2SByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { int packetID = event.packetID(); - ByteBufferPacketListener c2sGamePacketListener = this.c2sGamePacketListeners[packetID]; - if (c2sGamePacketListener != null) { + ByteBufferPacketListenerHolder holder = this.c2sGamePacketListeners[packetID]; + if (holder != null) { try { - c2sGamePacketListener.onPacketReceive(user, event); + holder.listener().onPacketReceive(user, event); } catch (Throwable t) { - this.plugin.logger().warn("An error occurred when handling received packet id: " + packetID, t); + this.plugin.logger().warn("An error occurred when handling packet " + holder.id(), t); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java index 8d903bca8..19ac2a8d1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PlayPacketIdHelper.java @@ -13,6 +13,7 @@ import java.util.*; public class PlayPacketIdHelper { // 1.20.5-latest private static final Map> byName = new EnumMap<>(PacketFlow.class); + private static final Map byId = new EnumMap<>(PacketFlow.class); // 1.20-1.20.4 private static final Map, Integer>> byClazz = new EnumMap<>(PacketFlow.class); @@ -38,6 +39,15 @@ public class PlayPacketIdHelper { byClazz.put(PacketFlow.valueOf(entry.getKey().toUpperCase(Locale.ROOT)), entry.getValue()); } } + if (!byName.isEmpty()) { + for (Map.Entry> entry : byName.entrySet()) { + String[] ids = new String[entry.getValue().size()]; + for (Map.Entry nameIdEntry : entry.getValue().entrySet()) { + ids[nameIdEntry.getValue()] = nameIdEntry.getKey(); + } + byId.put(entry.getKey(), ids); + } + } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to init packet registry", e); } @@ -51,6 +61,10 @@ public class PlayPacketIdHelper { } } + public static String byId(int id, PacketFlow direction) { + return byId.get(direction)[id]; + } + public static int byName(String packetName, PacketFlow direction) { return byName.get(direction).getOrDefault(packetName, -1); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListenerHolder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListenerHolder.java new file mode 100644 index 000000000..d59614303 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/listener/ByteBufferPacketListenerHolder.java @@ -0,0 +1,4 @@ +package net.momirealms.craftengine.bukkit.plugin.network.listener; + +public record ByteBufferPacketListenerHolder(String id, ByteBufferPacketListener listener) { +} From 80a021d197a9344373ca1fcf75c5da02f658e3d0 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 00:32:13 +0800 Subject: [PATCH 147/226] =?UTF-8?q?=E7=A7=BB=E9=99=A4viaversion=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 14 +++----------- gradle.properties | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 26bf40917..2952fe1bc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -137,7 +137,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private static final String PACKET_DECODER = "craftengine_decoder"; private final boolean hasModelEngine; - private final boolean hasViaVersion; private int[] blockStateRemapper; private int[] modBlockStateRemapper; @@ -148,7 +147,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes this.s2cGamePacketListeners = new ByteBufferPacketListenerHolder[PlayPacketIdHelper.count(PacketFlow.CLIENTBOUND)]; this.c2sGamePacketListeners = new ByteBufferPacketListenerHolder[PlayPacketIdHelper.count(PacketFlow.SERVERBOUND)]; this.hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null; - this.hasViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; this.plugin = plugin; // set up packet id this.packetIds = VersionHelper.isOrAbove1_20_5() ? new PacketIds1_20_5() : new PacketIds1_20(); @@ -534,13 +532,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes return hasModelEngine; } - public boolean hasViaVersion() { - return hasViaVersion; - } - public void simulatePacket(@NotNull NetWorkUser player, Object packet) { Channel channel = player.nettyChannel(); - if (channel.isOpen()) { + if (channel != null && channel.isOpen()) { List handlerNames = channel.pipeline().names(); if (handlerNames.contains("via-encoder")) { channel.pipeline().context("via-decoder").fireChannelRead(packet); @@ -673,9 +667,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes String encoderName = pipeline.names().contains("outbound_config") ? "outbound_config" : "encoder"; pipeline.addBefore(encoderName, PACKET_ENCODER, new PluginChannelEncoder(user)); - channel.closeFuture().addListener((ChannelFutureListener) future -> { - handleDisconnection(user.nettyChannel()); - }); + channel.closeFuture().addListener((ChannelFutureListener) future -> handleDisconnection(user.nettyChannel())); setUser(channel, user); } @@ -933,7 +925,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private void decompress(ChannelHandlerContext ctx, ByteBuf input, ByteBuf output) { ChannelHandler decompressor = ctx.pipeline().get("decompress"); if (decompressor != null) { - ByteBuf temp = (ByteBuf) callDecode(decompressor, ctx, input).get(0); + ByteBuf temp = (ByteBuf) callDecode(decompressor, ctx, input).getFirst(); try { output.clear().writeBytes(temp); } finally { diff --git a/gradle.properties b/gradle.properties index 20d23a1d9..efd0b34c1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.2 +project_version=0.0.63.3 config_version=45 lang_version=29 project_group=net.momirealms From d1541d428fd4f8eebb090c82a3b1f0fc0cefb686 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 00:46:50 +0800 Subject: [PATCH 148/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=8D=E8=BD=BD?= =?UTF-8?q?=E5=90=8E=E5=9F=BA=E4=BA=8Etag=E7=9A=84=E7=89=A9=E5=93=81?= =?UTF-8?q?=E6=9C=AA=E8=83=BD=E5=8F=8A=E6=97=B6=E5=8F=82=E4=B8=8E=E9=85=8D?= =?UTF-8?q?=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/recipe/BukkitRecipeManager.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index b738734a3..06d11bf38 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -258,7 +258,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { // 已经被替换过的数据包配方 private final Set replacedDatapackRecipes = new HashSet<>(); // 换成的数据包配方 - private Map> lastDatapackRecipes = Map.of(); + private Map lastDatapackRecipes = Map.of(); private Object lastRecipeManager = null; public BukkitRecipeManager(BukkitCraftEngine plugin) { @@ -386,18 +386,33 @@ public class BukkitRecipeManager extends AbstractRecipeManager { } boolean hasDisabledAny = !Config.disabledVanillaRecipes().isEmpty(); - for (Map.Entry> entry : this.lastDatapackRecipes.entrySet()) { + for (Map.Entry entry : this.lastDatapackRecipes.entrySet()) { + Key id = entry.getKey(); if (hasDisabledAny && Config.disabledVanillaRecipes().contains(entry.getKey())) { - this.recipesToUnregister.add(Pair.of(entry.getKey(), false)); + this.recipesToUnregister.add(Pair.of(id, false)); continue; } - markAsDataPackRecipe(entry.getKey()); - registerInternalRecipe(entry.getKey(), entry.getValue()); + + JsonObject jsonObject = entry.getValue(); + Key serializerType = Key.of(jsonObject.get("type").getAsString()); + // noinspection unchecked + RecipeSerializer> serializer = (RecipeSerializer>) BuiltInRegistries.RECIPE_SERIALIZER.getValue(serializerType); + if (serializer == null) { + continue; + } + + try { + Recipe recipe = serializer.readJson(id, jsonObject); + markAsDataPackRecipe(id); + registerInternalRecipe(id, recipe); + } catch (Exception e) { + this.plugin.logger().warn("Failed to load data pack recipe " + id + ". Json: " + jsonObject, e); + } } } @SuppressWarnings("unchecked") - private Map> scanResources() throws Throwable { + private Map scanResources() throws Throwable { Object fileToIdConverter = CoreReflections.methodHandle$FileToIdConverter$json.invokeExact((String) (VersionHelper.isOrAbove1_21() ? "recipe" : "recipes")); Object minecraftServer = FastNMS.INSTANCE.method$MinecraftServer$getServer(); Object packRepository = CoreReflections.methodHandle$MinecraftServer$getPackRepository.invokeExact(minecraftServer); @@ -406,7 +421,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { for (Object pack : selected) { packResources.add(CoreReflections.methodHandle$Pack$open.invokeExact(pack)); } - Map> recipes = new HashMap<>(); + Map recipes = new HashMap<>(); try (AutoCloseable resourceManager = (AutoCloseable) CoreReflections.methodHandle$MultiPackResourceManagerConstructor.invokeExact(CoreReflections.instance$PackType$SERVER_DATA, packResources)) { Map scannedResources = (Map) CoreReflections.methodHandle$FileToIdConverter$listMatchingResources.invokeExact(fileToIdConverter, resourceManager); @@ -414,17 +429,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { Key id = extractKeyFromResourceLocation(entry.getKey().toString()); Reader reader = (Reader) CoreReflections.methodHandle$Resource$openAsReader.invokeExact(entry.getValue()); JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject(); - Key serializerType = Key.of(jsonObject.get("type").getAsString()); - RecipeSerializer> serializer = (RecipeSerializer>) BuiltInRegistries.RECIPE_SERIALIZER.getValue(serializerType); - if (serializer == null) { - continue; - } - try { - Recipe recipe = serializer.readJson(id, jsonObject); - recipes.put(id, recipe); - } catch (Exception e) { - this.plugin.logger().warn("Failed to load data pack recipe " + id + ". Json: " + jsonObject, e); - } + recipes.put(id, jsonObject); } } catch (Throwable e) { this.plugin.logger().warn("Unknown error occurred when loading data pack recipes", e); From 517965711b6f409d1ee18fa08a44dde2e98317fa Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 00:59:17 +0800 Subject: [PATCH 149/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0stepOn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/injector/BlockGenerator.java | 17 +++++++++++++++++ .../reflection/minecraft/CoreReflections.java | 4 ++++ .../craftengine/core/block/BlockBehavior.java | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index f7105c15e..ae5e8c670 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -167,6 +167,9 @@ public final class BlockGenerator { // updateEntityMovementAfterFallOn .method(ElementMatchers.is(CoreReflections.method$Block$updateEntityMovementAfterFallOn)) .intercept(MethodDelegation.to(UpdateEntityMovementAfterFallOnInterceptor.INSTANCE)) + // stepOn + .method(ElementMatchers.is(CoreReflections.method$Block$stepOn)) + .intercept(MethodDelegation.to(StepOnInterceptor.INSTANCE)) ; // 1.21.5+ if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) { @@ -708,6 +711,20 @@ public final class BlockGenerator { } } + public static class StepOnInterceptor { + public static final StepOnInterceptor INSTANCE = new StepOnInterceptor(); + + @RuntimeType + public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + holder.value().stepOn(thisObj, args, superMethod); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run stepOn", e); + } + } + } + public static class FallOnInterceptor { public static final FallOnInterceptor INSTANCE = new FallOnInterceptor(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 48eb0072e..30a9f19cc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -1687,6 +1687,10 @@ public final class CoreReflections { ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, clazz$BlockState, clazz$LevelReader, clazz$BlockPos) ); + public static final Method method$Block$stepOn = requireNonNull( + ReflectionUtils.getMethod(clazz$Block, void.class, new String[] {"stepOn", "a"}, clazz$Level, clazz$BlockPos, clazz$BlockState, clazz$Entity) + ); + public static final Method method$BlockBehaviour$onExplosionHit = MiscUtils.requireNonNullIf( ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, clazz$BlockState, VersionHelper.isOrAbove1_21_2() ? clazz$ServerLevel : clazz$Level, clazz$BlockPos, clazz$Explosion, BiConsumer.class), VersionHelper.isOrAbove1_21() diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 4f20228e3..f1aba39ce 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -178,6 +178,11 @@ public abstract class BlockBehavior { public void spawnAfterBreak(Object thisBlock, Object[] args, Callable superMethod) throws Exception { } + // Level level, BlockPos pos, BlockState state, Entity entity + public void stepOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); + } + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { return state; } From 2b4ec32d13382f9ca751bbdb8b22ec85ec950d7b Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 01:26:24 +0800 Subject: [PATCH 150/226] =?UTF-8?q?=E6=94=B9=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/FenceBlockBehavior.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java index d167464d2..812fcd935 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java @@ -33,7 +33,7 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { private final BooleanProperty eastProperty; private final BooleanProperty southProperty; private final BooleanProperty westProperty; - private final Object selfBlockTag; + private final Object fenceBlockTag; private final Object connectableBlockTag; private final boolean canLeash; @@ -42,7 +42,7 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { BooleanProperty eastProperty, BooleanProperty southProperty, BooleanProperty westProperty, - Object selfBlockTag, + Object fenceBlockTag, Object connectableBlockTag, boolean canLeash) { super(customBlock); @@ -50,7 +50,7 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { this.eastProperty = eastProperty; this.southProperty = southProperty; this.westProperty = westProperty; - this.selfBlockTag = selfBlockTag; + this.fenceBlockTag = fenceBlockTag; this.connectableBlockTag = connectableBlockTag; this.canLeash = canLeash; } @@ -70,7 +70,7 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { private boolean isSameFence(BlockStateWrapper state) { Object blockState = state.literalObject(); - return FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.selfBlockTag) + return FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.fenceBlockTag) && FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.connectableBlockTag) == FastNMS.INSTANCE.method$BlockStateBase$is(this.customBlock.defaultState().customBlockState().literalObject(), this.connectableBlockTag); } @@ -144,12 +144,12 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { BooleanProperty east = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("east"), "warning.config.block.behavior.fence.missing_east"); BooleanProperty south = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("south"), "warning.config.block.behavior.fence.missing_south"); BooleanProperty west = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("west"), "warning.config.block.behavior.fence.missing_west"); - Object selfBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("self-block-tag", "minecraft:fences").toString()))); - selfBlockTag = selfBlockTag != null ? selfBlockTag : MTagKeys.Block$FENCES; + Object fenceBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("fence-block-tag", "minecraft:fences").toString()))); + fenceBlockTag = fenceBlockTag != null ? fenceBlockTag : MTagKeys.Block$FENCES; Object connectableBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("connectable-block-tag", "minecraft:wooden_fences").toString()))); connectableBlockTag = connectableBlockTag != null ? connectableBlockTag : MTagKeys.Block$WOODEN_FENCES; boolean canLeash = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-leash", false), "can-leash"); - return new FenceBlockBehavior(block, north, east, south, west, selfBlockTag, connectableBlockTag, canLeash); + return new FenceBlockBehavior(block, north, east, south, west, fenceBlockTag, connectableBlockTag, canLeash); } } } From 5f54171ec35b1c98bb667905f8761d07e4031219 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 01:37:32 +0800 Subject: [PATCH 151/226] =?UTF-8?q?=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/FenceBlockBehavior.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java index 812fcd935..0a4de1cbf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java @@ -33,7 +33,6 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { private final BooleanProperty eastProperty; private final BooleanProperty southProperty; private final BooleanProperty westProperty; - private final Object fenceBlockTag; private final Object connectableBlockTag; private final boolean canLeash; @@ -42,7 +41,6 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { BooleanProperty eastProperty, BooleanProperty southProperty, BooleanProperty westProperty, - Object fenceBlockTag, Object connectableBlockTag, boolean canLeash) { super(customBlock); @@ -50,7 +48,6 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { this.eastProperty = eastProperty; this.southProperty = southProperty; this.westProperty = westProperty; - this.fenceBlockTag = fenceBlockTag; this.connectableBlockTag = connectableBlockTag; this.canLeash = canLeash; } @@ -70,7 +67,7 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { private boolean isSameFence(BlockStateWrapper state) { Object blockState = state.literalObject(); - return FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.fenceBlockTag) + return FastNMS.INSTANCE.method$BlockStateBase$is(blockState, MTagKeys.Block$FENCES) && FastNMS.INSTANCE.method$BlockStateBase$is(blockState, this.connectableBlockTag) == FastNMS.INSTANCE.method$BlockStateBase$is(this.customBlock.defaultState().customBlockState().literalObject(), this.connectableBlockTag); } @@ -144,12 +141,10 @@ public class FenceBlockBehavior extends BukkitBlockBehavior { BooleanProperty east = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("east"), "warning.config.block.behavior.fence.missing_east"); BooleanProperty south = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("south"), "warning.config.block.behavior.fence.missing_south"); BooleanProperty west = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("west"), "warning.config.block.behavior.fence.missing_west"); - Object fenceBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("fence-block-tag", "minecraft:fences").toString()))); - fenceBlockTag = fenceBlockTag != null ? fenceBlockTag : MTagKeys.Block$FENCES; Object connectableBlockTag = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("connectable-block-tag", "minecraft:wooden_fences").toString()))); connectableBlockTag = connectableBlockTag != null ? connectableBlockTag : MTagKeys.Block$WOODEN_FENCES; boolean canLeash = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-leash", false), "can-leash"); - return new FenceBlockBehavior(block, north, east, south, west, fenceBlockTag, connectableBlockTag, canLeash); + return new FenceBlockBehavior(block, north, east, south, west, connectableBlockTag, canLeash); } } } From 13bcf6333c066bf0d5b4e280275d6865676e3df1 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 01:42:22 +0800 Subject: [PATCH 152/226] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- .../block/behavior/DirectionalAttachedBlockBehavior.java | 4 ++-- common-files/src/main/resources/translations/en.yml | 2 +- common-files/src/main/resources/translations/zh_cn.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 836dcd8f0..daf38199a 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,10 @@ The code you contribute will be open-sourced under the GPLv3 license. If you pre 3. Once done, submit a **pull request** to **dev** branch for review. We appreciate your contributions! ## Differences Between Versions -| Version | Official Support | Max Players | Dev Builds | -|-------------------|------------------|-------------|------------| -| Community Edition | ❌ No | 30 | ❌ No | -| Premium Edition | ✔️ Yes | Unlimited | ✔️ Yes | +| Version | Official Support | Exclusive Features | Dev Builds | +|-------------------|------------------|--------------------|------------| +| Community Edition | ❌ No | ❌ No | ❌ No | +| Premium Edition | ✔️ Yes | ✔️ Yes | ✔️ Yes | ### 💖 Support the Developer Help sustain CraftEngine's development by going Premium! diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index 1b33c2747..a155235db 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -137,11 +137,11 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Property facing = ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.surface_attached.missing_facing"); + Property facing = ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.directional_attached.missing_facing"); boolean isHorizontalDirection = facing.valueClass() == HorizontalDirection.class; boolean isDirection = facing.valueClass() == Direction.class; if (!(isHorizontalDirection || isDirection)) { - throw new LocalizedResourceConfigException("warning.config.block.behavior.surface_attached.missing_facing"); + throw new LocalizedResourceConfigException("warning.config.block.behavior.directional_attached.missing_facing"); } Tuple, Set, Set> tuple = readTagsAndState(arguments); int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 559efd97f..71250f8c3 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -319,7 +319,7 @@ warning.config.block.behavior.pressure_plate.missing_powered: "Issue fou warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.block.behavior.double_high.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'double_block' behavior." warning.config.block.behavior.change_over_time.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' argument for 'change_over_time_block' behavior." -warning.config.block.behavior.surface_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'surface_attached_block' behavior." +warning.config.block.behavior.directional_attached.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'directional_attached_block' behavior." warning.config.block.behavior.wall_torch_particle.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'wall_torch_particle_block' behavior." warning.config.block.behavior.fence.missing_north: "Issue found in file - The block '' is missing the required 'north' property for 'fence_block' behavior." warning.config.block.behavior.fence.missing_east: "Issue found in file - The block '' is missing the required 'east' property for 'fence_block' behavior." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index d15fae40a..1c2c4ca57 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -313,7 +313,7 @@ warning.config.block.behavior.pressure_plate.missing_powered: "在文件 warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.double_high.missing_half: "在文件 发现问题 - 方块 '' 的 'double_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.change_over_time.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 参数" -warning.config.block.behavior.surface_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'surface_attached_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.directional_attached.missing_facing: "在文件 发现问题 - 方块 '' 的 'directional_attached_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.wall_torch_particle.missing_facing: "在文件 发现问题 - 配置项 '' 的 'wall_torch_particle_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.fence.missing_north: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'north' 属性" warning.config.block.behavior.fence.missing_east: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'east' 属性" From a32e50c4b8101a32e7947c2c61e5618fb2e84b37 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 03:19:11 +0800 Subject: [PATCH 153/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8C=BA=E5=9F=9F=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/config.yml | 39 ++++---- .../core/plugin/config/Config.java | 91 ++++++++++--------- 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index cb1226ba1..c69bf57ba 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -219,24 +219,7 @@ image: chat: true command: true sign: true - # Allow and tags in third-party plugins via packet manipulation - # ⚠️ Disable unused handlers to reduce async thread workload - intercept-packets: - system-chat: true - tab-list: true # Tab list header and footer - player-info: true # Player list in tab - set-score: true - actionbar: true - title: true - bossbar: true - container: true # GUI - team: true # Team prefix, suffix and display name - scoreboard: true - entity-name: false - armor-stand: true # Legacy Holograms - text-display: true # Modern Holograms - item: true - advancement: true + # Defines Unicode characters used for positioning # - Must match the font defined in resource packs # - Do NOT modify unless you understand text rendering mechanics @@ -287,6 +270,26 @@ image: 128: '\uf844' 256: '\uf845' +network: + # Allow tags in third-party plugins via packet manipulation + # ⚠️ Disable unused handlers to reduce async thread workload + intercept-packets: + system-chat: true + tab-list: true # Tab list header and footer + player-info: true # Player list in tab + set-score: true + actionbar: true + title: true + bossbar: true + container: true # GUI + team: true # Team prefix, suffix and display name + scoreboard: true + entity-name: false + armor-stand: true # Legacy Holograms + text-display: true # Modern Holograms + item: true + advancement: true + recipe: # Master switch for custom recipes # NOTE: When enabled, plugin recipes will OVERRIDE vanilla recipes diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index ffdc1cacf..834947120 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -134,21 +134,21 @@ public class Config { protected boolean image$illegal_characters_filter$anvil; protected boolean image$illegal_characters_filter$sign; protected boolean image$illegal_characters_filter$book; - protected boolean image$intercept_packets$system_chat; - protected boolean image$intercept_packets$tab_list; - protected boolean image$intercept_packets$actionbar; - protected boolean image$intercept_packets$title; - protected boolean image$intercept_packets$bossbar; - protected boolean image$intercept_packets$container; - protected boolean image$intercept_packets$team; - protected boolean image$intercept_packets$scoreboard; - protected boolean image$intercept_packets$entity_name; - protected boolean image$intercept_packets$text_display; - protected boolean image$intercept_packets$armor_stand; - protected boolean image$intercept_packets$player_info; - protected boolean image$intercept_packets$set_score; - protected boolean image$intercept_packets$item; - protected boolean image$intercept_packets$advancement; + protected boolean network$intercept_packets$system_chat; + protected boolean network$intercept_packets$tab_list; + protected boolean network$intercept_packets$actionbar; + protected boolean network$intercept_packets$title; + protected boolean network$intercept_packets$bossbar; + protected boolean network$intercept_packets$container; + protected boolean network$intercept_packets$team; + protected boolean network$intercept_packets$scoreboard; + protected boolean network$intercept_packets$entity_name; + protected boolean network$intercept_packets$text_display; + protected boolean network$intercept_packets$armor_stand; + protected boolean network$intercept_packets$player_info; + protected boolean network$intercept_packets$set_score; + protected boolean network$intercept_packets$item; + protected boolean network$intercept_packets$advancement; protected boolean item$client_bound_model; protected boolean item$non_italic_tag; @@ -405,21 +405,22 @@ public class Config { image$illegal_characters_filter$chat = config.getBoolean("image.illegal-characters-filter.chat", true); image$illegal_characters_filter$command = config.getBoolean("image.illegal-characters-filter.command", true); image$illegal_characters_filter$sign = config.getBoolean("image.illegal-characters-filter.sign", true); - image$intercept_packets$system_chat = config.getBoolean("image.intercept-packets.system-chat", true); - image$intercept_packets$tab_list = config.getBoolean("image.intercept-packets.tab-list", true); - image$intercept_packets$actionbar = config.getBoolean("image.intercept-packets.actionbar", true); - image$intercept_packets$title = config.getBoolean("image.intercept-packets.title", true); - image$intercept_packets$bossbar = config.getBoolean("image.intercept-packets.bossbar", true); - image$intercept_packets$container = config.getBoolean("image.intercept-packets.container", true); - image$intercept_packets$team = config.getBoolean("image.intercept-packets.team", true); - image$intercept_packets$scoreboard = config.getBoolean("image.intercept-packets.scoreboard", true); - image$intercept_packets$entity_name = config.getBoolean("image.intercept-packets.entity-name", false); - image$intercept_packets$text_display = config.getBoolean("image.intercept-packets.text-display", true); - image$intercept_packets$armor_stand = config.getBoolean("image.intercept-packets.armor-stand", true); - image$intercept_packets$player_info = config.getBoolean("image.intercept-packets.player-info", true); - image$intercept_packets$set_score = config.getBoolean("image.intercept-packets.set-score", true); - image$intercept_packets$item = config.getBoolean("image.intercept-packets.item", true); - image$intercept_packets$advancement = config.getBoolean("image.intercept-packets.advancement", true); + + network$intercept_packets$system_chat = config.getBoolean("network.intercept-packets.system-chat", true); + network$intercept_packets$tab_list = config.getBoolean("network.intercept-packets.tab-list", true); + network$intercept_packets$actionbar = config.getBoolean("network.intercept-packets.actionbar", true); + network$intercept_packets$title = config.getBoolean("network.intercept-packets.title", true); + network$intercept_packets$bossbar = config.getBoolean("network.intercept-packets.bossbar", true); + network$intercept_packets$container = config.getBoolean("network.intercept-packets.container", true); + network$intercept_packets$team = config.getBoolean("network.intercept-packets.team", true); + network$intercept_packets$scoreboard = config.getBoolean("network.intercept-packets.scoreboard", true); + network$intercept_packets$entity_name = config.getBoolean("network.intercept-packets.entity-name", false); + network$intercept_packets$text_display = config.getBoolean("network.intercept-packets.text-display", true); + network$intercept_packets$armor_stand = config.getBoolean("network.intercept-packets.armor-stand", true); + network$intercept_packets$player_info = config.getBoolean("network.intercept-packets.player-info", true); + network$intercept_packets$set_score = config.getBoolean("network.intercept-packets.set-score", true); + network$intercept_packets$item = config.getBoolean("network.intercept-packets.item", true); + network$intercept_packets$advancement = config.getBoolean("network.intercept-packets.advancement", true); // emoji emoji$contexts$chat = config.getBoolean("emoji.contexts.chat", true); @@ -729,63 +730,63 @@ public class Config { } public static boolean interceptSystemChat() { - return instance.image$intercept_packets$system_chat; + return instance.network$intercept_packets$system_chat; } public static boolean interceptTabList() { - return instance.image$intercept_packets$tab_list; + return instance.network$intercept_packets$tab_list; } public static boolean interceptActionBar() { - return instance.image$intercept_packets$actionbar; + return instance.network$intercept_packets$actionbar; } public static boolean interceptTitle() { - return instance.image$intercept_packets$title; + return instance.network$intercept_packets$title; } public static boolean interceptBossBar() { - return instance.image$intercept_packets$bossbar; + return instance.network$intercept_packets$bossbar; } public static boolean interceptContainer() { - return instance.image$intercept_packets$container; + return instance.network$intercept_packets$container; } public static boolean interceptTeam() { - return instance.image$intercept_packets$team; + return instance.network$intercept_packets$team; } public static boolean interceptEntityName() { - return instance.image$intercept_packets$entity_name; + return instance.network$intercept_packets$entity_name; } public static boolean interceptScoreboard() { - return instance.image$intercept_packets$scoreboard; + return instance.network$intercept_packets$scoreboard; } public static boolean interceptTextDisplay() { - return instance.image$intercept_packets$text_display; + return instance.network$intercept_packets$text_display; } public static boolean interceptArmorStand() { - return instance.image$intercept_packets$armor_stand; + return instance.network$intercept_packets$armor_stand; } public static boolean interceptPlayerInfo() { - return instance.image$intercept_packets$player_info; + return instance.network$intercept_packets$player_info; } public static boolean interceptSetScore() { - return instance.image$intercept_packets$set_score; + return instance.network$intercept_packets$set_score; } public static boolean interceptItem() { - return instance.image$intercept_packets$item; + return instance.network$intercept_packets$item; } public static boolean interceptAdvancement() { - return instance.image$intercept_packets$advancement; + return instance.network$intercept_packets$advancement; } public static boolean predictBreaking() { From 6ed54af7ab634675ef451bfee01a86e18fc6bcfe Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 09:16:26 +0800 Subject: [PATCH 154/226] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E6=96=B9=E5=9D=97=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 4 + .../block/behavior/ButtonBlockBehavior.java | 217 ++++++++++++++++++ .../DirectionalAttachedBlockBehavior.java | 20 +- ...hedHorizontalDirectionalBlockBehavior.java | 150 ++++++++++++ .../behavior/PressurePlateBlockBehavior.java | 7 +- .../item/recipe/BukkitRecipeManager.java | 2 +- .../plugin/network/BukkitNetworkManager.java | 9 +- .../reflection/minecraft/CoreReflections.java | 6 + .../minecraft/MBuiltInRegistries.java | 5 + .../reflection/minecraft/MEntitySelector.java | 12 + .../reflection/minecraft/MGameEvent.java | 15 ++ .../craftengine/core/block/BlockBehavior.java | 10 +- .../core/block/properties/Properties.java | 2 + .../block/state/properties/AttachFace.java | 7 + gradle.properties | 2 +- 15 files changed, 444 insertions(+), 24 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 4070b0f48..fc8ba1cc6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -37,6 +37,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SIMPLE_PARTICLE_BLOCK = Key.from("craftengine:simple_particle_block"); public static final Key WALL_TORCH_PARTICLE_BLOCK = Key.from("craftengine:wall_torch_particle_block"); public static final Key FENCE_BLOCK = Key.from("craftengine:fence_block"); + public static final Key BUTTON_BLOCK = Key.from("craftengine:button_block"); + public static final Key FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK = Key.from("craftengine:face_attached_horizontal_directional_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -72,5 +74,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SIMPLE_PARTICLE_BLOCK, SimpleParticleBlockBehavior.FACTORY); register(WALL_TORCH_PARTICLE_BLOCK, WallTorchParticleBlockBehavior.FACTORY); register(FENCE_BLOCK, FenceBlockBehavior.FACTORY); + register(BUTTON_BLOCK, ButtonBlockBehavior.FACTORY); + register(FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK, FaceAttachedHorizontalDirectionalBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java new file mode 100644 index 000000000..3be8fa890 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java @@ -0,0 +1,217 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelector; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MGameEvent; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.BooleanProperty; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class ButtonBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final BooleanProperty poweredProperty; + private final int ticksToStayPressed; + private final boolean canButtonBeActivatedByArrows; + private final SoundData buttonClickOnSound; + private final SoundData buttonClickOffSound; + + public ButtonBlockBehavior(CustomBlock customBlock, + BooleanProperty powered, + int ticksToStayPressed, + boolean canButtonBeActivatedByArrows, + SoundData buttonClickOnSound, + SoundData buttonClickOffSound) { + super(customBlock); + this.poweredProperty = powered; + this.ticksToStayPressed = ticksToStayPressed; + this.canButtonBeActivatedByArrows = canButtonBeActivatedByArrows; + this.buttonClickOnSound = buttonClickOnSound; + this.buttonClickOffSound = buttonClickOffSound; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + if (!state.get(this.poweredProperty)) { + press(BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()), + state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), + context.getPlayer() != null ? context.getPlayer().serverPlayer() : null); + return InteractionResult.SUCCESS_AND_CANCEL; + } + return InteractionResult.PASS; + } + + @Override + public void onExplosionHit(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return; + if (FastNMS.INSTANCE.method$Explosion$canTriggerBlocks(args[3]) && !blockState.get(this.poweredProperty)) { + press(thisBlock, blockState, args[1], args[2], null); + } + } + + @Override + public void affectNeighborsAfterRemoval(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return; + if (!(boolean) args[3] && blockState.get(this.poweredProperty)) { + updateNeighbours(thisBlock, blockState, args[1], args[2]); + } + } + + @Override + public void onRemove(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return; + if (!(boolean) args[4] && blockState.get(this.poweredProperty)) { + updateNeighbours(thisBlock, blockState, args[1], args[2]); + } + } + + @Override + public int getSignal(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return 0; + return blockState.get(this.poweredProperty) ? 15 : 0; + } + + @Override + public int getDirectSignal(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return 0; + return blockState.get(this.poweredProperty) + && FaceAttachedHorizontalDirectionalBlockBehavior.getConnectedDirection(blockState) + == DirectionUtils.fromNMSDirection(args[3]) ? 15 : 0; + } + + @Override + public boolean isSignalSource(Object thisBlock, Object[] args, Callable superMethod) { + return true; + } + + @Override + public void tick(Object thisBlock, Object[] args, Callable superMethod) { + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return; + if (blockState.get(this.poweredProperty)) { + checkPressed(thisBlock, state, level, pos); + } + } + + @Override + public void entityInside(Object thisBlock, Object[] args, Callable superMethod) { + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return; + if (this.canButtonBeActivatedByArrows && !blockState.get(this.poweredProperty)) { + checkPressed(thisBlock, state, level, pos); + } + } + + private void checkPressed(Object thisBlock, Object state, Object level, Object pos) { + Object abstractArrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( + level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move( + FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape( + state, level, pos, CoreReflections.instance$CollisionContext$empty + )), pos), MEntitySelector.NO_SPECTATORS).stream().findFirst().orElse(null) : null; + boolean flag = abstractArrow != null; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return; + boolean poweredValue = blockState.get(this.poweredProperty); + if (flag != poweredValue) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, flag).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + updateNeighbours(thisBlock, blockState, level, pos); + playSound(null, level, pos, flag); + Object gameEvent = VersionHelper.isOrAbove1_20_5() + ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE) + : flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE; + FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, abstractArrow, gameEvent, pos); + } + + if (flag) { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); + } + } + + private void updateNeighbours(Object thisBlock, ImmutableBlockState state, Object level, Object pos) { + Direction direction = FaceAttachedHorizontalDirectionalBlockBehavior.getConnectedDirection(state); + if (direction == null) return; + Direction opposite = direction.opposite(); + Object nmsDirection = DirectionUtils.toNMSDirection(opposite); + Object orientation = null; + if (VersionHelper.isOrAbove1_21_2()) { + @SuppressWarnings("unchecked") + Property facing = (Property) state.owner().value().getProperty("facing"); + if (facing != null) { + orientation = FastNMS.INSTANCE.method$ExperimentalRedstoneUtils$initialOrientation( + level, nmsDirection, opposite.axis().isHorizontal() ? CoreReflections.instance$Direction$UP : DirectionUtils.toNMSDirection(state.get(facing).toDirection()) + ); + } + } + FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, pos, thisBlock, orientation); + FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, FastNMS.INSTANCE.method$BlockPos$relative(pos, nmsDirection), thisBlock, orientation); + } + + private void playSound(@Nullable Object player, Object level, Object pos, boolean hitByArrow) { + SoundData soundData = getSound(hitByArrow); + if (soundData == null) return; + Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(soundData.id()), Optional.empty()); + FastNMS.INSTANCE.method$LevelAccessor$playSound(level, player, pos, sound, CoreReflections.instance$SoundSource$BLOCKS, soundData.volume().get(), soundData.pitch().get()); + } + + private SoundData getSound(boolean isOn) { + return isOn ? this.buttonClickOnSound : this.buttonClickOffSound; + } + + private void press(Object thisBlock, ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); + playSound(player, level, pos, true); + Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvent.BLOCK_ACTIVATE) : MGameEvent.BLOCK_ACTIVATE; + FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos); + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings({"unchecked", "DuplicatedCode"}) + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + BooleanProperty powered = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.button.missing_powered"); + int ticksToStayPressed = ResourceConfigUtils.getAsInt(arguments.getOrDefault("ticks-to-stay-pressed", 30), "ticks-to-stay-pressed"); + boolean canButtonBeActivatedByArrows = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-button-be-activated-by-arrows", true), "can-button-be-activated-by-arrows"); + Map sounds = (Map) arguments.get("sounds"); + SoundData buttonClickOnSound = null; + SoundData buttonClickOffSound = null; + if (sounds != null) { + buttonClickOnSound = Optional.ofNullable(sounds.get("on")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + buttonClickOffSound = Optional.ofNullable(sounds.get("off")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + } + return new ButtonBlockBehavior(block, powered, ticksToStayPressed, canButtonBeActivatedByArrows, buttonClickOnSound, buttonClickOffSound); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index a155235db..f2e4d9987 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -25,7 +25,7 @@ import org.bukkit.Registry; import java.util.*; import java.util.concurrent.Callable; -public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBehavior { +public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; private final boolean isSixDirection; @@ -37,12 +37,11 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh public DirectionalAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty, boolean isSixDirection, - int delay, boolean blacklist, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { - super(customBlock, delay); + super(customBlock); this.facingProperty = facingProperty; this.isSixDirection = isSixDirection; this.tagsCanSurviveOn = tagsCanSurviveOn; @@ -69,8 +68,8 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh } @Override - protected boolean canSurvive(Object thisBlock, Object blockState, Object world, Object pos) throws Exception { - ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(blockState).orElse(null); + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (state == null) return false; DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null); if (behavior == null) return false; @@ -80,10 +79,10 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh } else { direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection(); } - BlockPos blockPos = LocationUtils.fromBlockPos(pos).relative(direction); + BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction); Object nmsPos = LocationUtils.toBlockPos(blockPos); - Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, nmsPos); - return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, world, nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL) + Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos); + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL) && mayPlaceOn(nmsState); } @@ -144,14 +143,13 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh throw new LocalizedResourceConfigException("warning.config.block.behavior.directional_attached.missing_facing"); } Tuple, Set, Set> tuple = readTagsAndState(arguments); - int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist"); - return new DirectionalAttachedBlockBehavior(block, facing, isDirection, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right()); + return new DirectionalAttachedBlockBehavior(block, facing, isDirection, blacklistMode, tuple.left(), tuple.mid(), tuple.right()); } } @SuppressWarnings("DuplicatedCode") - private static Tuple, Set, Set> readTagsAndState(Map arguments) { + public static Tuple, Set, Set> readTagsAndState(Map arguments) { List mcTags = new ArrayList<>(); for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("attached-block-tags", List.of()))) { mcTags.add(BlockTags.getOrCreate(Key.of(tag))); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java new file mode 100644 index 000000000..cc877ef06 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java @@ -0,0 +1,150 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.block.state.properties.AttachFace; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.Tuple; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; + +public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property attachFaceProperty; + private final Property facingProperty; + private final List tagsCanSurviveOn; + private final Set blockStatesCanSurviveOn; + private final Set customBlocksCansSurviveOn; + private final boolean blacklistMode; + + public FaceAttachedHorizontalDirectionalBlockBehavior(CustomBlock customBlock, + boolean blacklist, + List tagsCanSurviveOn, + Set blockStatesCanSurviveOn, + Set customBlocksCansSurviveOn, + Property attachFace, + Property facing) { + super(customBlock); + this.tagsCanSurviveOn = tagsCanSurviveOn; + this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; + this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; + this.blacklistMode = blacklist; + this.attachFaceProperty = attachFace; + this.facingProperty = facing; + } + + @Override + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Direction direction = getConnectedDirection(BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null)); + if (direction == null) return false; + direction = direction.opposite(); + Object nmsDirection = DirectionUtils.toNMSDirection(direction); + Object targetPos = FastNMS.INSTANCE.method$BlockPos$relative(args[2], nmsDirection); + Object targetState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], targetPos); + return canAttach(args[1], targetPos, nmsDirection, targetState) && mayPlaceOn(targetState); + } + + @SuppressWarnings("unchecked") + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + Property face = (Property) state.owner().value().getProperty("face"); + Property facing = (Property) state.owner().value().getProperty("facing"); + if (face == null || facing == null) return null; + for (Direction direction : context.getNearestLookingDirections()) { + if (direction.axis() == Direction.Axis.Y) { + state = state + .with(face, direction == Direction.UP ? AttachFace.CEILING : AttachFace.FLOOR) + .with(facing, context.getHorizontalDirection().toHorizontalDirection()); + } else { + state = state.with(face, AttachFace.WALL).with(facing, direction.opposite().toHorizontalDirection()); + } + if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()))) { + return state; + } + } + return null; + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Direction direction = getConnectedDirection(BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null)); + if (direction == null) return MBlocks.AIR$defaultState; + if (DirectionUtils.toNMSDirection(direction.opposite()) == args[updateShape$direction] && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos])) { + return MBlocks.AIR$defaultState; + } + return superMethod.call(); + } + + private boolean mayPlaceOn(Object state) { + for (Object tag : this.tagsCanSurviveOn) { + if (FastNMS.INSTANCE.method$BlockStateBase$is(state, tag)) { + return !this.blacklistMode; + } + } + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); + if (optionalCustomState.isEmpty()) { + if (!this.blockStatesCanSurviveOn.isEmpty() && this.blockStatesCanSurviveOn.contains(state)) { + return !this.blacklistMode; + } + } else { + ImmutableBlockState belowCustomState = optionalCustomState.get(); + if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) { + return !this.blacklistMode; + } + if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) { + return !this.blacklistMode; + } + } + return this.blacklistMode; + } + + public static boolean canAttach(Object level, Object targetPos, Object direction, Object targetState) { + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy( + targetState, level, targetPos, + FastNMS.INSTANCE.method$Direction$getOpposite(direction), + CoreReflections.instance$SupportType$FULL + ); + } + + @Nullable + public static Direction getConnectedDirection(ImmutableBlockState state) { + if (state == null) return null; + FaceAttachedHorizontalDirectionalBlockBehavior behavior = state.behavior().getAs(FaceAttachedHorizontalDirectionalBlockBehavior.class).orElse(null); + if (behavior == null) return null; + return switch (state.get(behavior.attachFaceProperty)) { + case CEILING -> Direction.DOWN; + case FLOOR -> Direction.UP; + default -> state.get(behavior.facingProperty).toDirection(); + }; + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Property attachFace = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("face"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_face"); + Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_facing"); + Tuple, Set, Set> tuple = DirectionalAttachedBlockBehavior.readTagsAndState(arguments); + boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist"); + return new FaceAttachedHorizontalDirectionalBlockBehavior(block, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), attachFace, facing); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 09c8c920d..5ec5cc268 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -4,6 +4,7 @@ import io.papermc.paper.event.entity.EntityInsideBlockEvent; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelector; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; @@ -28,6 +29,7 @@ import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.function.Predicate; public class PressurePlateBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); @@ -111,7 +113,10 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { case MOBS -> CoreReflections.clazz$LivingEntity; }; Object box = FastNMS.INSTANCE.method$AABB$move(CoreReflections.instance$BasePressurePlateBlock$TOUCH_AABB, pos); - return FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass(level, box, clazz) > 0 ? 15 : 0; + return !FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( + level, clazz, box, + MEntitySelector.NO_SPECTATORS.and(entity -> !FastNMS.INSTANCE.method$Entity$isIgnoringBlockTriggers(entity)) + ).isEmpty() ? 15 : 0; } private Object setSignalForState(Object state, int strength) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index 06d11bf38..e385ffca4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -395,7 +395,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { JsonObject jsonObject = entry.getValue(); Key serializerType = Key.of(jsonObject.get("type").getAsString()); - // noinspection unchecked + @SuppressWarnings("unchecked") RecipeSerializer> serializer = (RecipeSerializer>) BuiltInRegistries.RECIPE_SERIALIZER.getValue(serializerType); if (serializer == null) { continue; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 2952fe1bc..4671b7ed5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -961,6 +961,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes */ public static class HelloListener implements NMSPacketListener { + @SuppressWarnings("unchecked") @Override public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { BukkitServerPlayer player = (BukkitServerPlayer) user; @@ -984,7 +985,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } else { Optional uuid; try { - // noinspection unchecked uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); } catch (Throwable t) { CraftEngine.instance().logger().severe("Failed to get uuid from ServerboundHelloPacket", t); @@ -1478,6 +1478,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes public static class EditBookListener implements NMSPacketListener { + @SuppressWarnings("unchecked") @Override public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { if (!Config.filterBook()) return; @@ -1492,7 +1493,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes List pages; try { - // noinspection unchecked pages = (List) NetworkReflections.methodHandle$ServerboundEditBookPacket$pagesGetter.invokeExact(packet); } catch (Throwable t) { CraftEngine.instance().logger().warn("Failed to get pages from ServerboundEditBookPacket", t); @@ -1501,7 +1501,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes List newPages = new ArrayList<>(pages.size()); Optional title; try { - // noinspection unchecked title = (Optional) NetworkReflections.methodHandle$ServerboundEditBookPacket$titleGetter.invokeExact(packet); } catch (Throwable t) { CraftEngine.instance().logger().warn("Failed to get title from ServerboundEditBookPacket", t); @@ -1733,6 +1732,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes public static class FinishConfigurationListener implements NMSPacketListener { + @SuppressWarnings("unchecked") @Override public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { if (!VersionHelper.isOrAbove1_20_2() || !Config.sendPackOnJoin()) { @@ -1793,7 +1793,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } Queue configurationTasks; try { - // noinspection unchecked configurationTasks = (Queue) CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$configurationTasksGetter.invokeExact(packetListener); } catch (Throwable e) { CraftEngine.instance().logger().warn("Failed to get configuration tasks for player " + user.name(), e); @@ -3665,7 +3664,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object packedItem = packedItems.get(i); int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); if (entityDataId != BaseEntityData.CustomName.id()) continue; - // noinspection unchecked + @SuppressWarnings("unchecked") Optional optionalTextComponent = (Optional) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 859f826ed..4fb178606 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4400,4 +4400,10 @@ public final class CoreReflections { "world.level.block.FenceGateBlock" ) ); + + public static final Class clazz$GameEvent = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.level.gameevent.GameEvent") + ) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java index ef6d7a8b1..565ae69c3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java @@ -22,6 +22,7 @@ public final class MBuiltInRegistries { public static final Object PARTICLE_TYPE; public static final Object DATA_COMPONENT_TYPE; public static final Object LOOT_POOL_ENTRY_TYPE; + public static final Object GAME_EVENT; static { Field[] fields = CoreReflections.clazz$BuiltInRegistries.getDeclaredFields(); @@ -37,6 +38,7 @@ public final class MBuiltInRegistries { Object registries$RecipeType = null; Object registries$DataComponentType = null; Object registries$LootPoolEntryType = null; + Object registries$GameEvent = null; for (Field field : fields) { Type fieldType = field.getGenericType(); if (fieldType instanceof ParameterizedType paramType) { @@ -67,6 +69,8 @@ public final class MBuiltInRegistries { registries$Fluid = field.get(null); } else if (type == CoreReflections.clazz$LootPoolEntryType) { registries$LootPoolEntryType = field.get(null); + } else if (type == CoreReflections.clazz$GameEvent) { + registries$GameEvent = field.get(null); } } } @@ -82,6 +86,7 @@ public final class MBuiltInRegistries { RECIPE_TYPE = requireNonNull(registries$RecipeType); LOOT_POOL_ENTRY_TYPE = requireNonNull(registries$LootPoolEntryType); DATA_COMPONENT_TYPE = registries$DataComponentType; + GAME_EVENT = requireNonNull(registries$GameEvent); } catch (ReflectiveOperationException e) { throw new ReflectionInitException("Failed to init BuiltInRegistries", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java new file mode 100644 index 000000000..7a2b43ab7 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java @@ -0,0 +1,12 @@ +package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; + +import java.util.function.Predicate; + +public final class MEntitySelector { + private MEntitySelector() {} + + public static final Predicate NO_SPECTATORS = entity -> !FastNMS.INSTANCE.method$Entity$isSpectator(entity); + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java new file mode 100644 index 000000000..500f39d8c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java @@ -0,0 +1,15 @@ +package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; + +public final class MGameEvent { + private MGameEvent() {} + + public static final Object BLOCK_ACTIVATE = getById("block_activate"); + public static final Object BLOCK_DEACTIVATE = getById("block_deactivate"); + + private static Object getById(String id) { + Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id); + return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.GAME_EVENT, rl); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index f1aba39ce..e969cab8c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -55,18 +55,18 @@ public abstract class BlockBehavior { superMethod.call(); } - // ServerLevel level, BlockPos pos, RandomSource random + // BlockState state, ServerLevel level, BlockPos pos, RandomSource random public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { superMethod.call(); } - // ServerLevel level, BlockPos pos, RandomSource random + // BlockState state, ServerLevel level, BlockPos pos, RandomSource random public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { superMethod.call(); } // 1.20-1.20.4 BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, UseOnContext context - // 1.20.5+ Level level, BlockPos pos, BlockState oldState, boolean movedByPiston + // 1.20.5+ BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston public void onPlace(Object thisBlock, Object[] args, Callable superMethod) throws Exception { superMethod.call(); } @@ -95,12 +95,12 @@ public abstract class BlockBehavior { return false; } - //BlockState state + // BlockState state public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { return false; } - //BlockState state, Level level, BlockPos pos + // BlockState state, Level level, BlockPos pos public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { return 0; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java index e5a143264..e559a080a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java @@ -22,6 +22,7 @@ public final class Properties { public static final Key STAIRS_SHAPE = Key.of("craftengine:stairs_shape"); public static final Key SLAB_TYPE = Key.of("craftengine:slab_type"); public static final Key SOFA_SHAPE = Key.of("craftengine:sofa_shape"); + public static final Key ATTACH_FACE = Key.of("craftengine:attach_face"); static { register(BOOLEAN, BooleanProperty.FACTORY); @@ -38,6 +39,7 @@ public final class Properties { register(STAIRS_SHAPE, new EnumProperty.Factory<>(StairsShape.class)); register(SLAB_TYPE, new EnumProperty.Factory<>(SlabType.class)); register(SOFA_SHAPE, new EnumProperty.Factory<>(SofaShape.class)); + register(ATTACH_FACE, new EnumProperty.Factory<>(AttachFace.class)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java new file mode 100644 index 000000000..de9d6267b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.block.state.properties; + +public enum AttachFace { + FLOOR, + WALL, + CEILING +} diff --git a/gradle.properties b/gradle.properties index e6e8440a9..e0c51c8a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.91 +nms_helper_version=1.0.92 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 1d500a580de7b39a5bbcf2945d813379e0b1aa88 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 09:59:40 +0800 Subject: [PATCH 155/226] =?UTF-8?q?fix(block):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84tick=E6=96=B9=E5=9D=97=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 这个逆天问题,终于被我解决了真的是折磨死我了 - 解释下是如何解决的,因为ce是一个方块一个状态,所以说直接拿获取到的thisblock去tick方块会导致前面如果你做了一个setblock来更新ce内部的方块状态就会导致thisblock和实际tick的这个方块对不上然后导致tick失效,需要在setblock拿一下设置的方块状态然后重新获取正确的block才能被之前tick,这次更新顺手把压力板修了,之前用的是不正确的解决方案,然后修复下保险箱的切换 - 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊漏电了(不是 --- .../block/behavior/ButtonBlockBehavior.java | 26 +++++++++------- .../behavior/PressurePlateBlockBehavior.java | 20 ++++++------ .../entity/SimpleStorageBlockEntity.java | 31 +++++++++++-------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java index 3be8fa890..7bca026c1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java @@ -53,8 +53,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { @Override public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { if (!state.get(this.poweredProperty)) { - press(BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()), - state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), + press(state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), context.getPlayer() != null ? context.getPlayer().serverPlayer() : null); return InteractionResult.SUCCESS_AND_CANCEL; } @@ -66,7 +65,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (blockState == null) return; if (FastNMS.INSTANCE.method$Explosion$canTriggerBlocks(args[3]) && !blockState.get(this.poweredProperty)) { - press(thisBlock, blockState, args[1], args[2], null); + press(blockState, args[1], args[2], null); } } @@ -117,7 +116,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); if (blockState == null) return; if (blockState.get(this.poweredProperty)) { - checkPressed(thisBlock, state, level, pos); + checkPressed(state, level, pos); } } @@ -129,11 +128,12 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); if (blockState == null) return; if (this.canButtonBeActivatedByArrows && !blockState.get(this.poweredProperty)) { - checkPressed(thisBlock, state, level, pos); + checkPressed(state, level, pos); } } - private void checkPressed(Object thisBlock, Object state, Object level, Object pos) { + private void checkPressed(Object state, Object level, Object pos) { + Object tickState = state; Object abstractArrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move( FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape( @@ -144,8 +144,9 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { if (blockState == null) return; boolean poweredValue = blockState.get(this.poweredProperty); if (flag != poweredValue) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, flag).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); - updateNeighbours(thisBlock, blockState, level, pos); + tickState = blockState.with(this.poweredProperty, flag).customBlockState().literalObject(); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, tickState, UpdateOption.UPDATE_ALL.flags()); + updateNeighbours(BlockStateUtils.getBlockOwner(tickState), blockState, level, pos); playSound(null, level, pos, flag); Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE) @@ -154,7 +155,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { } if (flag) { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), this.ticksToStayPressed); } } @@ -188,9 +189,10 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { return isOn ? this.buttonClickOnSound : this.buttonClickOffSound; } - private void press(Object thisBlock, ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); + private void press(ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { + Object tickState = state.with(this.poweredProperty, true).customBlockState().literalObject(); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, tickState, UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), this.ticksToStayPressed); playSound(player, level, pos, true); Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvent.BLOCK_ACTIVATE) : MGameEvent.BLOCK_ACTIVATE; FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 5ec5cc268..a3e85d6ae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -29,7 +29,6 @@ import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -import java.util.function.Predicate; public class PressurePlateBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); @@ -87,7 +86,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { Object state = args[0]; int signalForState = this.getSignalForState(state); if (signalForState > 0) { - this.checkPressed(null, args[1], args[2], state, signalForState, thisBlock); + this.checkPressed(null, args[1], args[2], state, signalForState); } } @@ -101,9 +100,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { Object state = args[0]; int signalForState = this.getSignalForState(state); if (signalForState == 0) { - this.checkPressed(args[3], args[1], args[2], state, signalForState, thisBlock); - } else { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(args[1], args[2], thisBlock, this.pressedTime); + this.checkPressed(args[3], args[1], args[2], state, signalForState); } } @@ -125,16 +122,17 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { return optionalCustomState.get().with(this.poweredProperty, strength > 0).customBlockState().literalObject(); } - private void checkPressed(@Nullable Object entity, Object level, Object pos, Object state, int currentSignal, Object thisBlock) { + private void checkPressed(@Nullable Object entity, Object level, Object pos, Object state, int currentSignal) { + Object tickState = state; int signalStrength = this.getSignalStrength(level, pos); boolean wasActive = currentSignal > 0; boolean isActive = signalStrength > 0; if (currentSignal != signalStrength) { - Object blockState = this.setSignalForState(state, signalStrength); - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState, 2); - this.updateNeighbours(level, pos, thisBlock); - FastNMS.INSTANCE.method$Level$setBlocksDirty(level, pos, state, blockState); + tickState = this.setSignalForState(state, signalStrength); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, tickState, 2); + this.updateNeighbours(level, pos, BlockStateUtils.getBlockOwner(tickState)); + FastNMS.INSTANCE.method$Level$setBlocksDirty(level, pos, state, tickState); } org.bukkit.World craftWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(level); @@ -150,7 +148,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { } if (isActive) { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.pressedTime); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), this.pressedTime); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index fcb654c71..f889df503 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -107,8 +107,8 @@ public class SimpleStorageBlockEntity extends BlockEntity { // 有非观察者的人,那么就不触发开启音效和事件 if (!hasNoViewer(this.inventory.getViewers())) return; this.maxInteractionDistance = Math.max(player.getCachedInteractionRange(), this.maxInteractionDistance); - this.setOpen(player); - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(super.world.world().serverWorld(), LocationUtils.toBlockPos(this.pos), BlockStateUtils.getBlockOwner(this.blockState.customBlockState().literalObject()), 5); + ImmutableBlockState state = this.setOpen(player); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(super.world.world().serverWorld(), LocationUtils.toBlockPos(this.pos), BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()), 5); } } @@ -127,8 +127,8 @@ public class SimpleStorageBlockEntity extends BlockEntity { } } - private void setOpen(@Nullable Player player) { - this.updateOpenBlockState(true); + private ImmutableBlockState setOpen(@Nullable Player player) { + ImmutableBlockState state = this.updateOpenBlockState(true); org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld(); if (player != null) { bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_OPEN, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); @@ -140,10 +140,11 @@ public class SimpleStorageBlockEntity extends BlockEntity { if (soundData != null) { super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData); } + return state; } - private void setClose(@Nullable Player player) { - this.updateOpenBlockState(false); + private ImmutableBlockState setClose(@Nullable Player player) { + ImmutableBlockState state = this.updateOpenBlockState(false); org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld(); if (player != null) { bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_CLOSE, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); @@ -155,6 +156,7 @@ public class SimpleStorageBlockEntity extends BlockEntity { if (soundData != null) { super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData); } + return state; } private boolean hasNoViewer(List viewers) { @@ -170,16 +172,19 @@ public class SimpleStorageBlockEntity extends BlockEntity { return this.isValid() && this.inventory != null && this.behavior != null; } - public void updateOpenBlockState(boolean open) { + public ImmutableBlockState updateOpenBlockState(boolean open) { ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos); - if (state == null || state.behavior() != this.behavior) return; + if (state == null || state.behavior() != this.behavior) return this.blockState; Property property = this.behavior.openProperty(); - if (property == null) return; - super.world.world().setBlockAt(this.pos.x(), this.pos.y(), this.pos.z(), state.with(property, open), UpdateOption.UPDATE_ALL.flags()); + if (property == null) return state; + state = state.with(property, open); + super.world.world().setBlockAt(this.pos.x(), this.pos.y(), this.pos.z(), state, UpdateOption.UPDATE_ALL.flags()); + return state; } public void checkOpeners(Object level, Object pos, Object blockState) { if (!this.isValidContainer()) return; + Object tickState = blockState; double maxInteractionDistance = 0d; List viewers = this.inventory.getViewers(); int validViewers = 0; @@ -193,14 +198,14 @@ public class SimpleStorageBlockEntity extends BlockEntity { } boolean shouldOpen = validViewers != 0; if (shouldOpen && !this.openState) { - this.setOpen(null); + tickState = this.setOpen(null).customBlockState().literalObject(); } else if (!shouldOpen && this.openState) { - this.setClose(null); + tickState = this.setClose(null).customBlockState().literalObject(); } this.maxInteractionDistance = maxInteractionDistance; if (!viewers.isEmpty()) { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(blockState), 5); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), 5); } } From 09c60273be08b758b632f886c257f204f18c7551 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 10:42:57 +0800 Subject: [PATCH 156/226] =?UTF-8?q?Revert=20"fix(block):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=94=99=E8=AF=AF=E7=9A=84tick=E6=96=B9=E5=9D=97?= =?UTF-8?q?=E6=96=B9=E5=BC=8F"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1d500a580de7b39a5bbcf2945d813379e0b1aa88. --- .../block/behavior/ButtonBlockBehavior.java | 26 +++++++--------- .../behavior/PressurePlateBlockBehavior.java | 20 ++++++------ .../entity/SimpleStorageBlockEntity.java | 31 ++++++++----------- 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java index 7bca026c1..3be8fa890 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java @@ -53,7 +53,8 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { @Override public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { if (!state.get(this.poweredProperty)) { - press(state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), + press(BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()), + state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), context.getPlayer() != null ? context.getPlayer().serverPlayer() : null); return InteractionResult.SUCCESS_AND_CANCEL; } @@ -65,7 +66,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (blockState == null) return; if (FastNMS.INSTANCE.method$Explosion$canTriggerBlocks(args[3]) && !blockState.get(this.poweredProperty)) { - press(blockState, args[1], args[2], null); + press(thisBlock, blockState, args[1], args[2], null); } } @@ -116,7 +117,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); if (blockState == null) return; if (blockState.get(this.poweredProperty)) { - checkPressed(state, level, pos); + checkPressed(thisBlock, state, level, pos); } } @@ -128,12 +129,11 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); if (blockState == null) return; if (this.canButtonBeActivatedByArrows && !blockState.get(this.poweredProperty)) { - checkPressed(state, level, pos); + checkPressed(thisBlock, state, level, pos); } } - private void checkPressed(Object state, Object level, Object pos) { - Object tickState = state; + private void checkPressed(Object thisBlock, Object state, Object level, Object pos) { Object abstractArrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move( FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape( @@ -144,9 +144,8 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { if (blockState == null) return; boolean poweredValue = blockState.get(this.poweredProperty); if (flag != poweredValue) { - tickState = blockState.with(this.poweredProperty, flag).customBlockState().literalObject(); - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, tickState, UpdateOption.UPDATE_ALL.flags()); - updateNeighbours(BlockStateUtils.getBlockOwner(tickState), blockState, level, pos); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, flag).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + updateNeighbours(thisBlock, blockState, level, pos); playSound(null, level, pos, flag); Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE) @@ -155,7 +154,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { } if (flag) { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), this.ticksToStayPressed); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); } } @@ -189,10 +188,9 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { return isOn ? this.buttonClickOnSound : this.buttonClickOffSound; } - private void press(ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { - Object tickState = state.with(this.poweredProperty, true).customBlockState().literalObject(); - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, tickState, UpdateOption.UPDATE_ALL.flags()); - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), this.ticksToStayPressed); + private void press(Object thisBlock, ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); playSound(player, level, pos, true); Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvent.BLOCK_ACTIVATE) : MGameEvent.BLOCK_ACTIVATE; FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index a3e85d6ae..5ec5cc268 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -29,6 +29,7 @@ import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.function.Predicate; public class PressurePlateBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); @@ -86,7 +87,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { Object state = args[0]; int signalForState = this.getSignalForState(state); if (signalForState > 0) { - this.checkPressed(null, args[1], args[2], state, signalForState); + this.checkPressed(null, args[1], args[2], state, signalForState, thisBlock); } } @@ -100,7 +101,9 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { Object state = args[0]; int signalForState = this.getSignalForState(state); if (signalForState == 0) { - this.checkPressed(args[3], args[1], args[2], state, signalForState); + this.checkPressed(args[3], args[1], args[2], state, signalForState, thisBlock); + } else { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(args[1], args[2], thisBlock, this.pressedTime); } } @@ -122,17 +125,16 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { return optionalCustomState.get().with(this.poweredProperty, strength > 0).customBlockState().literalObject(); } - private void checkPressed(@Nullable Object entity, Object level, Object pos, Object state, int currentSignal) { - Object tickState = state; + private void checkPressed(@Nullable Object entity, Object level, Object pos, Object state, int currentSignal, Object thisBlock) { int signalStrength = this.getSignalStrength(level, pos); boolean wasActive = currentSignal > 0; boolean isActive = signalStrength > 0; if (currentSignal != signalStrength) { - tickState = this.setSignalForState(state, signalStrength); - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, tickState, 2); - this.updateNeighbours(level, pos, BlockStateUtils.getBlockOwner(tickState)); - FastNMS.INSTANCE.method$Level$setBlocksDirty(level, pos, state, tickState); + Object blockState = this.setSignalForState(state, signalStrength); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState, 2); + this.updateNeighbours(level, pos, thisBlock); + FastNMS.INSTANCE.method$Level$setBlocksDirty(level, pos, state, blockState); } org.bukkit.World craftWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(level); @@ -148,7 +150,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { } if (isActive) { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), this.pressedTime); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.pressedTime); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index f889df503..fcb654c71 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -107,8 +107,8 @@ public class SimpleStorageBlockEntity extends BlockEntity { // 有非观察者的人,那么就不触发开启音效和事件 if (!hasNoViewer(this.inventory.getViewers())) return; this.maxInteractionDistance = Math.max(player.getCachedInteractionRange(), this.maxInteractionDistance); - ImmutableBlockState state = this.setOpen(player); - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(super.world.world().serverWorld(), LocationUtils.toBlockPos(this.pos), BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()), 5); + this.setOpen(player); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(super.world.world().serverWorld(), LocationUtils.toBlockPos(this.pos), BlockStateUtils.getBlockOwner(this.blockState.customBlockState().literalObject()), 5); } } @@ -127,8 +127,8 @@ public class SimpleStorageBlockEntity extends BlockEntity { } } - private ImmutableBlockState setOpen(@Nullable Player player) { - ImmutableBlockState state = this.updateOpenBlockState(true); + private void setOpen(@Nullable Player player) { + this.updateOpenBlockState(true); org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld(); if (player != null) { bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_OPEN, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); @@ -140,11 +140,10 @@ public class SimpleStorageBlockEntity extends BlockEntity { if (soundData != null) { super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData); } - return state; } - private ImmutableBlockState setClose(@Nullable Player player) { - ImmutableBlockState state = this.updateOpenBlockState(false); + private void setClose(@Nullable Player player) { + this.updateOpenBlockState(false); org.bukkit.World bukkitWorld = (org.bukkit.World) super.world.world().platformWorld(); if (player != null) { bukkitWorld.sendGameEvent((org.bukkit.entity.Player) player.platformPlayer(), GameEvent.CONTAINER_CLOSE, new Vector(this.pos.x(), this.pos.y(), this.pos.z())); @@ -156,7 +155,6 @@ public class SimpleStorageBlockEntity extends BlockEntity { if (soundData != null) { super.world.world().playBlockSound(Vec3d.atCenterOf(this.pos), soundData); } - return state; } private boolean hasNoViewer(List viewers) { @@ -172,19 +170,16 @@ public class SimpleStorageBlockEntity extends BlockEntity { return this.isValid() && this.inventory != null && this.behavior != null; } - public ImmutableBlockState updateOpenBlockState(boolean open) { + public void updateOpenBlockState(boolean open) { ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos); - if (state == null || state.behavior() != this.behavior) return this.blockState; + if (state == null || state.behavior() != this.behavior) return; Property property = this.behavior.openProperty(); - if (property == null) return state; - state = state.with(property, open); - super.world.world().setBlockAt(this.pos.x(), this.pos.y(), this.pos.z(), state, UpdateOption.UPDATE_ALL.flags()); - return state; + if (property == null) return; + super.world.world().setBlockAt(this.pos.x(), this.pos.y(), this.pos.z(), state.with(property, open), UpdateOption.UPDATE_ALL.flags()); } public void checkOpeners(Object level, Object pos, Object blockState) { if (!this.isValidContainer()) return; - Object tickState = blockState; double maxInteractionDistance = 0d; List viewers = this.inventory.getViewers(); int validViewers = 0; @@ -198,14 +193,14 @@ public class SimpleStorageBlockEntity extends BlockEntity { } boolean shouldOpen = validViewers != 0; if (shouldOpen && !this.openState) { - tickState = this.setOpen(null).customBlockState().literalObject(); + this.setOpen(null); } else if (!shouldOpen && this.openState) { - tickState = this.setClose(null).customBlockState().literalObject(); + this.setClose(null); } this.maxInteractionDistance = maxInteractionDistance; if (!viewers.isEmpty()) { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(tickState), 5); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, BlockStateUtils.getBlockOwner(blockState), 5); } } From 1ac958cf659124482837c2a6de225334720fd926 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 10:48:08 +0800 Subject: [PATCH 157/226] =?UTF-8?q?fix(block):=20=E6=9B=B4=E5=A5=BD?= =?UTF-8?q?=E7=9A=84=E4=BF=AE=E5=A4=8D=E9=94=99=E8=AF=AF=E7=9A=84tick?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 之前的方法太麻烦了可能会导致一些第三方开发者不知道这个问题,通过修改匹配方式解决此问题 --- .../plugin/injector/BlockStateGenerator.java | 29 ++++++++++++++++--- .../reflection/minecraft/CoreReflections.java | 4 +++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java index 825f90a50..4dee54943 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java @@ -12,6 +12,7 @@ import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; import net.bytebuddy.implementation.bind.annotation.This; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; @@ -21,14 +22,15 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlockStateProperties; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MLootContextParams; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.block.BlockSettings; -import net.momirealms.craftengine.core.block.DelegatingBlockState; -import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.ObjectHolder; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.World; @@ -39,6 +41,8 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; public final class BlockStateGenerator { private static MethodHandle constructor$CraftEngineBlockState; @@ -64,7 +68,9 @@ public final class BlockStateGenerator { .method(ElementMatchers.is(CoreReflections.method$StateHolder$getValue)) .intercept(MethodDelegation.to(GetPropertyValueInterceptor.INSTANCE)) .method(ElementMatchers.is(CoreReflections.method$StateHolder$setValue)) - .intercept(MethodDelegation.to(SetPropertyValueInterceptor.INSTANCE)); + .intercept(MethodDelegation.to(SetPropertyValueInterceptor.INSTANCE)) + .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isBlock)) + .intercept(MethodDelegation.to(IsBlockInterceptor.INSTANCE)); Class clazz$CraftEngineBlock = stateBuilder.make().load(BlockStateGenerator.class.getClassLoader()).getLoaded(); constructor$CraftEngineBlockState = VersionHelper.isOrAbove1_20_5() ? MethodHandles.publicLookup().in(clazz$CraftEngineBlock) @@ -183,6 +189,21 @@ public final class BlockStateGenerator { } } + public static class IsBlockInterceptor { + public static final IsBlockInterceptor INSTANCE = new IsBlockInterceptor(); + + @RuntimeType + public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { + DelegatingBlockState customState = (DelegatingBlockState) thisObj; + ImmutableBlockState thisState = customState.blockState(); + if (thisState == null) return false; + if (!(args[0] instanceof DelegatingBlock delegatingBlock)) return false; + BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); + if (behavior == null) return false; + return behavior.equals(thisState.owner().value()); + } + } + public static class CreateStateInterceptor { public static final CreateStateInterceptor INSTANCE = new CreateStateInterceptor(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 4fb178606..e3eded271 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4406,4 +4406,8 @@ public final class CoreReflections { BukkitReflectionUtils.assembleMCClass("world.level.gameevent.GameEvent") ) ); + + public static final Method method$BlockStateBase$isBlock = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Block) + ); } From ecab22cc9b0a85d85b28a14a545a8b98d411fa24 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 11:24:08 +0800 Subject: [PATCH 158/226] =?UTF-8?q?feat(block):=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/PressurePlateBlockBehavior.java | 2 - .../plugin/injector/BlockStateGenerator.java | 74 ++++++++++++++++++- .../reflection/minecraft/CoreReflections.java | 22 ++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 5ec5cc268..0453403b5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -102,8 +102,6 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { int signalForState = this.getSignalForState(state); if (signalForState == 0) { this.checkPressed(args[3], args[1], args[2], state, signalForState, thisBlock); - } else { - FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(args[1], args[2], thisBlock, this.pressedTime); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java index 4dee54943..9281595d8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java @@ -20,6 +20,7 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlockStateProperties; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MLootContextParams; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; @@ -70,7 +71,19 @@ public final class BlockStateGenerator { .method(ElementMatchers.is(CoreReflections.method$StateHolder$setValue)) .intercept(MethodDelegation.to(SetPropertyValueInterceptor.INSTANCE)) .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isBlock)) - .intercept(MethodDelegation.to(IsBlockInterceptor.INSTANCE)); + .intercept(MethodDelegation.to(IsBlockInterceptor.INSTANCE)) + .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isHolderSetBlock)) + .intercept(MethodDelegation.to(IsHolderSetBlockInterceptor.INSTANCE)); + if (CoreReflections.method$BlockStateBase$isHolderBlock != null) { + stateBuilder = stateBuilder + .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isHolderBlock)) + .intercept(MethodDelegation.to(IsHolderBlockInterceptor.INSTANCE)); + } + if (CoreReflections.method$BlockStateBase$isResourceKeyBlock != null) { + stateBuilder = stateBuilder + .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isResourceKeyBlock)) + .intercept(MethodDelegation.to(IsResourceKeyBlockInterceptor.INSTANCE)); + } Class clazz$CraftEngineBlock = stateBuilder.make().load(BlockStateGenerator.class.getClassLoader()).getLoaded(); constructor$CraftEngineBlockState = VersionHelper.isOrAbove1_20_5() ? MethodHandles.publicLookup().in(clazz$CraftEngineBlock) @@ -200,7 +213,64 @@ public final class BlockStateGenerator { if (!(args[0] instanceof DelegatingBlock delegatingBlock)) return false; BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); if (behavior == null) return false; - return behavior.equals(thisState.owner().value()); + return behavior.block().equals(thisState.owner().value()); + } + } + + public static class IsHolderSetBlockInterceptor { + public static final IsHolderSetBlockInterceptor INSTANCE = new IsHolderSetBlockInterceptor(); + + @SuppressWarnings("unchecked") + @RuntimeType + public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { + DelegatingBlockState customState = (DelegatingBlockState) thisObj; + ImmutableBlockState thisState = customState.blockState(); + if (thisState == null) return false; + CustomBlock thisBlock = thisState.owner().value(); + for (Object holder : (Iterable) args[0]) { + Object block = FastNMS.INSTANCE.method$Holder$value(holder); + if (!(block instanceof DelegatingBlock delegatingBlock)) continue; + BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); + if (behavior == null) continue; + if (behavior.block().equals(thisBlock)) return true; + } + return false; + } + } + + public static class IsHolderBlockInterceptor { + public static final IsHolderBlockInterceptor INSTANCE = new IsHolderBlockInterceptor(); + + @RuntimeType + public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { + DelegatingBlockState customState = (DelegatingBlockState) thisObj; + ImmutableBlockState thisState = customState.blockState(); + if (thisState == null) return false; + CustomBlock thisBlock = thisState.owner().value(); + Object block = FastNMS.INSTANCE.method$Holder$value(args[0]); + if (!(block instanceof DelegatingBlock delegatingBlock)) return false; + BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); + if (behavior == null) return false; + return behavior.block().equals(thisBlock); + } + } + + public static class IsResourceKeyBlockInterceptor { + public static final IsResourceKeyBlockInterceptor INSTANCE = new IsResourceKeyBlockInterceptor(); + + @RuntimeType + public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { + DelegatingBlockState customState = (DelegatingBlockState) thisObj; + ImmutableBlockState thisState = customState.blockState(); + if (thisState == null) return false; + CustomBlock thisBlock = thisState.owner().value(); + Object block = FastNMS.INSTANCE.method$HolderGetter$getResourceKey(MBuiltInRegistries.BLOCK, args[0]) + .map(FastNMS.INSTANCE::method$Holder$value) + .orElse(null); + if (!(block instanceof DelegatingBlock delegatingBlock)) return false; + BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); + if (behavior == null) return false; + return behavior.block().equals(thisBlock); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index e3eded271..571f99f4a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4407,7 +4407,29 @@ public final class CoreReflections { ) ); + public static final Class clazz$HolderSet = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("core.HolderSet") + ) + ); + public static final Method method$BlockStateBase$isBlock = requireNonNull( ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Block) ); + + public static final Method method$BlockStateBase$isHolderSetBlock = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$HolderSet) + ); + + // 1.20.2+ + public static final Method method$BlockStateBase$isHolderBlock = MiscUtils.requireNonNullIf( + ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Holder), + VersionHelper.isOrAbove1_20_2() + ); + + // 1.20.3+ + public static final Method method$BlockStateBase$isResourceKeyBlock = MiscUtils.requireNonNullIf( + ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$ResourceKey), + VersionHelper.isOrAbove1_20_3() + ); } From 3e15a6bbc2eb360658abc78a1cbc3ba1aa3187d8 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 15:26:33 +0800 Subject: [PATCH 159/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dimage=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/config.yml | 2 ++ .../core/pack/cache/AutoIdCache.java | 27 +++++++++++++++++++ .../core/plugin/config/Config.java | 7 +++++ .../context/function/ActionBarFunction.java | 14 +++++----- .../context/function/MessageFunction.java | 16 +++++------ .../context/function/OpenWindowFunction.java | 12 ++++----- .../context/function/TitleFunction.java | 19 ++++++------- gradle.properties | 2 +- 8 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index c69bf57ba..22cbd0dad 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -163,6 +163,8 @@ equipment: humanoid-leggings: minecraft:trims/entity/humanoid_leggings/chainmail block: + # This decides the amount of real blocks on serverside. Requires a restart to apply. + serverside-blocks: 2000 # Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience. sound-system: enable: true diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java new file mode 100644 index 000000000..1588a2d1b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.core.pack.cache; + +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; + +public class AutoIdCache { + private final Map forcedIds = new HashMap<>(); + private final Map cachedIds = new HashMap<>(); + private final BitSet occupiedIds = new BitSet(); + private int currentAutoId; + + public AutoIdCache(int startIndex) { + this.currentAutoId = startIndex; + } + + public boolean setForcedId(final String name, int index) { + if (this.occupiedIds.get(index)) { + return false; + } + this.occupiedIds.set(index); + this.forcedIds.put(name, index); + return true; + } + + +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 834947120..65681858e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -123,6 +123,7 @@ public class Config { protected int block$predict_breaking_interval; protected double block$extended_interaction_range; protected boolean block$chunk_relighter; + protected int block$serverside_blocks; protected boolean recipe$enable; protected boolean recipe$disable_vanilla_recipes$all; @@ -134,6 +135,7 @@ public class Config { protected boolean image$illegal_characters_filter$anvil; protected boolean image$illegal_characters_filter$sign; protected boolean image$illegal_characters_filter$book; + protected boolean network$intercept_packets$system_chat; protected boolean network$intercept_packets$tab_list; protected boolean network$intercept_packets$actionbar; @@ -392,6 +394,7 @@ public class Config { block$predict_breaking_interval = Math.max(config.getInt("block.predict-breaking.interval", 10), 1); block$extended_interaction_range = Math.max(config.getDouble("block.predict-breaking.extended-interaction-range", 0.5), 0.0); block$chunk_relighter = config.getBoolean("block.chunk-relighter", true); + block$serverside_blocks = config.getInt("block.serverside-blocks", 2000); // recipe recipe$enable = config.getBoolean("recipe.enable", true); @@ -478,6 +481,10 @@ public class Config { return instance.metrics; } + public static int serverSideBlocks() { + return instance.block$serverside_blocks; + } + public static boolean filterConfigurationPhaseDisconnect() { return instance.filterConfigurationPhaseDisconnect; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java index 86aeebfa5..bcd5041cb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java @@ -5,8 +5,6 @@ import net.momirealms.craftengine.core.plugin.context.*; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; -import net.momirealms.craftengine.core.plugin.context.text.TextProvider; -import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -16,12 +14,12 @@ import java.util.List; import java.util.Map; public class ActionBarFunction extends AbstractConditionalFunction { - private final TextProvider message; + private final String message; private final PlayerSelector selector; - public ActionBarFunction(List> predicates, @Nullable PlayerSelector selector, TextProvider messages) { + public ActionBarFunction(List> predicates, @Nullable PlayerSelector selector, String message) { super(predicates); - this.message = messages; + this.message = message; this.selector = selector; } @@ -29,12 +27,12 @@ public class ActionBarFunction extends AbstractConditionalF public void runInternal(CTX ctx) { if (this.selector == null) { ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { - it.sendActionBar(AdventureHelper.miniMessage().deserialize(this.message.get(ctx), ctx.tagResolvers())); + it.sendActionBar(AdventureHelper.miniMessage().deserialize(this.message, ctx.tagResolvers())); }); } else { for (Player viewer : this.selector.get(ctx)) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); - viewer.sendActionBar(AdventureHelper.miniMessage().deserialize(this.message.get(relationalContext), relationalContext.tagResolvers())); + viewer.sendActionBar(AdventureHelper.miniMessage().deserialize(this.message, relationalContext.tagResolvers())); } } } @@ -53,7 +51,7 @@ public class ActionBarFunction extends AbstractConditionalF @Override public Function create(Map arguments) { String message = ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "actionbar", "message"), "warning.config.function.actionbar.missing_actionbar"); - return new ActionBarFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), TextProviders.fromString(message)); + return new ActionBarFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), message); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java index ffedc3333..c69d3663e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java @@ -5,8 +5,6 @@ import net.momirealms.craftengine.core.plugin.context.*; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; -import net.momirealms.craftengine.core.plugin.context.text.TextProvider; -import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -17,11 +15,11 @@ import java.util.List; import java.util.Map; public class MessageFunction extends AbstractConditionalFunction { - private final List messages; + private final List messages; private final PlayerSelector selector; private final boolean overlay; - public MessageFunction(List> predicates, @Nullable PlayerSelector selector, List messages, boolean overlay) { + public MessageFunction(List> predicates, @Nullable PlayerSelector selector, List messages, boolean overlay) { super(predicates); this.messages = messages; this.selector = selector; @@ -32,15 +30,15 @@ public class MessageFunction extends AbstractConditionalFun public void runInternal(CTX ctx) { if (this.selector == null) { ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { - for (TextProvider c : this.messages) { - it.sendMessage(AdventureHelper.miniMessage().deserialize(c.get(ctx), ctx.tagResolvers()), this.overlay); + for (String c : this.messages) { + it.sendMessage(AdventureHelper.miniMessage().deserialize(c, ctx.tagResolvers()), this.overlay); } }); } else { for (Player viewer : this.selector.get(ctx)) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); - for (TextProvider c : this.messages) { - viewer.sendMessage(AdventureHelper.miniMessage().deserialize(c.get(relationalContext), relationalContext.tagResolvers()), this.overlay); + for (String c : this.messages) { + viewer.sendMessage(AdventureHelper.miniMessage().deserialize(c, relationalContext.tagResolvers()), this.overlay); } } } @@ -60,7 +58,7 @@ public class MessageFunction extends AbstractConditionalFun @Override public Function create(Map arguments) { Object message = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(arguments, "messages", "message"), "warning.config.function.command.missing_message"); - List messages = MiscUtils.getAsStringList(message).stream().map(TextProviders::fromString).toList(); + List messages = MiscUtils.getAsStringList(message); boolean overlay = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("overlay", false), "overlay"); return new MessageFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), messages, overlay); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java index 3cda10b9b..bbe2fe3d0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java @@ -6,8 +6,6 @@ import net.momirealms.craftengine.core.plugin.context.*; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; -import net.momirealms.craftengine.core.plugin.context.text.TextProvider; -import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.plugin.gui.GuiType; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.AdventureHelper; @@ -24,9 +22,9 @@ import java.util.Optional; public class OpenWindowFunction extends AbstractConditionalFunction { private final PlayerSelector selector; private final GuiType guiType; - private final TextProvider optionalTitle; + private final String optionalTitle; - public OpenWindowFunction(List> predicates, @Nullable PlayerSelector selector, GuiType guiType, TextProvider optionalTitle) { + public OpenWindowFunction(List> predicates, @Nullable PlayerSelector selector, GuiType guiType, String optionalTitle) { super(predicates); this.selector = selector; this.guiType = guiType; @@ -39,7 +37,7 @@ public class OpenWindowFunction extends AbstractConditional ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { CraftEngine.instance().guiManager().openInventory(it, this.guiType); if (this.optionalTitle != null) { - CraftEngine.instance().guiManager().updateInventoryTitle(it, AdventureHelper.miniMessage().deserialize(this.optionalTitle.get(ctx), ctx.tagResolvers())); + CraftEngine.instance().guiManager().updateInventoryTitle(it, AdventureHelper.miniMessage().deserialize(this.optionalTitle, ctx.tagResolvers())); } }); } else { @@ -47,7 +45,7 @@ public class OpenWindowFunction extends AbstractConditional CraftEngine.instance().guiManager().openInventory(viewer, this.guiType); if (this.optionalTitle != null) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); - CraftEngine.instance().guiManager().updateInventoryTitle(viewer, AdventureHelper.miniMessage().deserialize(this.optionalTitle.get(relationalContext), relationalContext.tagResolvers())); + CraftEngine.instance().guiManager().updateInventoryTitle(viewer, AdventureHelper.miniMessage().deserialize(this.optionalTitle, relationalContext.tagResolvers())); } } } @@ -66,7 +64,7 @@ public class OpenWindowFunction extends AbstractConditional @Override public Function create(Map arguments) { - TextProvider title = Optional.ofNullable(arguments.get("title")).map(String::valueOf).map(TextProviders::fromString).orElse(null); + String title = Optional.ofNullable(arguments.get("title")).map(String::valueOf).orElse(null); String rawType = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("gui-type"), "warning.config.function.open_window.missing_gui_type"); try { GuiType type = GuiType.valueOf(rawType.toUpperCase(Locale.ENGLISH)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java index cbae62005..97a56b443 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java @@ -7,8 +7,6 @@ import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; -import net.momirealms.craftengine.core.plugin.context.text.TextProvider; -import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.Nullable; @@ -18,14 +16,14 @@ import java.util.Map; public class TitleFunction extends AbstractConditionalFunction { private final PlayerSelector selector; - private final TextProvider main; - private final TextProvider sub; + private final String main; + private final String sub; private final NumberProvider fadeIn; private final NumberProvider stay; private final NumberProvider fadeOut; public TitleFunction(List> predicates, @Nullable PlayerSelector selector, - TextProvider main, TextProvider sub, NumberProvider fadeIn, NumberProvider stay, NumberProvider fadeOut) { + String main, String sub, NumberProvider fadeIn, NumberProvider stay, NumberProvider fadeOut) { super(predicates); this.selector = selector; this.main = main; @@ -39,16 +37,16 @@ public class TitleFunction extends AbstractConditionalFunct public void runInternal(CTX ctx) { if (this.selector == null) { ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> it.sendTitle( - AdventureHelper.miniMessage().deserialize(this.main.get(ctx), ctx.tagResolvers()), - AdventureHelper.miniMessage().deserialize(this.sub.get(ctx), ctx.tagResolvers()), + AdventureHelper.miniMessage().deserialize(this.main, ctx.tagResolvers()), + AdventureHelper.miniMessage().deserialize(this.sub, ctx.tagResolvers()), this.fadeIn.getInt(ctx), this.stay.getInt(ctx), this.fadeOut.getInt(ctx) )); } else { for (Player viewer : this.selector.get(ctx)) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer)); viewer.sendTitle( - AdventureHelper.miniMessage().deserialize(this.main.get(relationalContext), relationalContext.tagResolvers()), - AdventureHelper.miniMessage().deserialize(this.sub.get(relationalContext), relationalContext.tagResolvers()), + AdventureHelper.miniMessage().deserialize(this.main, relationalContext.tagResolvers()), + AdventureHelper.miniMessage().deserialize(this.sub, relationalContext.tagResolvers()), this.fadeIn.getInt(relationalContext), this.stay.getInt(relationalContext), this.fadeOut.getInt(relationalContext) ); } @@ -73,8 +71,7 @@ public class TitleFunction extends AbstractConditionalFunct NumberProvider fadeIn = NumberProviders.fromObject(arguments.getOrDefault("fade-in", 10)); NumberProvider stay = NumberProviders.fromObject(arguments.getOrDefault("stay", 20)); NumberProvider fadeOut = NumberProviders.fromObject(arguments.getOrDefault("fade-out", 5)); - return new TitleFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), - TextProviders.fromString(title), TextProviders.fromString(subtitle), fadeIn, stay, fadeOut); + return new TitleFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), title, subtitle, fadeIn, stay, fadeOut); } } } diff --git a/gradle.properties b/gradle.properties index e6e8440a9..1aab59f11 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.3 +project_version=0.0.63.4 config_version=46 lang_version=30 project_group=net.momirealms From 71f75d5d16152087682197140bfeaf76246579a5 Mon Sep 17 00:00:00 2001 From: Xiao-MoMi <972454774@qq.com> Date: Fri, 19 Sep 2025 20:39:22 +0800 Subject: [PATCH 160/226] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=BC=B9=E5=B0=84?= =?UTF-8?q?=E7=89=A9=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/entity/projectile/ProjectileMeta.java | 4 +--- .../craftengine/core/entity/projectile/ProjectileType.java | 5 ----- .../net/momirealms/craftengine/core/item/ItemSettings.java | 4 +--- 3 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileType.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java b/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java index 4dbefc9b4..7801b8f9d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java @@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.entity.projectile; import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.util.Key; -import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -13,6 +12,5 @@ public record ProjectileMeta(Key item, Vector3f scale, Vector3f translation, Quaternionf rotation, - double range, - @Nullable ProjectileType type) { + double range) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileType.java b/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileType.java deleted file mode 100644 index 0c0e1d5ed..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileType.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.momirealms.craftengine.core.entity.projectile; - -public enum ProjectileType { - TRIDENT -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java index 03c355356..076316376 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java @@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.entity.projectile.ProjectileMeta; -import net.momirealms.craftengine.core.entity.projectile.ProjectileType; import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; import net.momirealms.craftengine.core.item.equipment.Equipment; import net.momirealms.craftengine.core.item.modifier.EquippableModifier; @@ -408,9 +407,8 @@ public class ItemSettings { Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation"); Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale"); Quaternionf rotation = ResourceConfigUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation"), "rotation"); - ProjectileType type = Optional.ofNullable(args.get("type")).map(String::valueOf).map(it -> ProjectileType.valueOf(it.toUpperCase(Locale.ENGLISH))).orElse(null); double range = ResourceConfigUtils.getAsDouble(args.getOrDefault("range", 1), "range"); - return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range, type)); + return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range)); })); registerFactory("helmet", (value -> { Map args = MiscUtils.castToMap(value, false); From 3ec61174180088f1379b2bd3b7651dbe6a6ea86f Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 23:33:16 +0800 Subject: [PATCH 161/226] =?UTF-8?q?fix(compatibility):=20=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=E4=B8=80=E4=B8=AA=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/compatibility/BukkitCompatibilityManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index c4ca0328e..8e4f5785e 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -20,6 +20,7 @@ import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockR import net.momirealms.craftengine.bukkit.font.BukkitFontManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.entity.furniture.ExternalModel; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.loot.LootConditions; @@ -32,6 +33,7 @@ import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldManager; import org.bukkit.Bukkit; +import org.bukkit.Registry; import org.bukkit.plugin.Plugin; import java.util.*; @@ -250,6 +252,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager { weBlockRegister.register(newBlockId); } } catch (Exception e) { + // 检查是不是有插件干预了注册表 + if (Registry.MATERIAL.get(KeyUtils.toNamespacedKey(BukkitBlockManager.instance().blockRegisterOrder().getFirst())) != null) return; this.plugin.logger().warn("Failed to initialize world edit hook", e); } } From 51a641ed91fd6f2f9e4903464bd6461e7b623a23 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sat, 20 Sep 2025 02:18:05 +0800 Subject: [PATCH 162/226] =?UTF-8?q?fix(network):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E9=94=99=E8=AF=AF=E7=9A=84=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/handler/MinecartPacketHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java index 718946298..ff4c81023 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java @@ -25,7 +25,7 @@ import java.util.Optional; public class MinecartPacketHandler implements EntityPacketHandler { public static final MinecartPacketHandler INSTANCE = new MinecartPacketHandler(); - private static final BlockStateHandler BLOCK_STATE_HANDLER = VersionHelper.isOrAbove1_21_3() ? BlockStateHandler_1_21_3.INSTANCE : BlockStateHandler_1_20.INSTANCE; + private static final BlockStateHandler BLOCK_STATE_HANDLER = VersionHelper.isOrAbove1_21_5() ? BlockStateHandler_1_21_5.INSTANCE : BlockStateHandler_1_20.INSTANCE; @Override public void handleSetEntityData(Player user, ByteBufPacketEvent event) { @@ -69,8 +69,8 @@ public class MinecartPacketHandler implements EntityPacketHandler { Object handle(NetWorkUser user, Object packedItem, int entityDataId); } - static class BlockStateHandler_1_21_3 implements BlockStateHandler { - protected static final BlockStateHandler INSTANCE = new BlockStateHandler_1_21_3(); + static class BlockStateHandler_1_21_5 implements BlockStateHandler { + protected static final BlockStateHandler INSTANCE = new BlockStateHandler_1_21_5(); @Override public Object handle(NetWorkUser user, Object packedItem, int entityDataId) { From 862203ca2a730d31010cdfeec20651ecb3c151b8 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sat, 20 Sep 2025 02:32:12 +0800 Subject: [PATCH 163/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BukkitCompatibilityManager.java | 4 ---- .../plugin/injector/BlockStateGenerator.java | 16 ++++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index 8e4f5785e..c4ca0328e 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -20,7 +20,6 @@ import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockR import net.momirealms.craftengine.bukkit.font.BukkitFontManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.entity.furniture.ExternalModel; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.loot.LootConditions; @@ -33,7 +32,6 @@ import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldManager; import org.bukkit.Bukkit; -import org.bukkit.Registry; import org.bukkit.plugin.Plugin; import java.util.*; @@ -252,8 +250,6 @@ public class BukkitCompatibilityManager implements CompatibilityManager { weBlockRegister.register(newBlockId); } } catch (Exception e) { - // 检查是不是有插件干预了注册表 - if (Registry.MATERIAL.get(KeyUtils.toNamespacedKey(BukkitBlockManager.instance().blockRegisterOrder().getFirst())) != null) return; this.plugin.logger().warn("Failed to initialize world edit hook", e); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java index 9281595d8..148fa9e3d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java @@ -243,14 +243,14 @@ public final class BlockStateGenerator { @RuntimeType public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - DelegatingBlockState customState = (DelegatingBlockState) thisObj; - ImmutableBlockState thisState = customState.blockState(); - if (thisState == null) return false; - CustomBlock thisBlock = thisState.owner().value(); Object block = FastNMS.INSTANCE.method$Holder$value(args[0]); if (!(block instanceof DelegatingBlock delegatingBlock)) return false; BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); if (behavior == null) return false; + DelegatingBlockState customState = (DelegatingBlockState) thisObj; + ImmutableBlockState thisState = customState.blockState(); + if (thisState == null) return false; + CustomBlock thisBlock = thisState.owner().value(); return behavior.block().equals(thisBlock); } } @@ -260,16 +260,16 @@ public final class BlockStateGenerator { @RuntimeType public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - DelegatingBlockState customState = (DelegatingBlockState) thisObj; - ImmutableBlockState thisState = customState.blockState(); - if (thisState == null) return false; - CustomBlock thisBlock = thisState.owner().value(); Object block = FastNMS.INSTANCE.method$HolderGetter$getResourceKey(MBuiltInRegistries.BLOCK, args[0]) .map(FastNMS.INSTANCE::method$Holder$value) .orElse(null); if (!(block instanceof DelegatingBlock delegatingBlock)) return false; BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); if (behavior == null) return false; + DelegatingBlockState customState = (DelegatingBlockState) thisObj; + ImmutableBlockState thisState = customState.blockState(); + if (thisState == null) return false; + CustomBlock thisBlock = thisState.owner().value(); return behavior.block().equals(thisBlock); } } From ea1b684982dd803d7c35d5208df84baf2478c4a1 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sat, 20 Sep 2025 02:35:21 +0800 Subject: [PATCH 164/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/ButtonBlockBehavior.java | 12 ++++++------ .../block/behavior/PressurePlateBlockBehavior.java | 5 ++--- .../{MEntitySelector.java => MEntitySelectors.java} | 4 ++-- .../minecraft/{MGameEvent.java => MGameEvents.java} | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/{MEntitySelector.java => MEntitySelectors.java} (80%) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/{MGameEvent.java => MGameEvents.java} (89%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java index 3be8fa890..afa85f737 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelector; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MGameEvent; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelectors; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MGameEvents; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.KeyUtils; @@ -138,7 +138,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move( FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape( state, level, pos, CoreReflections.instance$CollisionContext$empty - )), pos), MEntitySelector.NO_SPECTATORS).stream().findFirst().orElse(null) : null; + )), pos), MEntitySelectors.NO_SPECTATORS).stream().findFirst().orElse(null) : null; boolean flag = abstractArrow != null; ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); if (blockState == null) return; @@ -148,8 +148,8 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { updateNeighbours(thisBlock, blockState, level, pos); playSound(null, level, pos, flag); Object gameEvent = VersionHelper.isOrAbove1_20_5() - ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE) - : flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE; + ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE) + : flag ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE; FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, abstractArrow, gameEvent, pos); } @@ -192,7 +192,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); playSound(player, level, pos, true); - Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvent.BLOCK_ACTIVATE) : MGameEvent.BLOCK_ACTIVATE; + Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvents.BLOCK_ACTIVATE) : MGameEvents.BLOCK_ACTIVATE; FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 0453403b5..3d8263e77 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -4,7 +4,7 @@ import io.papermc.paper.event.entity.EntityInsideBlockEvent; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelector; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelectors; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; @@ -29,7 +29,6 @@ import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -import java.util.function.Predicate; public class PressurePlateBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); @@ -113,7 +112,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { Object box = FastNMS.INSTANCE.method$AABB$move(CoreReflections.instance$BasePressurePlateBlock$TOUCH_AABB, pos); return !FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( level, clazz, box, - MEntitySelector.NO_SPECTATORS.and(entity -> !FastNMS.INSTANCE.method$Entity$isIgnoringBlockTriggers(entity)) + MEntitySelectors.NO_SPECTATORS.and(entity -> !FastNMS.INSTANCE.method$Entity$isIgnoringBlockTriggers(entity)) ).isEmpty() ? 15 : 0; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelectors.java similarity index 80% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelectors.java index 7a2b43ab7..76bbaf19b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelectors.java @@ -4,8 +4,8 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import java.util.function.Predicate; -public final class MEntitySelector { - private MEntitySelector() {} +public final class MEntitySelectors { + private MEntitySelectors() {} public static final Predicate NO_SPECTATORS = entity -> !FastNMS.INSTANCE.method$Entity$isSpectator(entity); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvents.java similarity index 89% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvents.java index 500f39d8c..fad931574 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvents.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; import net.momirealms.craftengine.bukkit.nms.FastNMS; -public final class MGameEvent { - private MGameEvent() {} +public final class MGameEvents { + private MGameEvents() {} public static final Object BLOCK_ACTIVATE = getById("block_activate"); public static final Object BLOCK_DEACTIVATE = getById("block_deactivate"); From 150444819c9df01ee1eafcf757d14af8c30d69f7 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sat, 20 Sep 2025 02:46:42 +0800 Subject: [PATCH 165/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...hedHorizontalDirectionalBlockBehavior.java | 20 +++++++++---------- .../src/main/resources/translations/en.yml | 3 +++ .../src/main/resources/translations/zh_cn.yml | 3 +++ .../core/block/properties/Properties.java | 4 ++-- .../{AttachFace.java => AnchorType.java} | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) rename core/src/main/java/net/momirealms/craftengine/core/block/state/properties/{AttachFace.java => AnchorType.java} (80%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java index cc877ef06..597470895 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java @@ -11,7 +11,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.AttachFace; +import net.momirealms.craftengine.core.block.state.properties.AnchorType; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; @@ -27,7 +27,7 @@ import java.util.concurrent.Callable; public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final Property attachFaceProperty; + private final Property anchorTypeProperty; private final Property facingProperty; private final List tagsCanSurviveOn; private final Set blockStatesCanSurviveOn; @@ -39,14 +39,14 @@ public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockB List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn, - Property attachFace, + Property anchorType, Property facing) { super(customBlock); this.tagsCanSurviveOn = tagsCanSurviveOn; this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; this.blacklistMode = blacklist; - this.attachFaceProperty = attachFace; + this.anchorTypeProperty = anchorType; this.facingProperty = facing; } @@ -64,16 +64,16 @@ public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockB @SuppressWarnings("unchecked") @Override public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { - Property face = (Property) state.owner().value().getProperty("face"); + Property face = (Property) state.owner().value().getProperty("face"); Property facing = (Property) state.owner().value().getProperty("facing"); if (face == null || facing == null) return null; for (Direction direction : context.getNearestLookingDirections()) { if (direction.axis() == Direction.Axis.Y) { state = state - .with(face, direction == Direction.UP ? AttachFace.CEILING : AttachFace.FLOOR) + .with(face, direction == Direction.UP ? AnchorType.CEILING : AnchorType.FLOOR) .with(facing, context.getHorizontalDirection().toHorizontalDirection()); } else { - state = state.with(face, AttachFace.WALL).with(facing, direction.opposite().toHorizontalDirection()); + state = state.with(face, AnchorType.WALL).with(facing, direction.opposite().toHorizontalDirection()); } if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()))) { return state; @@ -128,7 +128,7 @@ public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockB if (state == null) return null; FaceAttachedHorizontalDirectionalBlockBehavior behavior = state.behavior().getAs(FaceAttachedHorizontalDirectionalBlockBehavior.class).orElse(null); if (behavior == null) return null; - return switch (state.get(behavior.attachFaceProperty)) { + return switch (state.get(behavior.anchorTypeProperty)) { case CEILING -> Direction.DOWN; case FLOOR -> Direction.UP; default -> state.get(behavior.facingProperty).toDirection(); @@ -140,11 +140,11 @@ public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockB @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Property attachFace = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("face"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_face"); + Property anchorType = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("face"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_face"); Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_facing"); Tuple, Set, Set> tuple = DirectionalAttachedBlockBehavior.readTagsAndState(arguments); boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist"); - return new FaceAttachedHorizontalDirectionalBlockBehavior(block, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), attachFace, facing); + return new FaceAttachedHorizontalDirectionalBlockBehavior(block, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), anchorType, facing); } } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 71250f8c3..e2ea5b94a 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -325,6 +325,9 @@ warning.config.block.behavior.fence.missing_north: "Issue found in file warning.config.block.behavior.fence.missing_east: "Issue found in file - The block '' is missing the required 'east' property for 'fence_block' behavior." warning.config.block.behavior.fence.missing_south: "Issue found in file - The block '' is missing the required 'south' property for 'fence_block' behavior." warning.config.block.behavior.fence.missing_west: "Issue found in file - The block '' is missing the required 'west' property for 'fence_block' behavior." +warning.config.block.behavior.face_attached_horizontal_directional.missing_face: "Issue found in file - The block '' is missing the required 'face' property for 'face_attached_horizontal_directional_block' behavior." +warning.config.block.behavior.face_attached_horizontal_directional.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'face_attached_horizontal_directional_block' behavior." +warning.config.block.behavior.button.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'button_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 1c2c4ca57..add5680d3 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -319,6 +319,9 @@ warning.config.block.behavior.fence.missing_north: "在文件 warning.config.block.behavior.fence.missing_east: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'east' 属性" warning.config.block.behavior.fence.missing_south: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'south' 属性" warning.config.block.behavior.fence.missing_west: "在文件 发现问题 - 方块 '' 的 'fence_block' 行为缺少必需的 'west' 属性" +warning.config.block.behavior.face_attached_horizontal_directional.missing_face: "在文件 发现问题 - 方块 '' 的 'face_attached_horizontal_directional_block' 行为缺少必需的 'face' 属性" +warning.config.block.behavior.face_attached_horizontal_directional.missing_facing: "在文件 发现问题 - 方块 '' 的 'face_attached_horizontal_directional_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.button.missing_powered: "在文件 发现问题 - 方块 '' 的 'button_block' 行为缺少必需的 'powered' 属性" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java index e559a080a..42753ec45 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java @@ -22,7 +22,7 @@ public final class Properties { public static final Key STAIRS_SHAPE = Key.of("craftengine:stairs_shape"); public static final Key SLAB_TYPE = Key.of("craftengine:slab_type"); public static final Key SOFA_SHAPE = Key.of("craftengine:sofa_shape"); - public static final Key ATTACH_FACE = Key.of("craftengine:attach_face"); + public static final Key ANCHOR_TYPE = Key.of("craftengine:anchor_type"); static { register(BOOLEAN, BooleanProperty.FACTORY); @@ -39,7 +39,7 @@ public final class Properties { register(STAIRS_SHAPE, new EnumProperty.Factory<>(StairsShape.class)); register(SLAB_TYPE, new EnumProperty.Factory<>(SlabType.class)); register(SOFA_SHAPE, new EnumProperty.Factory<>(SofaShape.class)); - register(ATTACH_FACE, new EnumProperty.Factory<>(AttachFace.class)); + register(ANCHOR_TYPE, new EnumProperty.Factory<>(AnchorType.class)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java similarity index 80% rename from core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java rename to core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java index de9d6267b..65bec0f74 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.core.block.state.properties; -public enum AttachFace { +public enum AnchorType { FLOOR, WALL, CEILING From a45feed63db6fae2cb11172478324b406a1c7f86 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 21 Sep 2025 04:22:44 +0800 Subject: [PATCH 166/226] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=85=BC=E5=AE=B91.2?= =?UTF-8?q?1.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/util/LegacyAuthLibUtils.java | 16 ++ .../block/behavior/FallingBlockBehavior.java | 7 +- .../bukkit/entity/data/EntityData.java | 7 + .../bukkit/entity/data/EntityDataValue.java | 3 +- .../item/factory/BukkitItemFactory.java | 2 +- .../plugin/command/BukkitCommandManager.java | 2 +- .../plugin/network/BukkitNetworkManager.java | 23 ++- .../handler/ProjectilePacketHandler.java | 15 +- .../reflection/minecraft/CoreReflections.java | 36 ++-- .../default/resourcepack/pack.mcmeta | 8 +- .../craftengine/core/block/BlockBehavior.java | 3 +- .../resolution/ResolutionMergePackMcMeta.java | 167 +++++++++++++----- .../core/util/FriendlyByteBuf.java | 65 +++++-- .../craftengine/core/util/MCUtils.java | 9 + .../core/util/MinecraftVersion.java | 1 + .../core/util/MinecraftVersions.java | 1 + .../craftengine/core/util/VersionHelper.java | 6 + .../craftengine/core/world/Vec3d.java | 1 + gradle.properties | 4 +- 19 files changed, 280 insertions(+), 96 deletions(-) create mode 100644 bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyAuthLibUtils.java diff --git a/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyAuthLibUtils.java b/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyAuthLibUtils.java new file mode 100644 index 000000000..63a00b16e --- /dev/null +++ b/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyAuthLibUtils.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.bukkit.util; + +import com.mojang.authlib.GameProfile; + +import java.util.UUID; + +public class LegacyAuthLibUtils { + + public static String getName(GameProfile profile) { + return profile.getName(); + } + + public static UUID getId(GameProfile profile) { + return profile.getId(); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index e41956aec..48a99aa1f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; @@ -75,8 +76,7 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { Object level = args[0]; Object fallingBlockEntity = args[2]; Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlockEntity); - boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); - if (!isSilent) { + if (!BaseEntityData.Silent.get(entityData)) { Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; @@ -93,12 +93,11 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { Object level = args[0]; Object pos = args[1]; Object entityData = CoreReflections.field$Entity$entityData.get(fallingBlock); - boolean isSilent = (boolean) CoreReflections.method$SynchedEntityData$get.invoke(entityData, CoreReflections.instance$Entity$DATA_SILENT); Object blockState = args[2]; int stateId = BlockStateUtils.blockStateToId(blockState); ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId); if (immutableBlockState == null || immutableBlockState.isEmpty()) return; - if (!isSilent) { + if (!BaseEntityData.Silent.get(entityData)) { net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), immutableBlockState.settings().sounds().landSound()); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityData.java index da99028c4..0574a93f9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityData.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityData.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.bukkit.entity.data; +import net.momirealms.craftengine.bukkit.nms.FastNMS; + import java.util.List; public interface EntityData { @@ -27,6 +29,11 @@ public interface EntityData { list.add(EntityDataValue.create(id(), serializer(), entityDataAccessor(), value)); } + @SuppressWarnings("unchecked") + default T get(Object entityData) { + return (T) FastNMS.INSTANCE.method$SynchedEntityData$get(entityData, entityDataAccessor()); + } + static EntityData of(Class clazz, Object serializer, T defaultValue) { return new SimpleEntityData<>(clazz, serializer, defaultValue); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java index fee5d034f..30d191d09 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java @@ -71,7 +71,8 @@ public class EntityDataValue { if (VersionHelper.isOrAbove1_21_5()) Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE = initSerializersByName("OPTIONAL_LIVING_ENTITY_REFERENCE"); else Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE = null; Serializers$OPTIONAL_GLOBAL_POS = initSerializersByName("OPTIONAL_GLOBAL_POS"); - Serializers$COMPOUND_TAG = initSerializersByName("COMPOUND_TAG"); + if (!VersionHelper.isOrAbove1_21_9()) Serializers$COMPOUND_TAG = initSerializersByName("COMPOUND_TAG"); + else Serializers$COMPOUND_TAG = null; Serializers$VILLAGER_DATA = initSerializersByName("VILLAGER_DATA"); Serializers$OPTIONAL_UNSIGNED_INT = initSerializersByName("OPTIONAL_UNSIGNED_INT"); Serializers$POSE = initSerializersByName("POSE"); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java index c8f760d99..f836d910b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java @@ -49,7 +49,7 @@ public abstract class BukkitItemFactory> extend case "1.21.4" -> { return new ComponentItemFactory1_21_4(plugin); } - case "1.21.5", "1.21.6", "1.21.7", "1.21.8" -> { + case "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9" -> { return new ComponentItemFactory1_21_5(plugin); } default -> throw new IllegalStateException("Unsupported server version: " + plugin.serverVersion()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 9d80faf29..024b38db9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -60,7 +60,7 @@ public class BukkitCommandManager extends AbstractCommandManager )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); - if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) { + if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER) && manager.hasCapability(CloudBukkitCapabilities.BRIGADIER)) { manager.registerBrigadier(); manager.brigadierManager().setNativeNumberSuggestions(true); } else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 4671b7ed5..7ced7ec1b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -1815,8 +1815,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes @Override public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { GameProfile gameProfile = FastNMS.INSTANCE.field$ClientboundLoginFinishedPacket$gameProfile(packet); - user.setVerifiedName(gameProfile.getName()); - user.setVerifiedUUID(gameProfile.getId()); + if (VersionHelper.isOrAbove1_21_9()) { + user.setVerifiedName(gameProfile.name()); + user.setVerifiedUUID(gameProfile.id()); + } else { + user.setVerifiedName(LegacyAuthLibUtils.getName(gameProfile)); + user.setVerifiedUUID(LegacyAuthLibUtils.getId(gameProfile)); + } } } @@ -3533,6 +3538,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes double x = buf.readDouble(); double y = buf.readDouble(); double z = buf.readDouble(); + Vec3d movement = VersionHelper.isOrAbove1_21_9() ? buf.readLpVec3() : null; byte xRot = buf.readByte(); byte yRot = buf.readByte(); byte yHeadRot = buf.readByte(); @@ -3540,9 +3546,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // Falling blocks int remapped = remapBlockState(data, user.clientModEnabled()); if (remapped != data) { - int xa = buf.readShort(); - int ya = buf.readShort(); - int za = buf.readShort(); + int xa = VersionHelper.isOrAbove1_21_9() ? -1 : buf.readShort(); + int ya = VersionHelper.isOrAbove1_21_9() ? -1 : buf.readShort(); + int za = VersionHelper.isOrAbove1_21_9() ? -1 : buf.readShort(); event.setChanged(true); buf.clear(); buf.writeVarInt(event.packetID()); @@ -3552,13 +3558,14 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.writeDouble(x); buf.writeDouble(y); buf.writeDouble(z); + if (VersionHelper.isOrAbove1_21_9()) buf.writeLpVec3(movement); buf.writeByte(xRot); buf.writeByte(yRot); buf.writeByte(yHeadRot); buf.writeVarInt(remapped); - buf.writeShort(xa); - buf.writeShort(ya); - buf.writeShort(za); + if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(xa); + if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(ya); + if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(za); } }; this.handlers[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java index 6e697af25..1524c7a39 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java @@ -18,6 +18,7 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.Vec3d; import org.bukkit.inventory.ItemStack; import java.util.ArrayList; @@ -70,13 +71,14 @@ public class ProjectilePacketHandler implements EntityPacketHandler { double x = buf.readDouble(); double y = buf.readDouble(); double z = buf.readDouble(); + Vec3d movement = VersionHelper.isOrAbove1_21_9() ? buf.readLpVec3() : null; byte xRot = buf.readByte(); byte yRot = buf.readByte(); byte yHeadRot = buf.readByte(); int data = buf.readVarInt(); - int xa = buf.readShort(); - int ya = buf.readShort(); - int za = buf.readShort(); + int xa = VersionHelper.isOrAbove1_21_9() ? -1 : buf.readShort(); + int ya = VersionHelper.isOrAbove1_21_9() ? -1 : buf.readShort(); + int za = VersionHelper.isOrAbove1_21_9() ? -1 : buf.readShort(); event.setChanged(true); buf.clear(); buf.writeVarInt(event.packetID()); @@ -86,13 +88,14 @@ public class ProjectilePacketHandler implements EntityPacketHandler { buf.writeDouble(x); buf.writeDouble(y); buf.writeDouble(z); + if (VersionHelper.isOrAbove1_21_9()) buf.writeLpVec3(movement); buf.writeByte(MCUtils.packDegrees(MCUtils.clamp(-MCUtils.unpackDegrees(xRot), -90.0F, 90.0F))); buf.writeByte(MCUtils.packDegrees(-MCUtils.unpackDegrees(yRot))); buf.writeByte(yHeadRot); buf.writeVarInt(data); - buf.writeShort(xa); - buf.writeShort(ya); - buf.writeShort(za); + if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(xa); + if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(ya); + if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(za); } private Object convertCustomProjectilePositionSyncPacket(Object packet) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 571f99f4a..65a0549f6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -667,9 +667,9 @@ public final class CoreReflections { ) ); - public static final Method method$SynchedEntityData$get = requireNonNull( - ReflectionUtils.getMethod(clazz$SynchedEntityData, Object.class, clazz$EntityDataAccessor) - ); + // public static final Method method$SynchedEntityData$get = requireNonNull( + // ReflectionUtils.getDeclaredMethod(clazz$SynchedEntityData, Object.class, new String[]{"get", VersionHelper.isOrAbove1_20_5() ? "a" : "b"}, clazz$EntityDataAccessor) + // ); public static final Class clazz$SynchedEntityData$DataValue = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( @@ -974,9 +974,9 @@ public final class CoreReflections { ReflectionUtils.getDeclaredField(clazz$PalettedContainer$Data, clazz$Palette, 0) ); - public static final Method method$Palette$write = requireNonNull( - ReflectionUtils.getMethod(clazz$Palette, void.class, clazz$FriendlyByteBuf) - ); + // public static final Method method$Palette$write = requireNonNull( + // ReflectionUtils.getMethod(clazz$Palette, void.class, clazz$FriendlyByteBuf) + // ); public static final Class clazz$ChunkAccess = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( @@ -1576,7 +1576,9 @@ public final class CoreReflections { ); public static final Method method$BlockBehaviour$getAnalogOutputSignal = requireNonNull( - ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, int.class, new String[]{"getAnalogOutputSignal", "a"}, clazz$BlockState, clazz$Level, clazz$BlockPos) + VersionHelper.isOrAbove1_21_9() + ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, int.class, new String[]{"getAnalogOutputSignal", "a"}, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$Direction) + : ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, int.class, new String[]{"getAnalogOutputSignal", "a"}, clazz$BlockState, clazz$Level, clazz$BlockPos) ); public static final Method method$Entity$level = requireNonNull( @@ -3609,23 +3611,23 @@ public final class CoreReflections { clazz$Registry, clazz$HolderLookup$RegistryLookup, new String[]{"asLookup", "p"} ); - public static final Field field$ServerEntity$broadcast = requireNonNull( - ReflectionUtils.getDeclaredField( - clazz$ServerEntity, Consumer.class, 0 - ) - ); + // public static final Field field$ServerEntity$broadcast = requireNonNull( + // ReflectionUtils.getDeclaredField( + // clazz$ServerEntity, Consumer.class, 0 + // ) + // ); - public static final MethodHandle methodHandle$ServerEntity$broadcastSetter; + // public static final MethodHandle methodHandle$ServerEntity$broadcastSetter; public static final MethodHandle methodHandle$ServerEntity$updateIntervalSetter; public static final MethodHandle methodHandle$ServerPlayer$connectionGetter; public static final MethodHandle methodHandle$ServerPlayer$getAttributeMethod; static { try { - methodHandle$ServerEntity$broadcastSetter = requireNonNull( - ReflectionUtils.unreflectSetter(field$ServerEntity$broadcast) - .asType(MethodType.methodType(void.class, Object.class, Consumer.class)) - ); + // methodHandle$ServerEntity$broadcastSetter = requireNonNull( + // ReflectionUtils.unreflectSetter(field$ServerEntity$broadcast) + // .asType(MethodType.methodType(void.class, Object.class, Consumer.class)) + // ); methodHandle$ServerEntity$updateIntervalSetter = requireNonNull( ReflectionUtils.unreflectSetter(field$ServerEntity$updateInterval) .asType(MethodType.methodType(void.class, Object.class, int.class)) diff --git a/common-files/src/main/resources/resources/default/resourcepack/pack.mcmeta b/common-files/src/main/resources/resources/default/resourcepack/pack.mcmeta index 0ae3705e1..4d5e8390e 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/pack.mcmeta +++ b/common-files/src/main/resources/resources/default/resourcepack/pack.mcmeta @@ -1,10 +1,12 @@ { - "pack":{ + "pack": { "pack_format": 15, - "description":"CraftEngine", + "description": "CraftEngine", "supported_formats": { "min_inclusive": 15, "max_inclusive": 1000 - } + }, + "min_format": 15, + "max_format": 1000 } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index e969cab8c..d81554d1a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -100,7 +100,8 @@ public abstract class BlockBehavior { return false; } - // BlockState state, Level level, BlockPos pos + // 1.20.1~1.21.8 BlockState state, Level level, BlockPos pos + // 1.21.9+ BlockState state, Level level, BlockPos pos public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { return 0; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java index 186a1c3eb..be609eeb1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java @@ -19,63 +19,148 @@ public class ResolutionMergePackMcMeta implements Resolution { this.description = description; } - private static class MinMax { - int min; - int max; - - MinMax(int min, int max) { - this.min = min; - this.max = max; - } + record MinMax(int min, int max) { } + @SuppressWarnings("DuplicatedCode") public static void mergeMcMeta(Path file1, Path file2, JsonElement customDescription) throws IOException { - JsonElement elem1 = GsonHelper.readJsonFile(file1); - JsonElement elem2 = GsonHelper.readJsonFile(file2); + JsonObject mcmeta1 = GsonHelper.readJsonFile(file1).getAsJsonObject(); + JsonObject mcmeta2 = GsonHelper.readJsonFile(file2).getAsJsonObject(); - JsonObject merged = mergeValues(elem1.getAsJsonObject(), elem2.getAsJsonObject()) - .getAsJsonObject(); + JsonObject merged = mergeValues(mcmeta1, mcmeta2).getAsJsonObject(); - if (merged.has("pack")) { - JsonObject pack = merged.getAsJsonObject("pack"); + if (mcmeta1.has("pack") && mcmeta2.has("pack")) { + JsonObject mergedPack = merged.getAsJsonObject("pack"); + JsonObject mcmeta1Pack = mcmeta1.getAsJsonObject("pack"); + JsonObject mcmeta2Pack = mcmeta2.getAsJsonObject("pack"); - int pf1 = elem1.getAsJsonObject().getAsJsonObject("pack") - .getAsJsonPrimitive("pack_format").getAsInt(); - int pf2 = elem2.getAsJsonObject().getAsJsonObject("pack") - .getAsJsonPrimitive("pack_format").getAsInt(); - pack.addProperty("pack_format", Math.max(pf1, pf2)); + int minPackFormat = Integer.MAX_VALUE; + int maxPackFormat = Integer.MIN_VALUE; + JsonArray mergedMinFormat = null; + JsonArray mergedMaxFormat = null; - JsonElement sf1 = elem1.getAsJsonObject().getAsJsonObject("pack") - .get("supported_formats"); - JsonElement sf2 = elem2.getAsJsonObject().getAsJsonObject("pack") - .get("supported_formats"); + if (mcmeta1Pack.has("pack_format") && mcmeta2Pack.has("pack_format")) { + int packFormat1 = mcmeta1Pack.getAsJsonPrimitive("pack_format").getAsInt(); + int packFormat2 = mcmeta2Pack.getAsJsonPrimitive("pack_format").getAsInt(); + int mergedPackFormat = maxPackFormat = Math.max(packFormat1, packFormat2); + minPackFormat = Math.min(packFormat1, packFormat2); + mergedPack.addProperty("pack_format", mergedPackFormat); + } else if (mcmeta1Pack.has("pack_format")) { + minPackFormat = maxPackFormat = mcmeta1Pack.getAsJsonPrimitive("pack_format").getAsInt(); + } else if (mcmeta2Pack.has("pack_format")) { + minPackFormat = maxPackFormat = mcmeta2Pack.getAsJsonPrimitive("pack_format").getAsInt(); + } - if (sf1 != null || sf2 != null) { - MinMax mergedMinMax = getMergedMinMax(sf1, sf2, pf1, pf2); + if (mcmeta1Pack.has("min_format") || mcmeta2Pack.has("min_format")) { + int[] minFormat1 = new int[]{Integer.MAX_VALUE, 0}; + int[] minFormat2 = new int[]{Integer.MAX_VALUE, 0}; - JsonElement mergedSf = createSupportedFormatsElement( - sf1 != null ? sf1 : sf2, + if (mcmeta1Pack.has("min_format")) { + JsonElement minFormat = mcmeta1Pack.get("min_format"); + if (minFormat.isJsonPrimitive()) { + minFormat1[0] = minFormat.getAsInt(); + } + if (minFormat.isJsonArray()) { + JsonArray minFormatArray = minFormat.getAsJsonArray(); + minFormat1[0] = minFormatArray.get(0).getAsInt(); + if (minFormatArray.size() > 1) { + minFormat1[1] = minFormatArray.get(1).getAsInt(); + } + } + } + + if (mcmeta2Pack.has("min_format")) { + JsonElement minFormat = mcmeta2Pack.get("min_format"); + if (minFormat.isJsonPrimitive()) { + minFormat2[0] = minFormat.getAsInt(); + } + if (mcmeta2Pack.isJsonArray()) { + JsonArray minFormatArray = minFormat.getAsJsonArray(); + minFormat2[0] = minFormatArray.get(0).getAsInt(); + if (minFormatArray.size() > 1) { + minFormat2[1] = minFormatArray.get(1).getAsInt(); + } + } + } + minPackFormat = Math.min(minPackFormat, Math.min(minFormat1[0], minFormat2[0])); + mergedMinFormat = new JsonArray(2); + mergedMinFormat.add(minPackFormat); + mergedMinFormat.add(Math.min(minFormat1[1], minFormat2[1])); + mergedPack.add("min_format", mergedMinFormat); + } + + if (mcmeta1Pack.has("max_format") || mcmeta2Pack.has("max_format")) { + int[] maxFormat1 = new int[]{Integer.MIN_VALUE, 0}; + int[] maxFormat2 = new int[]{Integer.MIN_VALUE, 0}; + + if (mcmeta1Pack.has("max_format")) { + JsonElement maxFormat = mcmeta1Pack.get("max_format"); + if (maxFormat.isJsonPrimitive()) { + maxFormat1[0] = maxFormat.getAsInt(); + } + if (maxFormat.isJsonArray()) { + JsonArray maxFormatArray = maxFormat.getAsJsonArray(); + maxFormat1[0] = maxFormatArray.get(0).getAsInt(); + if (maxFormatArray.size() > 1) { + maxFormat1[1] = maxFormatArray.get(1).getAsInt(); + } + } + } + + if (mcmeta2Pack.has("max_format")) { + JsonElement maxFormat = mcmeta2Pack.get("max_format"); + if (maxFormat.isJsonPrimitive()) { + maxFormat2[0] = maxFormat.getAsInt(); + } + if (maxFormat.isJsonArray()) { + JsonArray maxFormatArray = maxFormat.getAsJsonArray(); + maxFormat2[0] = maxFormatArray.get(0).getAsInt(); + if (maxFormatArray.size() > 1) { + maxFormat2[1] = maxFormatArray.get(1).getAsInt(); + } + } + } + + maxPackFormat = Math.max(maxPackFormat, Math.max(maxFormat1[0], maxFormat2[0])); + mergedMaxFormat = new JsonArray(2); + mergedMaxFormat.add(maxPackFormat); + mergedMaxFormat.add(Math.max(maxFormat1[1], maxFormat2[1])); + mergedPack.add("max_format", mergedMaxFormat); + } + + JsonElement supportedFormats1 = mcmeta1Pack.get("supported_formats"); + JsonElement supportedFormats2 = mcmeta2Pack.get("supported_formats"); + + if (supportedFormats1 != null || supportedFormats2 != null) { + MinMax mergedMinMax = getMergedMinMax(supportedFormats1, supportedFormats2, minPackFormat, maxPackFormat); + JsonElement mergedSupportedFormats = createSupportedFormatsElement( + supportedFormats1 != null ? supportedFormats1 : supportedFormats2, mergedMinMax.min, mergedMinMax.max ); - - pack.add("supported_formats", mergedSf); + if (mergedMinFormat != null && !mergedMinFormat.isEmpty()) { + mergedMinFormat.set(0, new JsonPrimitive(Math.min(mergedMinMax.min, mergedMinFormat.get(0).getAsInt()))); + } + if (mergedMaxFormat != null && !mergedMaxFormat.isEmpty()) { + mergedMaxFormat.set(0, new JsonPrimitive(Math.max(mergedMinMax.max, mergedMaxFormat.get(0).getAsInt()))); + } + mergedPack.add("supported_formats", mergedSupportedFormats); } if (customDescription != null) { - pack.add("description", customDescription); + mergedPack.add("description", customDescription); } else { - JsonPrimitive desc1 = elem1.getAsJsonObject().getAsJsonObject("pack") + JsonPrimitive description1 = mcmeta1.getAsJsonObject().getAsJsonObject("pack") .getAsJsonPrimitive("description"); - JsonPrimitive desc2 = elem2.getAsJsonObject().getAsJsonObject("pack") + JsonPrimitive description2 = mcmeta2.getAsJsonObject().getAsJsonObject("pack") .getAsJsonPrimitive("description"); - String mergedDesc = (desc1 != null ? desc1.getAsString() : "") - + (desc1 != null && desc2 != null ? "\n" : "") - + (desc2 != null ? desc2.getAsString() : ""); + String mergedDesc = (description1 != null ? description1.getAsString() : "") + + (description1 != null && description2 != null ? "\n" : "") + + (description2 != null ? description2.getAsString() : ""); if (!mergedDesc.isEmpty()) { - pack.addProperty("description", mergedDesc); + mergedPack.addProperty("description", mergedDesc); } } } @@ -83,16 +168,14 @@ public class ResolutionMergePackMcMeta implements Resolution { GsonHelper.writeJsonFile(merged, file1); } - private static MinMax getMergedMinMax(JsonElement sf1, JsonElement sf2, int pf1, int pf2) { + private static MinMax getMergedMinMax(JsonElement sf1, JsonElement sf2, int minPackFormat, int maxPackFormat) { MinMax mm1 = parseSupportedFormats(sf1); MinMax mm2 = parseSupportedFormats(sf2); int finalMin = Math.min(mm1.min, mm2.min); int finalMax = Math.max(mm1.max, mm2.max); - int pfMin = Math.min(pf1, pf2); - int pfMax = Math.max(pf1, pf2); - finalMin = Math.min(pfMin, finalMin); - finalMax = Math.max(pfMax, finalMax); + finalMin = Math.min(minPackFormat, finalMin); + finalMax = Math.max(maxPackFormat, finalMax); return new MinMax(finalMin, finalMax); } @@ -110,7 +193,7 @@ public class ResolutionMergePackMcMeta implements Resolution { if (supported.isJsonArray()) { JsonArray arr = supported.getAsJsonArray(); int min = arr.get(0).getAsInt(); - int max = arr.get(arr.size()-1).getAsInt(); + int max = arr.get(arr.size() - 1).getAsInt(); return new MinMax(min, max); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java index 68e30c8a2..62c582ee7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java @@ -14,6 +14,7 @@ import it.unimi.dsi.fastutil.ints.IntList; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.registry.Registry; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; import org.jetbrains.annotations.NotNull; @@ -76,7 +77,7 @@ public class FriendlyByteBuf extends ByteBuf { public > C readCollection(IntFunction collectionFactory, Reader reader) { int i = this.readVarInt(); C collection = collectionFactory.apply(i); - for(int j = 0; j < i; ++j) { + for (int j = 0; j < i; ++j) { collection.add(reader.apply(this)); } return collection; @@ -93,7 +94,7 @@ public class FriendlyByteBuf extends ByteBuf { public T[] readArray(Reader reader, Class type) { int i = this.readVarInt(); T[] array = (T[]) Array.newInstance(type, i); - for(int j = 0; j < i; ++j) { + for (int j = 0; j < i; ++j) { array[j] = reader.apply(this); } return array; @@ -101,7 +102,7 @@ public class FriendlyByteBuf extends ByteBuf { public void writeArray(T[] array, Writer writer) { this.writeVarInt(array.length); - for(T t : array) { + for (T t : array) { writer.accept(this, t); } } @@ -351,7 +352,7 @@ public class FriendlyByteBuf extends ByteBuf { } public long[] readFixedSizeLongArray(long[] output) { - for(int i = 0; i < output.length; ++i) { + for (int i = 0; i < output.length; ++i) { output[i] = this.readLong(); } return output; @@ -473,12 +474,12 @@ public class FriendlyByteBuf extends ByteBuf { public void writeHolderSet(Either, Key> holderSet) { holderSet.ifLeft( - ints -> { - writeVarInt(ints.size() + 1); - for (Integer anInt : ints) { - writeVarInt(anInt); + ints -> { + writeVarInt(ints.size() + 1); + for (Integer anInt : ints) { + writeVarInt(anInt); + } } - } ).ifRight(key -> { writeVarInt(0); writeKey(key); @@ -599,13 +600,57 @@ public class FriendlyByteBuf extends ByteBuf { @SuppressWarnings("unchecked") public > T readEnumConstant(Class enumClass) { - return (T)((Enum[])enumClass.getEnumConstants())[this.readVarInt()]; + return (T) ((Enum[]) enumClass.getEnumConstants())[this.readVarInt()]; } public FriendlyByteBuf writeEnumConstant(Enum instance) { return this.writeVarInt(instance.ordinal()); } + public Vec3d readLpVec3() { + int unsignedByte = this.readUnsignedByte(); + if (unsignedByte == 0) { + return Vec3d.ZERO; + } else { + int unsignedByte1 = this.readUnsignedByte(); + long unsignedInt = this.readUnsignedInt(); + long l = unsignedInt << 16 | (long) (unsignedByte1 << 8) | (long) unsignedByte; + long l1 = unsignedByte & 3; + if ((unsignedByte & 4) == 4) { + l1 |= ((long) this.readVarInt() & 4294967295L) << 2; + } + return new Vec3d( + (Math.min((double) ((l >> 3) & 32767L), (double) 32766.0F) * (double) 2.0F / (double) 32766.0F - (double) 1.0F) * (double) l1, + (Math.min((double) ((l >> 18) & 32767L), (double) 32766.0F) * (double) 2.0F / (double) 32766.0F - (double) 1.0F) * (double) l1, + (Math.min((double) ((l >> 33) & 32767L), (double) 32766.0F) * (double) 2.0F / (double) 32766.0F - (double) 1.0F) * (double) l1 + ); + } + } + + public void writeLpVec3(Vec3d vec3) { + double d = Double.isNaN(vec3.x) ? (double) 0.0F : Math.clamp(vec3.x, -1.7179869183E10, 1.7179869183E10); + double d1 = Double.isNaN(vec3.y) ? (double) 0.0F : Math.clamp(vec3.y, -1.7179869183E10, 1.7179869183E10); + double d2 = Double.isNaN(vec3.z) ? (double) 0.0F : Math.clamp(vec3.z, -1.7179869183E10, 1.7179869183E10); + double max = MCUtils.absMax(d, MCUtils.absMax(d1, d2)); + if (max < 3.051944088384301E-5) { + this.writeByte(0); + } else { + long l = MCUtils.ceilLong(max); + boolean flag = (l & 3L) != l; + long l1 = flag ? l & 3L | 4L : l; + long l2 = (Math.round(((d / (double) l) * (double) 0.5F + (double) 0.5F) * (double) 32766.0F)) << 3; + long l3 = (Math.round(((d1 / (double) l) * (double) 0.5F + (double) 0.5F) * (double) 32766.0F)) << 18; + long l4 = (Math.round(((d2 / (double) l) * (double) 0.5F + (double) 0.5F) * (double) 32766.0F)) << 33; + long l5 = l1 | l2 | l3 | l4; + this.writeByte((byte) ((int) l5)); + this.writeByte((byte) ((int) (l5 >> 8))); + this.writeInt((int) (l5 >> 16)); + if (flag) { + this.writeVarInt((int) (l >> 2)); + } + } + } + @FunctionalInterface public interface Writer extends BiConsumer { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java index 38cf3acdd..c78fc05d1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java @@ -282,4 +282,13 @@ public class MCUtils { public static double clamp(double value, double min, double max) { return value < min ? min : Math.min(value, max); } + + public static double absMax(double x, double y) { + return Math.max(Math.abs(x), Math.abs(y)); + } + + public static long ceilLong(double value) { + long l = (long)value; + return value > (double)l ? l + 1L : l; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java b/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java index 2e20c0a66..80fc10b38 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java @@ -22,6 +22,7 @@ public final class MinecraftVersion implements Comparable { PACK_FORMATS.put(1_21_06, 63); PACK_FORMATS.put(1_21_07, 64); PACK_FORMATS.put(1_21_08, 64); + PACK_FORMATS.put(1_21_09, 69); PACK_FORMATS.put(1_99_99, 1000); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersions.java b/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersions.java index 2f28c73ad..b9c6980cc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersions.java @@ -19,5 +19,6 @@ public final class MinecraftVersions { public static final MinecraftVersion V1_21_6 = new MinecraftVersion("1.21.6"); public static final MinecraftVersion V1_21_7 = new MinecraftVersion("1.21.7"); public static final MinecraftVersion V1_21_8 = new MinecraftVersion("1.21.8"); + public static final MinecraftVersion V1_21_9 = new MinecraftVersion("1.21.9"); public static final MinecraftVersion FUTURE = new MinecraftVersion("1.99.99"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java index 6b36f6bc3..2112ecd15 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java @@ -33,6 +33,7 @@ public class VersionHelper { private static final boolean v1_21_6; private static final boolean v1_21_7; private static final boolean v1_21_8; + private static final boolean v1_21_9; static { try (InputStream inputStream = Class.forName("net.minecraft.obfuscate.DontObfuscate").getResourceAsStream("/version.json")) { @@ -68,6 +69,7 @@ public class VersionHelper { v1_21_6 = version >= 12106; v1_21_7 = version >= 12107; v1_21_8 = version >= 12108; + v1_21_9 = version >= 12109; majorVersion = major; minorVersion = minor; @@ -239,4 +241,8 @@ public class VersionHelper { public static boolean isOrAbove1_21_8() { return v1_21_8; } + + public static boolean isOrAbove1_21_9() { + return v1_21_9; + } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java index aeb8b3df5..83ac823ba 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.util.MCUtils; public class Vec3d implements Position { + public static final Vec3d ZERO = new Vec3d(0, 0, 0); public final double x; public final double y; public final double z; diff --git a/gradle.properties b/gradle.properties index 1b25eea7b..6d228cad0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,13 +50,13 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.92 +nms_helper_version=1.0.93 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 amazon_awssdk_eventstream_version=1.0.1 jimfs_version=1.3.0 -authlib_version=6.0.58 +authlib_version=7.0.60 concurrent_util_version=0.0.3 # Proxy settings From 6c0f816e03306627ffa1888dc804081dc5c3de85 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Mon, 22 Sep 2025 12:26:59 +0800 Subject: [PATCH 167/226] =?UTF-8?q?=E8=A1=A5=E6=BC=8F=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/legacy/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bukkit/legacy/build.gradle.kts b/bukkit/legacy/build.gradle.kts index 2de912779..5703f4c6f 100644 --- a/bukkit/legacy/build.gradle.kts +++ b/bukkit/legacy/build.gradle.kts @@ -10,6 +10,8 @@ repositories { dependencies { // Platform compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT") + // authlib + compileOnly("com.mojang:authlib:6.0.58") } java { From de475746bd12e2b14d73c3ce31de4d1548e63461 Mon Sep 17 00:00:00 2001 From: iqtester <1835ww@gmail.com> Date: Mon, 22 Sep 2025 00:53:08 -0400 Subject: [PATCH 168/226] feat(BushBlockBehavior): added stackable-amount --- .../block/behavior/BushBlockBehavior.java | 30 +++++++++++++++++-- .../block/behavior/HangingBlockBehavior.java | 2 +- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java index f20eab56d..7cb60c452 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java @@ -26,11 +26,13 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { protected final Set customBlocksCansSurviveOn; protected final boolean blacklistMode; protected final boolean stackable; + protected final int stackableAmount; - public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { + public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, int stackableAmount, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { super(block, delay); this.blacklistMode = blacklist; this.stackable = stackable; + this.stackableAmount = stackableAmount; this.tagsCanSurviveOn = tagsCanSurviveOn; this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; @@ -42,9 +44,10 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { public BlockBehavior create(CustomBlock block, Map arguments) { Tuple, Set, Set> tuple = readTagsAndState(arguments, false); boolean stackable = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("stackable", false), "stackable"); + int stackableAmount = ResourceConfigUtils.getAsInt(arguments.getOrDefault("stackable-amount", 0), "stackable-amount"); int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist"); - return new BushBlockBehavior(block, delay, blacklistMode, stackable, tuple.left(), tuple.mid(), tuple.right()); + return new BushBlockBehavior(block, delay, blacklistMode, stackable, stackableAmount,tuple.left(), tuple.mid(), tuple.right()); } } @@ -96,7 +99,11 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { } else { ImmutableBlockState belowCustomState = optionalCustomState.get(); if (belowCustomState.owner().value() == super.customBlock) { - return this.stackable; + if (!stackable) return false; + if (this.stackableAmount > 0) { + return mayStackOn(world, belowPos); + } + return true; } if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) { return !this.blacklistMode; @@ -107,4 +114,21 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { } return this.blacklistMode; } + + protected boolean mayStackOn(Object world, Object belowPos) { + int count = 0; + Object cursorPos = belowPos; + + while (count <= this.stackableAmount) { + Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, cursorPos); + Optional belowCustomState = BlockStateUtils.getOptionalCustomBlockState(belowState); + if (belowCustomState.isPresent() && belowCustomState.get().owner().value() == super.customBlock) { + count++; + cursorPos = LocationUtils.below(cursorPos); + } else { + break; + } + } + return count <= this.stackableAmount; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java index 594962aca..06724bc9f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java @@ -16,7 +16,7 @@ public class HangingBlockBehavior extends BushBlockBehavior { public static final Factory FACTORY = new Factory(); public HangingBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn) { - super(block, delay, blacklist, stackable, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); + super(block, delay, blacklist, stackable, -1, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); } @Override From 4f96c9c25ac56eff4da3e5cf397e08e6c967c7ae Mon Sep 17 00:00:00 2001 From: iqtester <1835ww@gmail.com> Date: Mon, 22 Sep 2025 06:39:52 -0400 Subject: [PATCH 169/226] feat(BushBlockBehavior): max-height --- .../block/behavior/BushBlockBehavior.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java index 7cb60c452..bace95fe2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java @@ -26,13 +26,13 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { protected final Set customBlocksCansSurviveOn; protected final boolean blacklistMode; protected final boolean stackable; - protected final int stackableAmount; + protected final int maxHeight; - public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, int stackableAmount, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { + public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, int maxHeight, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { super(block, delay); this.blacklistMode = blacklist; this.stackable = stackable; - this.stackableAmount = stackableAmount; + this.maxHeight = maxHeight; this.tagsCanSurviveOn = tagsCanSurviveOn; this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; @@ -44,10 +44,10 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { public BlockBehavior create(CustomBlock block, Map arguments) { Tuple, Set, Set> tuple = readTagsAndState(arguments, false); boolean stackable = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("stackable", false), "stackable"); - int stackableAmount = ResourceConfigUtils.getAsInt(arguments.getOrDefault("stackable-amount", 0), "stackable-amount"); + int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 0), "max-height"); int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist"); - return new BushBlockBehavior(block, delay, blacklistMode, stackable, stackableAmount,tuple.left(), tuple.mid(), tuple.right()); + return new BushBlockBehavior(block, delay, blacklistMode, stackable, maxHeight,tuple.left(), tuple.mid(), tuple.right()); } } @@ -99,8 +99,8 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { } else { ImmutableBlockState belowCustomState = optionalCustomState.get(); if (belowCustomState.owner().value() == super.customBlock) { - if (!stackable) return false; - if (this.stackableAmount > 0) { + if (!this.stackable || this.maxHeight == 1) return false; + if (this.maxHeight > 1) { return mayStackOn(world, belowPos); } return true; @@ -116,10 +116,10 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { } protected boolean mayStackOn(Object world, Object belowPos) { - int count = 0; - Object cursorPos = belowPos; + int count = 1; + Object cursorPos = LocationUtils.below(belowPos); - while (count <= this.stackableAmount) { + while (count < this.maxHeight) { Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, cursorPos); Optional belowCustomState = BlockStateUtils.getOptionalCustomBlockState(belowState); if (belowCustomState.isPresent() && belowCustomState.get().owner().value() == super.customBlock) { @@ -129,6 +129,6 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { break; } } - return count <= this.stackableAmount; + return count < this.maxHeight; } } From d787f0bdfa807e973ca05c5929ef651f45315022 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 23 Sep 2025 11:28:58 +0800 Subject: [PATCH 170/226] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=93=9C=E8=97=A4=E7=9B=B8=E5=85=B3=E7=9A=84=E6=96=B9=E5=9D=97?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E5=B9=B6=E4=B8=94=E4=BF=AE=E5=A4=8D=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E6=96=B9=E5=9D=97=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/AttachedStemBlockBehavior.java | 110 +++++++++++++ .../block/behavior/BukkitBlockBehaviors.java | 4 + .../block/behavior/CropBlockBehavior.java | 2 +- .../block/behavior/StemBlockBehavior.java | 154 ++++++++++++++++++ .../UnsafeCompositeBlockBehavior.java | 12 +- .../plugin/injector/BlockGenerator.java | 18 ++ .../plugin/injector/BlockStateGenerator.java | 74 +++++---- .../reflection/minecraft/CoreReflections.java | 10 ++ .../plugin/reflection/minecraft/MBlocks.java | 67 +++----- .../plugin/reflection/minecraft/MTagKeys.java | 1 + .../default/configuration/templates.yml | 14 +- .../src/main/resources/translations/en.yml | 6 + .../src/main/resources/translations/zh_cn.yml | 6 + .../craftengine/core/block/BlockBehavior.java | 8 +- gradle.properties | 4 +- 15 files changed, 399 insertions(+), 91 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java new file mode 100644 index 000000000..1e30aafb9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java @@ -0,0 +1,110 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.IntegerProperty; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.*; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; + +public class AttachedStemBlockBehavior extends BushBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property facingProperty; + private final Key fruit; + private final Key stem; + + public AttachedStemBlockBehavior(CustomBlock customBlock, + int delay, + boolean blacklist, + List tagsCanSurviveOn, + Set blockStatesCanSurviveOn, + Set customBlocksCansSurviveOn, + Property facingProperty, + Key fruit, + Key stem) { + super(customBlock, delay, blacklist, false, tagsCanSurviveOn, blockStatesCanSurviveOn, customBlocksCansSurviveOn); + this.facingProperty = facingProperty; + this.fruit = fruit; + this.stem = stem; + } + + @Override + public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) { + return FastNMS.INSTANCE.field$FluidState$isEmpty(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(args[0])); + } + + @Override + public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + return (VersionHelper.isOrAbove1_20_5() ? args[1] : args[3]).equals(CoreReflections.instance$PathComputationType$AIR) + && !FastNMS.INSTANCE.field$BlockBehavior$hasCollision(thisBlock) || (boolean) superMethod.call(); + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object state = args[0]; + HorizontalDirection direction = DirectionUtils.fromNMSDirection(args[updateShape$direction]).toHorizontalDirection(); + Object neighborState = args[updateShape$neighborState]; + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); + if (optionalCustomState.isEmpty() || direction != optionalCustomState.get().get(this.facingProperty)) { + return super.updateShape(thisBlock, args, superMethod); + } + Optional optionalCustomNeighborState = BlockStateUtils.getOptionalCustomBlockState(neighborState); + if (optionalCustomNeighborState.isPresent()) { + ImmutableBlockState customNeighborState = optionalCustomNeighborState.get(); + if (!customNeighborState.owner().value().id().equals(this.fruit)) { + Object stemBlock = resetStemBlock(); + if (stemBlock != null) return stemBlock; + } + } else { + if (this.stem.namespace().equals("minecraft")) { + Key neighborBlockId = BlockStateUtils.getBlockOwnerIdFromState(neighborState); + if (!neighborBlockId.equals(this.fruit)) { + Object stemBlock = resetStemBlock(); + if (stemBlock != null) return stemBlock; + } + } else { + Object stemBlock = resetStemBlock(); + if (stemBlock != null) return stemBlock; + } + } + return super.updateShape(thisBlock, args, superMethod); + } + + private Object resetStemBlock() { + Optional optionalStemBlock = BukkitBlockManager.instance().blockById(this.stem); + if (optionalStemBlock.isPresent()) { + CustomBlock stemBlock = optionalStemBlock.get(); + IntegerProperty ageProperty = (IntegerProperty) stemBlock.getProperty("age"); + if (ageProperty == null) return stemBlock.defaultState().customBlockState().literalObject(); + return stemBlock.defaultState().with(ageProperty, ageProperty.max).customBlockState().literalObject(); + } + return null; + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Tuple, Set, Set> tuple = readTagsAndState(arguments, false); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist"); + @SuppressWarnings("unchecked") + Property facingProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.attached_stem.missing_facing"); + Key fruit = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("fruit"), "warning.config.block.behavior.attached_stem.missing_fruit")); + Key stem = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("stem"), "warning.config.block.behavior.attached_stem.missing_stem")); + return new AttachedStemBlockBehavior(block, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), facingProperty, fruit, stem); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index fc8ba1cc6..ce5a59520 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -39,6 +39,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key FENCE_BLOCK = Key.from("craftengine:fence_block"); public static final Key BUTTON_BLOCK = Key.from("craftengine:button_block"); public static final Key FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK = Key.from("craftengine:face_attached_horizontal_directional_block"); + public static final Key STEM_BLOCK = Key.from("craftengine:stem_block"); + public static final Key ATTACHED_STEM_BLOCK = Key.from("craftengine:attached_stem_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -76,5 +78,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(FENCE_BLOCK, FenceBlockBehavior.FACTORY); register(BUTTON_BLOCK, ButtonBlockBehavior.FACTORY); register(FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK, FaceAttachedHorizontalDirectionalBlockBehavior.FACTORY); + register(STEM_BLOCK, StemBlockBehavior.FACTORY); + register(ATTACHED_STEM_BLOCK, AttachedStemBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 7e0746ead..2e6793e35 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -77,7 +77,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { return minGrowLight; } - private static int getRawBrightness(Object level, Object pos) throws InvocationTargetException, IllegalAccessException { + public static int getRawBrightness(Object level, Object pos) throws InvocationTargetException, IllegalAccessException { return (int) CoreReflections.method$BlockAndTintGetter$getRawBrightness.invoke(level, pos, 0); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java new file mode 100644 index 000000000..d7d1180c3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java @@ -0,0 +1,154 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.IntegerProperty; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.*; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; + +public class StemBlockBehavior extends BushBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final IntegerProperty ageProperty; + private final Key fruit; + private final Key attachedStem; + private final int minGrowLight; + private final Object tagMayPlaceFruit; + private final Object blockMayPlaceFruit; + + public StemBlockBehavior(CustomBlock customBlock, + int delay, + boolean blacklist, + List tagsCanSurviveOn, + Set blockStatesCanSurviveOn, + Set customBlocksCansSurviveOn, + IntegerProperty ageProperty, + Key fruit, + Key attachedStem, + int minGrowLight, + Object tagMayPlaceFruit, + Object blockMayPlaceFruit) { + super(customBlock, delay, blacklist, false, tagsCanSurviveOn, blockStatesCanSurviveOn, customBlocksCansSurviveOn); + this.ageProperty = ageProperty; + this.fruit = fruit; + this.attachedStem = attachedStem; + this.minGrowLight = minGrowLight; + this.tagMayPlaceFruit = tagMayPlaceFruit; + this.blockMayPlaceFruit = blockMayPlaceFruit; + } + + @Override + public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) { + return FastNMS.INSTANCE.field$FluidState$isEmpty(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(args[0])); + } + + @Override + public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + return (VersionHelper.isOrAbove1_20_5() ? args[1] : args[3]).equals(CoreReflections.instance$PathComputationType$AIR) + && !FastNMS.INSTANCE.field$BlockBehavior$hasCollision(thisBlock) || (boolean) superMethod.call(); + } + + @Override + public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + if (CropBlockBehavior.getRawBrightness(level, pos) < this.minGrowLight) return; + ImmutableBlockState customState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (customState == null || customState.isEmpty()) return; + int age = customState.get(ageProperty); + if (age < ageProperty.max) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, customState.with(ageProperty, age + 1).customBlockState().literalObject(), 2); + return; + } + Object randomDirection = CoreReflections.instance$Direction$values[RandomUtils.generateRandomInt(2, 6)]; + Object blockPos = FastNMS.INSTANCE.method$BlockPos$relative(pos, randomDirection); + if (!FastNMS.INSTANCE.method$BlockStateBase$isAir(FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, blockPos))) + return; + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, FastNMS.INSTANCE.method$BlockPos$relative(blockPos, CoreReflections.instance$Direction$DOWN)); + if (mayPlaceFruit(blockState)) { + Optional optionalFruit = BukkitBlockManager.instance().blockById(this.fruit); + Object fruitState = null; + if (optionalFruit.isPresent()) { + fruitState = optionalFruit.get().defaultState().customBlockState().literalObject(); + } else if (fruit.namespace().equals("minecraft")) { + fruitState = FastNMS.INSTANCE.method$Block$defaultState(FastNMS.INSTANCE.method$Registry$getValue( + MBuiltInRegistries.BLOCK, + FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", fruit.value()) + )); + } + Optional optionalAttachedStem = BukkitBlockManager.instance().blockById(this.attachedStem); + if (fruitState == null || optionalAttachedStem.isEmpty()) return; + CustomBlock attachedStem = optionalAttachedStem.get(); + @SuppressWarnings("unchecked") + Property facing = (Property) attachedStem.getProperty("facing"); + if (facing == null) return; + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, fruitState, UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, attachedStem.defaultState().with(facing, DirectionUtils.fromNMSDirection(randomDirection).toHorizontalDirection()).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + } + } + + @Override + public boolean isValidBoneMealTarget(Object thisBlock, Object[] args) { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[2]).orElse(null); + if (state == null || state.isEmpty()) return false; + return state.get(ageProperty) != ageProperty.max; + } + + @Override + public boolean isBoneMealSuccess(Object thisBlock, Object[] args) { + return true; + } + + @Override + public void performBoneMeal(Object thisBlock, Object[] args) { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[3]).orElse(null); + if (state == null || state.isEmpty()) return; + int min = Math.min(7, state.get(ageProperty) + RandomUtils.generateRandomInt(Math.min(ageProperty.min + 2, ageProperty.max), Math.min(ageProperty.max - 2, ageProperty.max))); + Object blockState = state.with(ageProperty, min).customBlockState().literalObject(); + FastNMS.INSTANCE.method$LevelWriter$setBlock(args[0], args[2], blockState, 2); + if (min >= ageProperty.max) { + FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$randomTick(blockState, args[0], args[2]); + } + } + + private boolean mayPlaceFruit(Object blockState) { + boolean flag1 = tagMayPlaceFruit != null && FastNMS.INSTANCE.method$BlockStateBase$is(blockState, tagMayPlaceFruit); + boolean flag2 = blockMayPlaceFruit != null && FastNMS.INSTANCE.method$BlockStateBase$isBlock(blockState, blockMayPlaceFruit); + if (tagMayPlaceFruit == null && blockMayPlaceFruit == null) return true; + return flag1 || flag2; + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Tuple, Set, Set> tuple = readTagsAndState(arguments, false); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist"); + IntegerProperty ageProperty = (IntegerProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.stem.missing_age"); + Key fruit = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("fruit"), "warning.config.block.behavior.stem.missing_fruit")); + Key attachedStem = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("attached-stem"), "warning.config.block.behavior.stem.missing_attached_stem")); + int minGrowLight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("light-requirement", 9), "light-requirement"); + Object tagMayPlaceFruit = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("may-place-fruit", "minecraft:dirt").toString()))); + Object blockMayPlaceFruit = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("may-place-fruit", "minecraft:farmland").toString()))); + return new StemBlockBehavior(block, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), ageProperty, fruit, attachedStem, minGrowLight, tagMayPlaceFruit, blockMayPlaceFruit); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 2ab839d6b..8e9e4c631 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -381,4 +381,14 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior } FallOnBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); } -} \ No newline at end of file + + @Override + public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + if (!behavior.propagatesSkylightDown(thisBlock, args, superMethod)) { + return false; + } + } + return (boolean) superMethod.call(); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index ae5e8c670..d54835999 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -170,6 +170,9 @@ public final class BlockGenerator { // stepOn .method(ElementMatchers.is(CoreReflections.method$Block$stepOn)) .intercept(MethodDelegation.to(StepOnInterceptor.INSTANCE)) + // propagatesSkylightDown + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$propagatesSkylightDown)) + .intercept(MethodDelegation.to(PropagatesSkylightDownInterceptor.INSTANCE)) ; // 1.21.5+ if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) { @@ -760,4 +763,19 @@ public final class BlockGenerator { } } } + + public static class PropagatesSkylightDownInterceptor { + public static final PropagatesSkylightDownInterceptor INSTANCE = new PropagatesSkylightDownInterceptor(); + + @RuntimeType + public boolean intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + return holder.value().propagatesSkylightDown(thisObj, args, superMethod); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run propagatesSkylightDown", e); + return false; + } + } + } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java index 148fa9e3d..03d0d98b0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java @@ -12,7 +12,6 @@ import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.SuperCall; import net.bytebuddy.implementation.bind.annotation.This; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; @@ -23,15 +22,16 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlockState import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MLootContextParams; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.block.*; +import net.momirealms.craftengine.core.block.BlockSettings; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.DelegatingBlockState; +import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.util.ObjectHolder; +import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.World; @@ -42,8 +42,6 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.List; -import java.util.Optional; -import java.util.concurrent.Callable; public final class BlockStateGenerator { private static MethodHandle constructor$CraftEngineBlockState; @@ -87,11 +85,11 @@ public final class BlockStateGenerator { Class clazz$CraftEngineBlock = stateBuilder.make().load(BlockStateGenerator.class.getClassLoader()).getLoaded(); constructor$CraftEngineBlockState = VersionHelper.isOrAbove1_20_5() ? MethodHandles.publicLookup().in(clazz$CraftEngineBlock) - .findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class)) - .asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class)) : + .findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class)) + .asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, Reference2ObjectArrayMap.class, MapCodec.class)) : MethodHandles.publicLookup().in(clazz$CraftEngineBlock) - .findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class)) - .asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class)); + .findConstructor(clazz$CraftEngineBlock, MethodType.methodType(void.class, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class)) + .asType(MethodType.methodType(CoreReflections.clazz$BlockState, CoreReflections.clazz$Block, ImmutableMap.class, MapCodec.class)); String generatedFactoryClassName = packageWithName.substring(0, packageWithName.lastIndexOf('.')) + ".CraftEngineStateFactory"; DynamicType.Builder factoryBuilder = byteBuddy @@ -210,10 +208,12 @@ public final class BlockStateGenerator { DelegatingBlockState customState = (DelegatingBlockState) thisObj; ImmutableBlockState thisState = customState.blockState(); if (thisState == null) return false; - if (!(args[0] instanceof DelegatingBlock delegatingBlock)) return false; - BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); - if (behavior == null) return false; - return behavior.block().equals(thisState.owner().value()); + if (FastNMS.INSTANCE.method$Block$defaultState(args[0]) instanceof DelegatingBlockState holder) { + ImmutableBlockState holderState = holder.blockState(); + if (holderState == null) return false; + return holderState.owner().equals(thisState.owner()); + } + return false; } } @@ -226,13 +226,15 @@ public final class BlockStateGenerator { DelegatingBlockState customState = (DelegatingBlockState) thisObj; ImmutableBlockState thisState = customState.blockState(); if (thisState == null) return false; - CustomBlock thisBlock = thisState.owner().value(); + Holder owner = thisState.owner(); for (Object holder : (Iterable) args[0]) { Object block = FastNMS.INSTANCE.method$Holder$value(holder); - if (!(block instanceof DelegatingBlock delegatingBlock)) continue; - BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); - if (behavior == null) continue; - if (behavior.block().equals(thisBlock)) return true; + if (block == null) continue; + if (!(FastNMS.INSTANCE.method$Block$defaultState(block) instanceof DelegatingBlockState customHolder)) + continue; + ImmutableBlockState holderState = customHolder.blockState(); + if (holderState == null) continue; + return holderState.owner().equals(owner); } return false; } @@ -243,15 +245,17 @@ public final class BlockStateGenerator { @RuntimeType public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - Object block = FastNMS.INSTANCE.method$Holder$value(args[0]); - if (!(block instanceof DelegatingBlock delegatingBlock)) return false; - BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); - if (behavior == null) return false; DelegatingBlockState customState = (DelegatingBlockState) thisObj; ImmutableBlockState thisState = customState.blockState(); if (thisState == null) return false; - CustomBlock thisBlock = thisState.owner().value(); - return behavior.block().equals(thisBlock); + Object block = FastNMS.INSTANCE.method$Holder$value(args[0]); + if (block == null) return false; + if (FastNMS.INSTANCE.method$Block$defaultState(block) instanceof DelegatingBlockState holder) { + ImmutableBlockState holderState = holder.blockState(); + if (holderState == null) return false; + return holderState.owner().equals(thisState.owner()); + } + return false; } } @@ -260,17 +264,19 @@ public final class BlockStateGenerator { @RuntimeType public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - Object block = FastNMS.INSTANCE.method$HolderGetter$getResourceKey(MBuiltInRegistries.BLOCK, args[0]) - .map(FastNMS.INSTANCE::method$Holder$value) - .orElse(null); - if (!(block instanceof DelegatingBlock delegatingBlock)) return false; - BlockBehavior behavior = delegatingBlock.behaviorDelegate().value(); - if (behavior == null) return false; DelegatingBlockState customState = (DelegatingBlockState) thisObj; ImmutableBlockState thisState = customState.blockState(); if (thisState == null) return false; - CustomBlock thisBlock = thisState.owner().value(); - return behavior.block().equals(thisBlock); + Object block = FastNMS.INSTANCE.method$HolderGetter$getResourceKey(MBuiltInRegistries.BLOCK, args[0]) + .map(FastNMS.INSTANCE::method$Holder$value) + .orElse(null); + if (block == null) return false; + if (FastNMS.INSTANCE.method$Block$defaultState(block) instanceof DelegatingBlockState holder) { + ImmutableBlockState holderState = holder.blockState(); + if (holderState == null) return false; + return holderState.owner().equals(thisState.owner()); + } + return false; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 65a0549f6..345f5ab28 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4434,4 +4434,14 @@ public final class CoreReflections { ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$ResourceKey), VersionHelper.isOrAbove1_20_3() ); + + public static final Method method$BlockBehaviour$propagatesSkylightDown = requireNonNull( + VersionHelper.isOrAbove1_21_2() + ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "e_"}, clazz$BlockState) + : VersionHelper.isOrAbove1_20_5() + ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) + : VersionHelper.isOrAbove1_20_4() + ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) + : ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "c"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java index d9c489fba..c08c66559 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java @@ -6,54 +6,31 @@ import net.momirealms.craftengine.core.util.VersionHelper; public final class MBlocks { private MBlocks() {} - public static final Object AIR; - public static final Object AIR$defaultState; - public static final Object STONE; - public static final Object STONE$defaultState; - public static final Object FIRE; - public static final Object SOUL_FIRE; - public static final Object ICE; - public static final Object SHORT_GRASS; - public static final Object SHORT_GRASS$defaultState; - public static final Object SHULKER_BOX; - public static final Object COMPOSTER; - public static final Object SNOW; - public static final Object WATER; - public static final Object WATER$defaultState; - public static final Object TNT; - public static final Object TNT$defaultState; - public static final Object BARRIER; - public static final Object CARVED_PUMPKIN; - public static final Object JACK_O_LANTERN; - public static final Object MELON; - public static final Object PUMPKIN; + public static final Object AIR = getById("air");; + public static final Object AIR$defaultState = FastNMS.INSTANCE.method$Block$defaultState(AIR); + public static final Object STONE = getById("stone"); + public static final Object STONE$defaultState = FastNMS.INSTANCE.method$Block$defaultState(STONE); + public static final Object FIRE = getById("fire"); + public static final Object SOUL_FIRE = getById("soul_fire"); + public static final Object ICE = getById("ice"); + public static final Object SHORT_GRASS = getById(VersionHelper.isOrAbove1_20_3() ? "short_grass" : "grass"); + public static final Object SHORT_GRASS$defaultState = FastNMS.INSTANCE.method$Block$defaultState(SHORT_GRASS); + public static final Object SHULKER_BOX = getById("shulker_box"); + public static final Object COMPOSTER = getById("composter"); + public static final Object SNOW = getById("snow"); + public static final Object WATER = getById("water"); + public static final Object WATER$defaultState = FastNMS.INSTANCE.method$Block$defaultState(WATER); + public static final Object TNT = getById("tnt"); + public static final Object TNT$defaultState = FastNMS.INSTANCE.method$Block$defaultState(TNT); + public static final Object BARRIER = getById("barrier"); + public static final Object CARVED_PUMPKIN = getById("carved_pumpkin"); + public static final Object JACK_O_LANTERN = getById("jack_o_lantern"); + public static final Object MELON = getById("melon"); + public static final Object PUMPKIN = getById("pumpkin"); + public static final Object FARMLAND = getById("farmland"); private static Object getById(String id) { Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id); return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, rl); } - - static { - AIR = getById("air"); - AIR$defaultState = FastNMS.INSTANCE.method$Block$defaultState(AIR); - FIRE = getById("fire"); - SOUL_FIRE = getById("soul_fire"); - STONE = getById("stone"); - STONE$defaultState = FastNMS.INSTANCE.method$Block$defaultState(STONE); - ICE = getById("ice"); - SHORT_GRASS = getById(VersionHelper.isOrAbove1_20_3() ? "short_grass" : "grass"); - SHORT_GRASS$defaultState = FastNMS.INSTANCE.method$Block$defaultState(SHORT_GRASS); - SHULKER_BOX = getById("shulker_box"); - COMPOSTER = getById("composter"); - SNOW = getById("snow"); - WATER = getById("water"); - WATER$defaultState = FastNMS.INSTANCE.method$Block$defaultState(WATER); - TNT = getById("tnt"); - TNT$defaultState = FastNMS.INSTANCE.method$Block$defaultState(TNT); - BARRIER = getById("barrier"); - CARVED_PUMPKIN = getById("carved_pumpkin"); - JACK_O_LANTERN = getById("jack_o_lantern"); - MELON = getById("melon"); - PUMPKIN = getById("pumpkin"); - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java index fe3440027..3f444599e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKeys.java @@ -12,6 +12,7 @@ public final class MTagKeys { public static final Object Block$SHULKER_BOXES = create(MRegistries.BLOCK, "shulker_boxes"); public static final Object Block$FENCES = create(MRegistries.BLOCK, "fences"); public static final Object Block$WOODEN_FENCES = create(MRegistries.BLOCK, "wooden_fences"); + public static final Object Block$DIRT = create(MRegistries.BLOCK, "dirt"); private static Object create(Object registry, String location) { Object resourceLocation = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", location); diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index e2c881a6c..8e2458b32 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -48,13 +48,13 @@ templates#models#block: generation: parent: minecraft:block/cube_column textures: - particle: minecraft:block/custom/block_particle - down: minecraft:block/custom/block_down - up: minecraft:block/custom/block_up - north: minecraft:block/custom/block_north - east: minecraft:block/custom/block_east - south: minecraft:block/custom/block_south - west: minecraft:block/custom/block_west + particle: ${particle_texture} + down: ${down_texture} + up: ${up_texture} + north: ${north_texture} + east: ${east_texture} + south: ${south_texture} + west: ${west_texture} # 2D items templates#models#2d: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index e2ea5b94a..c12ee18f5 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -328,6 +328,12 @@ warning.config.block.behavior.fence.missing_west: "Issue found in file < warning.config.block.behavior.face_attached_horizontal_directional.missing_face: "Issue found in file - The block '' is missing the required 'face' property for 'face_attached_horizontal_directional_block' behavior." warning.config.block.behavior.face_attached_horizontal_directional.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'face_attached_horizontal_directional_block' behavior." warning.config.block.behavior.button.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'button_block' behavior." +warning.config.block.behavior.stem.missing_age: "Issue found in file - The block '' is missing the required 'age' property for 'stem_block' behavior." +warning.config.block.behavior.stem.missing_fruit: "Issue found in file - The block '' is missing the required 'fruit' argument for 'stem_block' behavior." +warning.config.block.behavior.stem.missing_attached_stem: "Issue found in file - The block '' is missing the required 'attached_stem' argument for 'stem_block' behavior." +warning.config.block.behavior.attached_stem.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'attached_stem_block' behavior." +warning.config.block.behavior.attached_stem.missing_fruit: "Issue found in file - The block '' is missing the required 'fruit' argument for 'attached_stem_block' behavior." +warning.config.block.behavior.attached_stem.missing_stem: "Issue found in file - The block '' is missing the required 'stem' argument for 'attached_stem_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index add5680d3..462138c04 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -322,6 +322,12 @@ warning.config.block.behavior.fence.missing_west: "在文件 发 warning.config.block.behavior.face_attached_horizontal_directional.missing_face: "在文件 发现问题 - 方块 '' 的 'face_attached_horizontal_directional_block' 行为缺少必需的 'face' 属性" warning.config.block.behavior.face_attached_horizontal_directional.missing_facing: "在文件 发现问题 - 方块 '' 的 'face_attached_horizontal_directional_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.button.missing_powered: "在文件 发现问题 - 方块 '' 的 'button_block' 行为缺少必需的 'powered' 属性" +warning.config.block.behavior.stem.missing_age: "在文件 发现问题 - 方块 '' 的 'stem_block' 行为缺少必需的 'age' 属性" +warning.config.block.behavior.stem.missing_fruit: "在文件 发现问题 - 方块 '' 的 'stem_block' 行为缺少必需的 'fruit' 选项" +warning.config.block.behavior.stem.missing_attached_stem: "在文件 发现问题 - 方块 '' 的 'stem_block' 行为缺少必需的 'attached_stem' 选项" +warning.config.block.behavior.attached_stem.missing_facing: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'facing' 属性" +warning.config.block.behavior.attached_stem.missing_fruit: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'fruit' 选项" +warning.config.block.behavior.attached_stem.missing_stem: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'stem' 选项" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index d81554d1a..d3b82a4cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -184,6 +184,12 @@ public abstract class BlockBehavior { superMethod.call(); } + // 1.20.1~1.21.1 BlockState state, BlockGetter level, BlockPos pos + // 1.21.2+ BlockState state + public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + return (boolean) superMethod.call(); + } + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { return state; } @@ -217,4 +223,4 @@ public abstract class BlockBehavior { } public abstract CustomBlock block(); -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 6d228cad0..1aa561ed6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Rule: [major update].[feature update].[bug fix] project_version=0.0.63.4 config_version=46 -lang_version=30 +lang_version=31 project_group=net.momirealms latest_supported_version=1.21.8 @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.93 +nms_helper_version=1.0.94 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From ea100a992d13d4d84f9c04e00119623e7a78c40f Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 23 Sep 2025 13:05:50 +0800 Subject: [PATCH 171/226] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=92=8C=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E9=9A=8F=E6=9C=BA=E6=95=B0=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../default/configuration/blocks/honeydew.yml | 294 ++++++++++++++++++ .../default/configuration/categories.yml | 4 +- .../resources/default/configuration/i18n.yml | 12 +- .../textures/block/custom/honeydew.png | Bin 0 -> 463 bytes .../textures/block/custom/honeydew_bottom.png | Bin 0 -> 420 bytes .../textures/block/custom/honeydew_top.png | Bin 0 -> 493 bytes .../textures/item/custom/honeydew_item.png | Bin 0 -> 377 bytes .../src/main/resources/translations/en.yml | 2 + .../src/main/resources/translations/zh_cn.yml | 2 + .../core/pack/AbstractPackManager.java | 5 + .../number/BinomialNumberProvider.java | 51 +++ .../context/number/NumberProviders.java | 2 + 12 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_top.png create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/honeydew_item.png create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/BinomialNumberProvider.java diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml new file mode 100644 index 000000000..12d8f5598 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml @@ -0,0 +1,294 @@ +items: + default:honeydew_item: + material: apple + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/honeydew_item + behavior: + type: block_item + block: default:honeydew_stem + default:honeydew: + material: nether_brick + data: + item-name: + model: + path: minecraft:item/custom/honeydew + generation: + parent: minecraft:block/custom/honeydew + behavior: + type: block_item + block: default:honeydew + +blocks: + default:honeydew: + loot: + pools: + - rolls: 1 + entries: + - type: alternatives + children: + - type: item + item: default:honeydew + conditions: + - type: enchantment + predicate: minecraft:silk_touch>=1 + - type: item + item: default:honeydew_item + functions: + - type: apply_bonus + enchantment: minecraft:fortune + formula: + type: ore_drops + - type: set_count + add: false + count: 3~7 + - type: explosion_decay + settings: + map-color: 19 + hardness: 1 + resistance: 1 + push-reaction: DESTROY + is-suffocating: true + is-redstone-conductor: true + item: default:honeydew + tags: + - minecraft:enderman_holdable + - minecraft:mineable/axe + - minecraft:sword_efficient + incorrect-tool-dig-speed: 1 + state: + id: 30 + state: note_block:30 + model: + template: default:model/cube + arguments: + model: minecraft:block/custom/honeydew + particle_texture: minecraft:block/custom/honeydew + down_texture: minecraft:block/custom/honeydew_bottom + up_texture: minecraft:block/custom/honeydew_top + north_texture: minecraft:block/custom/honeydew + east_texture: minecraft:block/custom/honeydew + south_texture: minecraft:block/custom/honeydew + west_texture: minecraft:block/custom/honeydew + default:honeydew_stem: + loot: + pools: + - rolls: 1 + entries: + - type: item + item: default:honeydew_item + functions: + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 0 + count: + type: binomial + extra: 3 + probability: 0.06666667 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 1 + count: + type: binomial + extra: 3 + probability: 0.13333334 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 2 + count: + type: binomial + extra: 3 + probability: 0.2 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 3 + count: + type: binomial + extra: 3 + probability: 0.26666668 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 4 + count: + type: binomial + extra: 3 + probability: 0.33333334 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 5 + count: + type: binomial + extra: 3 + probability: 0.4 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 6 + count: + type: binomial + extra: 3 + probability: 0.46666667 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 7 + count: + type: binomial + extra: 3 + probability: 0.53333336 + functions: + - type: explosion_decay + settings: + map-color: 7 + hardness: 0 + resistance: 0 + push-reaction: DESTROY + is-suffocating: false + is-redstone-conductor: false + item: default:honeydew_item + is-randomly-ticking: true + tags: + - minecraft:bee_growables + - minecraft:crops + - minecraft:maintains_farmland + behaviors: + type: stem_block + fruit: default:honeydew + attached-stem: default:attached_honeydew_stem + blacklist: false + bottom-blocks: + - minecraft:farmland + states: + properties: + age: + type: int + default: 0 + range: 0~7 + appearances: + age=0: + state: pumpkin_stem[age=0] + age=1: + state: pumpkin_stem[age=1] + age=2: + state: pumpkin_stem[age=2] + age=3: + state: pumpkin_stem[age=3] + age=4: + state: pumpkin_stem[age=4] + age=5: + state: pumpkin_stem[age=5] + age=6: + state: pumpkin_stem[age=6] + age=7: + state: pumpkin_stem[age=7] + variants: + age=0: + appearance: age=0 + id: 0 + age=1: + appearance: age=1 + id: 1 + age=2: + appearance: age=2 + id: 2 + age=3: + appearance: age=3 + id: 3 + age=4: + appearance: age=4 + id: 4 + age=5: + appearance: age=5 + id: 5 + age=6: + appearance: age=6 + id: 6 + age=7: + appearance: age=7 + id: 7 + default:attached_honeydew_stem: + loot: + pools: + - rolls: 1 + entries: + - type: item + item: default:honeydew_item + functions: + - type: set_count + add: false + count: + type: binomial + extra: 3 + probability: 0.53333336 + functions: + - type: explosion_decay + settings: + map-color: 7 + hardness: 0 + resistance: 0 + push-reaction: DESTROY + is-suffocating: false + is-redstone-conductor: false + item: default:honeydew_item + is-randomly-ticking: true + tags: + - minecraft:maintains_farmland + behaviors: + type: attached_stem_block + fruit: default:honeydew + stem: default:honeydew_stem + blacklist: false + bottom-blocks: + - minecraft:farmland + states: + properties: + facing: + type: horizontal_direction + default: north + appearances: + facing=east: + state: attached_pumpkin_stem[facing=east] + facing=south: + state: attached_pumpkin_stem[facing=south] + facing=west: + state: attached_pumpkin_stem[facing=west] + facing=north: + state: attached_pumpkin_stem[facing=north] + variants: + facing=east: + appearance: facing=east + id: 0 + facing=south: + appearance: facing=south + id: 1 + facing=west: + appearance: facing=west + id: 2 + facing=north: + appearance: facing=north + id: 3 diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 55610a412..6f02cfd78 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -78,4 +78,6 @@ categories: - default:bench - default:wooden_chair - default:flower_basket - - default:amethyst_torch \ No newline at end of file + - default:amethyst_torch + - default:honeydew_item + - default:honeydew \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index fa91ebbb0..fbd75318d 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -49,6 +49,8 @@ i18n: item.safe_block: Safe Block item.sofa: Sofa item.amethyst_torch: Amethyst Torch + item.honeydew_item: Honeydew Slice + item.honeydew: Honeydew category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -107,6 +109,8 @@ i18n: item.safe_block: 保险柜 item.sofa: 沙发 item.amethyst_torch: 紫水晶火把 + item.honeydew_item: 哈密瓜片 + item.honeydew: 哈密瓜 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -149,6 +153,9 @@ lang: block_name:default:sofa: Sofa block_name:default:amethyst_torch: Amethyst Torch block_name:default:amethyst_wall_torch: Amethyst Torch + block_name:default:honeydew: Honeydew + block_name:default:honeydew_stem: Honeydew Stem + block_name:default:default:attached_honeydew_stem: Honeydew Stem zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -178,4 +185,7 @@ lang: block_name:default:sleeper_sofa: 沙发 block_name:default:sofa: 沙发 block_name:default:amethyst_torch: 紫水晶火把 - block_name:default:amethyst_wall_torch: 紫水晶火把 \ No newline at end of file + block_name:default:amethyst_wall_torch: 紫水晶火把 + block_name:default:honeydew: 哈密瓜 + block_name:default:honeydew_stem: 哈密瓜茎 + block_name:default:default:attached_honeydew_stem: 哈密瓜茎 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png new file mode 100644 index 0000000000000000000000000000000000000000..df74b134bb10be3dbd46afc1bf2d9c71129a8fc8 GIT binary patch literal 463 zcmV;=0WkiFP)Px$iAh93R49>6k-=(%KoEw1MJU0NV_kYE;=xN1qz@3tNuMRqLLfdx0);+EPd&s_ zArBA&O9*&KB!sZ`C|$%$?BTbIdm5dY@Be4Udi_kYI9RJH6&`?L03a3KFpx?+k#O<< znCr%)yeOs8CMA^y2&x?Nq|#CyvpDF+qyGvdq2B>1JfJdAo7BbRne5eOpBLp~@*D}* zs++thjmd62l1e+2OU6Dcj0o{iz568eso-#+4oE^Db+c9#6q3+iZT69H&Cmeyq6A=5 zi|S0I!lNr=Ln>|64MiL}F_^w+r-3pFeNwnCt8OCU+FXd)G%+;8NruLx(l&ths@W!$ zUTyYP-LN7rN~>;$*92f^abV0u!p-7fxjPQ-g;aQ{@Rqw{d)1L}`#Z3XU?QZ_3`}XS zAeFv*bla;wU0QlutE#=~w+D}I0et*a)7yPNgYoeG@-%evZU6Fr76+$G%eE{IY~y&o zJu`TF@aEg|^mb2?+1J6aW?u)J-R7Ck`S$$rQw>>6{Q*}Z5xyxv;}ZY?002ovPDHLk FV1g!V(KrAA literal 0 HcmV?d00001 diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..a21c39cec5bfbd841da2aecdb77dc0b23131c39a GIT binary patch literal 420 zcmV;V0bBlwP)Px$UP(kjR5*=YQa^8kKoozOE_LR{2rX^4x! zGhqfI=qwwhhrt>g_Z$6P*b=8_O+*zu(6Uj1XL1j7r@qAFBDX>7r_akJa@>_kU%x*e zwgu?!tWsa@&T4m7+r=y{>bGZk%YfFT>M)j(EfE^@Svbc?Q2F<3jBH6&hnWCJM45fF zgs2Zo2jjXNTLPx$r%6OXR49>MQcX+4KoFg@sUb85^pZVjiKPvcQYginQ1FzTyn3r=1rLJ#6&CtW zM8rdS?LiPk3ZV$4AE8v(2vxC-QU&AT*==x7vokyIy?Hy*>7`v_X@?mu4~Qrp5RuD+ zcyQ4fChFxZ9>ohD5C8S!lvp|uzoRG8)e=kFmKo_PssheohAc25U2U|x=hb4X=i8Rq z>iI;3z_!eY2pJiDk*;9wZBgnf^Q+Oy91$Pqm7bPbrPN z-rmM~PSa-eh347fqgJszJF;}GLf!= z`Mg>L)=Cn9@$0C$Jn(oZjF#R%T^`8sv50(F3>o!umMDmSi^GiNKMFfdBPXnu!~SF^3x}D(^vz*Le;(#j%8b4YZSCLkKF_~z`Rh?`JEv(f jw2Ji-D;Ph5(Bt7Rur?NdAIn!}00000NkvXXu0mjfg*Mxx literal 0 HcmV?d00001 diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/honeydew_item.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/honeydew_item.png new file mode 100644 index 0000000000000000000000000000000000000000..5c46ede57f362cf847e055b5a49d59ae749c495a GIT binary patch literal 377 zcmV-<0fzpGP)Px$Gf6~2R5*>jlD$g9P#A`vRz)O%h}2GLJ?dhMAYGhX70D{N7O{g{K@hx#fL=kb zp!5oyow^j46cM2shhjjIK}1qH4&{U#Oeli*ZQuFM^S#KK!DTs zI-wWPGb^&+^$Rlv6nJq}2jK1bL!0Lcq#XeUogr%ng3t?Q3%HJuNBbtr^;Mp3MgTNg zwZuJoc~ABh^AeF5^o+|;7wm(sF9U;|Qt5R_De){oV zZyz;xEkV!xB6^59Uz{Yv>rXX~GhrIssue found in file warning.config.number.uniform.missing_max: "Issue found in file - The config '' is missing the required 'max' argument for 'uniform' number." warning.config.number.gaussian.missing_min: "Issue found in file - The config '' is missing the required 'min' argument for 'gaussian' number." warning.config.number.gaussian.missing_max: "Issue found in file - The config '' is missing the required 'max' argument for 'gaussian' number." +warning.config.number.binomial.missing_extra: "Issue found in file - The config '' is missing the required 'extra' argument for 'binomial' number." +warning.config.number.binomial.missing_probability: "Issue found in file - The config '' is missing the required 'probability' argument for 'binomial' number." warning.config.condition.all_of.missing_terms: "Issue found in file - The config '' is missing the required 'terms' argument for 'all_of' condition." warning.config.condition.all_of.invalid_terms_type: "Issue found in file - The config '' has a misconfigured 'all_of' condition, 'terms' should be a map list, current type: ''." warning.config.condition.any_of.missing_terms: "Issue found in file - The config '' is missing the required 'terms' argument for 'any_of' condition." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 462138c04..6f5171988 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -87,6 +87,8 @@ warning.config.number.uniform.missing_min: "在文件 发现问 warning.config.number.uniform.missing_max: "在文件 发现问题 - 配置项 '' 缺少 'uniform' 数字类型所需的 'max' 参数" warning.config.number.gaussian.missing_min: "在文件 发现问题 - 配置项 '' 缺少 'gaussian' 数字类型所需的 'min' 参数" warning.config.number.gaussian.missing_max: "在文件 发现问题 - 配置项 '' 缺少 'gaussian' 数字类型所需的 'max' 参数" +warning.config.number.binomial.missing_extra: "在文件 发现问题 - 配置项 '' 缺少 'binomial' 数字类型所需的 'extra' 参数" +warning.config.number.binomial.missing_probability: "在文件 发现问题 - 配置项 '' 缺少 'binomial' 数字类型所需的 'probability' 参数" warning.config.condition.all_of.missing_terms: "在文件 发现问题 - 配置项 '' 缺少 'all_of' 条件所需的 'terms' 参数" warning.config.condition.all_of.invalid_terms_type: "在文件 发现问题 - 配置项 '' 的 'all_of' 条件配置错误, 'terms' 应为映射列表, 当前类型: ''" warning.config.condition.any_of.missing_terms: "在文件 发现问题 - 配置项 '' 缺少 'any_of' 条件所需的 'terms' 参数" diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 62898220f..b3eafb708 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -431,6 +431,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/configuration/blocks/topaz_ore.yml"); plugin.saveResource("resources/default/configuration/blocks/netherite_anvil.yml"); plugin.saveResource("resources/default/configuration/blocks/amethyst_torch.yml"); + plugin.saveResource("resources/default/configuration/blocks/honeydew.yml"); // assets plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png"); @@ -537,6 +538,10 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_background.png.mcmeta"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png.mcmeta"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_top.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/honeydew_item.png"); } private TreeMap> updateCachedConfigFiles() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/BinomialNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/BinomialNumberProvider.java new file mode 100644 index 000000000..3d2306df8 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/BinomialNumberProvider.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.plugin.context.number; + +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.RandomUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; + +public record BinomialNumberProvider(NumberProvider trials, NumberProvider successProbability) implements NumberProvider { + public static final Factory FACTORY = new Factory(); + + @Override + public float getFloat(Context context) { + return getInt(context); + } + + @Override + public double getDouble(Context context) { + return getInt(context); + } + + @Override + public int getInt(Context context) { + int trialCount = this.trials.getInt(context); + float probability = this.successProbability.getFloat(context); + int successCount = 0; + + for (int i = 0; i < trialCount; i++) { + if (RandomUtils.generateRandomFloat(0, 1) < probability) { + successCount++; + } + } + return successCount; + } + + @Override + public Key type() { + return NumberProviders.BINOMIAL; + } + + public static class Factory implements NumberProviderFactory { + + @Override + public NumberProvider create(Map arguments) { + Object trials = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("extra"), "warning.config.number.binomial.missing_extra"); + Object successProbability = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("probability"), "warning.config.number.binomial.missing_probability"); + return new BinomialNumberProvider(NumberProviders.fromObject(trials), NumberProviders.fromObject(successProbability)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java index 49ce77b1a..0b46ffb92 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java @@ -18,6 +18,7 @@ public class NumberProviders { public static final Key UNIFORM = Key.of("craftengine:uniform"); public static final Key EXPRESSION = Key.of("craftengine:expression"); public static final Key GAUSSIAN = Key.of("craftengine:gaussian"); + public static final Key BINOMIAL = Key.of("craftengine:binomial"); static { register(FIXED, FixedNumberProvider.FACTORY); @@ -25,6 +26,7 @@ public class NumberProviders { register(UNIFORM, UniformNumberProvider.FACTORY); register(GAUSSIAN, GaussianNumberProvider.FACTORY); register(EXPRESSION, ExpressionNumberProvider.FACTORY); + register(BINOMIAL, BinomialNumberProvider.FACTORY); } public static void register(Key key, NumberProviderFactory factory) { From 895b903e6b8c600272bf19a5204c0a2649c70fb1 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 23 Sep 2025 13:10:24 +0800 Subject: [PATCH 172/226] =?UTF-8?q?=E8=A1=A5=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/additional-real-blocks.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 2c6f2c87d..98472f7de 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -84,4 +84,6 @@ minecraft:warped_fence_gate: 16 minecraft:barrier: 128 minecraft:white_bed: 1 minecraft:redstone_torch: 1 -minecraft:redstone_wall_torch: 4 \ No newline at end of file +minecraft:redstone_wall_torch: 4 +minecraft:pumpkin_stem: 8 +minecraft:attached_pumpkin_stem: 4 \ No newline at end of file From 5558a6d67323c450cb16eac17ea1af7dff4b729b Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 23 Sep 2025 13:28:44 +0800 Subject: [PATCH 173/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/reflection/minecraft/CoreReflections.java | 4 ++-- .../bukkit/plugin/reflection/minecraft/MBlocks.java | 2 +- .../resources/default/configuration/blocks/honeydew.yml | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 345f5ab28..64590cb41 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4441,7 +4441,7 @@ public final class CoreReflections { : VersionHelper.isOrAbove1_20_5() ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) : VersionHelper.isOrAbove1_20_4() - ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) - : ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "c"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) + ? ReflectionUtils.getDeclaredMethod(clazz$Block, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) + : ReflectionUtils.getDeclaredMethod(clazz$Block, boolean.class, new String[]{"propagatesSkylightDown", "c"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java index c08c66559..79389af1c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlocks.java @@ -6,7 +6,7 @@ import net.momirealms.craftengine.core.util.VersionHelper; public final class MBlocks { private MBlocks() {} - public static final Object AIR = getById("air");; + public static final Object AIR = getById("air"); public static final Object AIR$defaultState = FastNMS.INSTANCE.method$Block$defaultState(AIR); public static final Object STONE = getById("stone"); public static final Object STONE$defaultState = FastNMS.INSTANCE.method$Block$defaultState(STONE); diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml index 12d8f5598..25400f3ec 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml @@ -1,6 +1,7 @@ items: default:honeydew_item: material: apple + custom-model-data: 1000 data: item-name: model: @@ -12,6 +13,7 @@ items: block: default:honeydew_stem default:honeydew: material: nether_brick + custom-model-data: 3023 data: item-name: model: From ec0c61324d3f5ea07d3d5d9e1d934ced2f78295d Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 23 Sep 2025 14:33:19 +0800 Subject: [PATCH 174/226] =?UTF-8?q?=E5=9B=9E=E9=80=80=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=99=90?= =?UTF-8?q?=E5=88=B6=E6=95=B0=E9=87=8F=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/injector/BlockStateGenerator.java | 80 +------------------ .../reflection/minecraft/CoreReflections.java | 16 ---- .../default/configuration/blocks/honeydew.yml | 8 +- .../loot/function/LimitCountFunction.java | 65 +++++++++++++++ .../core/loot/function/LootFunctions.java | 2 + 5 files changed, 73 insertions(+), 98 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/loot/function/LimitCountFunction.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java index 03d0d98b0..2c2e4d036 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockStateGenerator.java @@ -19,19 +19,16 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlockStateProperties; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MLootContextParams; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.BlockSettings; -import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.DelegatingBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.World; @@ -69,19 +66,7 @@ public final class BlockStateGenerator { .method(ElementMatchers.is(CoreReflections.method$StateHolder$setValue)) .intercept(MethodDelegation.to(SetPropertyValueInterceptor.INSTANCE)) .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isBlock)) - .intercept(MethodDelegation.to(IsBlockInterceptor.INSTANCE)) - .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isHolderSetBlock)) - .intercept(MethodDelegation.to(IsHolderSetBlockInterceptor.INSTANCE)); - if (CoreReflections.method$BlockStateBase$isHolderBlock != null) { - stateBuilder = stateBuilder - .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isHolderBlock)) - .intercept(MethodDelegation.to(IsHolderBlockInterceptor.INSTANCE)); - } - if (CoreReflections.method$BlockStateBase$isResourceKeyBlock != null) { - stateBuilder = stateBuilder - .method(ElementMatchers.is(CoreReflections.method$BlockStateBase$isResourceKeyBlock)) - .intercept(MethodDelegation.to(IsResourceKeyBlockInterceptor.INSTANCE)); - } + .intercept(MethodDelegation.to(IsBlockInterceptor.INSTANCE)); Class clazz$CraftEngineBlock = stateBuilder.make().load(BlockStateGenerator.class.getClassLoader()).getLoaded(); constructor$CraftEngineBlockState = VersionHelper.isOrAbove1_20_5() ? MethodHandles.publicLookup().in(clazz$CraftEngineBlock) @@ -217,69 +202,6 @@ public final class BlockStateGenerator { } } - public static class IsHolderSetBlockInterceptor { - public static final IsHolderSetBlockInterceptor INSTANCE = new IsHolderSetBlockInterceptor(); - - @SuppressWarnings("unchecked") - @RuntimeType - public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - DelegatingBlockState customState = (DelegatingBlockState) thisObj; - ImmutableBlockState thisState = customState.blockState(); - if (thisState == null) return false; - Holder owner = thisState.owner(); - for (Object holder : (Iterable) args[0]) { - Object block = FastNMS.INSTANCE.method$Holder$value(holder); - if (block == null) continue; - if (!(FastNMS.INSTANCE.method$Block$defaultState(block) instanceof DelegatingBlockState customHolder)) - continue; - ImmutableBlockState holderState = customHolder.blockState(); - if (holderState == null) continue; - return holderState.owner().equals(owner); - } - return false; - } - } - - public static class IsHolderBlockInterceptor { - public static final IsHolderBlockInterceptor INSTANCE = new IsHolderBlockInterceptor(); - - @RuntimeType - public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - DelegatingBlockState customState = (DelegatingBlockState) thisObj; - ImmutableBlockState thisState = customState.blockState(); - if (thisState == null) return false; - Object block = FastNMS.INSTANCE.method$Holder$value(args[0]); - if (block == null) return false; - if (FastNMS.INSTANCE.method$Block$defaultState(block) instanceof DelegatingBlockState holder) { - ImmutableBlockState holderState = holder.blockState(); - if (holderState == null) return false; - return holderState.owner().equals(thisState.owner()); - } - return false; - } - } - - public static class IsResourceKeyBlockInterceptor { - public static final IsResourceKeyBlockInterceptor INSTANCE = new IsResourceKeyBlockInterceptor(); - - @RuntimeType - public boolean intercept(@This Object thisObj, @AllArguments Object[] args) { - DelegatingBlockState customState = (DelegatingBlockState) thisObj; - ImmutableBlockState thisState = customState.blockState(); - if (thisState == null) return false; - Object block = FastNMS.INSTANCE.method$HolderGetter$getResourceKey(MBuiltInRegistries.BLOCK, args[0]) - .map(FastNMS.INSTANCE::method$Holder$value) - .orElse(null); - if (block == null) return false; - if (FastNMS.INSTANCE.method$Block$defaultState(block) instanceof DelegatingBlockState holder) { - ImmutableBlockState holderState = holder.blockState(); - if (holderState == null) return false; - return holderState.owner().equals(thisState.owner()); - } - return false; - } - } - public static class CreateStateInterceptor { public static final CreateStateInterceptor INSTANCE = new CreateStateInterceptor(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 64590cb41..f4b0439d1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4419,22 +4419,6 @@ public final class CoreReflections { ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Block) ); - public static final Method method$BlockStateBase$isHolderSetBlock = requireNonNull( - ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$HolderSet) - ); - - // 1.20.2+ - public static final Method method$BlockStateBase$isHolderBlock = MiscUtils.requireNonNullIf( - ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Holder), - VersionHelper.isOrAbove1_20_2() - ); - - // 1.20.3+ - public static final Method method$BlockStateBase$isResourceKeyBlock = MiscUtils.requireNonNullIf( - ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$ResourceKey), - VersionHelper.isOrAbove1_20_3() - ); - public static final Method method$BlockBehaviour$propagatesSkylightDown = requireNonNull( VersionHelper.isOrAbove1_21_2() ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "e_"}, clazz$BlockState) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml index 25400f3ec..0291afcbe 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml @@ -40,13 +40,15 @@ blocks: - type: item item: default:honeydew_item functions: + - type: set_count + add: false + count: 3~7 - type: apply_bonus enchantment: minecraft:fortune formula: type: ore_drops - - type: set_count - add: false - count: 3~7 + - type: limit_count + max: 9 - type: explosion_decay settings: map-color: 19 diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/function/LimitCountFunction.java b/core/src/main/java/net/momirealms/craftengine/core/loot/function/LimitCountFunction.java new file mode 100644 index 000000000..8ecc4fc2b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/function/LimitCountFunction.java @@ -0,0 +1,65 @@ +package net.momirealms.craftengine.core.loot.function; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.loot.LootConditions; +import net.momirealms.craftengine.core.loot.LootContext; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class LimitCountFunction extends AbstractLootConditionalFunction { + public static final Factory FACTORY = new Factory<>(); + @Nullable + private final NumberProvider min; + @Nullable + private final NumberProvider max; + + public LimitCountFunction(List> predicates, @Nullable NumberProvider min, @Nullable NumberProvider max) { + super(predicates); + this.min = min; + this.max = max; + } + + @Override + public Key type() { + return LootFunctions.LIMIT_COUNT; + } + + @Override + protected Item applyInternal(Item item, LootContext context) { + int amount = item.count(); + if (min != null) { + int minAmount = min.getInt(context); + if (amount < minAmount) { + item.count(minAmount); + } + } + if (max != null) { + int maxAmount = max.getInt(context); + if (amount > maxAmount) { + item.count(maxAmount); + } + } + return item; + } + + public static class Factory implements LootFunctionFactory { + @SuppressWarnings("unchecked") + @Override + public LootFunction create(Map arguments) { + Object min = arguments.get("min"); + Object max = arguments.get("max"); + List> conditions = Optional.ofNullable(arguments.get("conditions")) + .map(it -> LootConditions.fromMapList((List>) it)) + .orElse(Collections.emptyList()); + return new LimitCountFunction<>(conditions, min == null ? null : NumberProviders.fromObject(min), max == null ? null : NumberProviders.fromObject(max)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/function/LootFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/loot/function/LootFunctions.java index c2233c20f..c9d221c95 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/function/LootFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/function/LootFunctions.java @@ -20,12 +20,14 @@ public class LootFunctions { public static final Key SET_COUNT = Key.from("craftengine:set_count"); public static final Key EXPLOSION_DECAY = Key.from("craftengine:explosion_decay"); public static final Key DROP_EXP = Key.from("craftengine:drop_exp"); + public static final Key LIMIT_COUNT = Key.from("craftengine:limit_count"); static { register(SET_COUNT, SetCountFunction.FACTORY); register(EXPLOSION_DECAY, ExplosionDecayFunction.FACTORY); register(APPLY_BONUS, ApplyBonusCountFunction.FACTORY); register(DROP_EXP, DropExpFunction.FACTORY); + register(LIMIT_COUNT, LimitCountFunction.FACTORY); } public static void register(Key key, LootFunctionFactory factory) { From 9480737b84efc58cf081ea8f10738d791eb4951d Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 23 Sep 2025 18:27:03 +0800 Subject: [PATCH 175/226] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=93=88=E5=AF=86?= =?UTF-8?q?=E7=93=9C=E9=85=8D=E7=BD=AE=E5=92=8C=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/AttachedStemBlockBehavior.java | 27 +- .../block/behavior/GrassBlockBehavior.java | 2 +- .../block/behavior/SaplingBlockBehavior.java | 2 +- .../block/behavior/StemBlockBehavior.java | 21 +- .../UnsafeCompositeBlockBehavior.java | 10 - .../plugin/injector/BlockGenerator.java | 18 -- .../reflection/minecraft/CoreReflections.java | 10 - .../configuration/blocks/hami_melon.yml | 230 ++++++++++++++ .../default/configuration/blocks/honeydew.yml | 298 ------------------ .../default/configuration/categories.yml | 5 +- .../resources/default/configuration/i18n.yml | 22 +- .../custom/{honeydew.png => hami_melon.png} | Bin ...eydew_bottom.png => hami_melon_bottom.png} | Bin .../{honeydew_top.png => hami_melon_top.png} | Bin .../textures/item/custom/hami_melon_seeds.png | Bin 0 -> 340 bytes ...honeydew_item.png => hami_melon_slice.png} | Bin .../craftengine/core/block/BlockBehavior.java | 6 - .../core/pack/AbstractPackManager.java | 11 +- gradle.properties | 4 +- 19 files changed, 266 insertions(+), 400 deletions(-) create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml delete mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/{honeydew.png => hami_melon.png} (100%) rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/{honeydew_bottom.png => hami_melon_bottom.png} (100%) rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/{honeydew_top.png => hami_melon_top.png} (100%) create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/{honeydew_item.png => hami_melon_slice.png} (100%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java index 1e30aafb9..f35d35159 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java @@ -11,40 +11,31 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.Callable; -public class AttachedStemBlockBehavior extends BushBlockBehavior { +public class AttachedStemBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; private final Key fruit; private final Key stem; public AttachedStemBlockBehavior(CustomBlock customBlock, - int delay, - boolean blacklist, - List tagsCanSurviveOn, - Set blockStatesCanSurviveOn, - Set customBlocksCansSurviveOn, Property facingProperty, Key fruit, Key stem) { - super(customBlock, delay, blacklist, false, tagsCanSurviveOn, blockStatesCanSurviveOn, customBlocksCansSurviveOn); + super(customBlock); this.facingProperty = facingProperty; this.fruit = fruit; this.stem = stem; } - @Override - public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) { - return FastNMS.INSTANCE.field$FluidState$isEmpty(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(args[0])); - } - @Override public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { return (VersionHelper.isOrAbove1_20_5() ? args[1] : args[3]).equals(CoreReflections.instance$PathComputationType$AIR) @@ -97,14 +88,12 @@ public class AttachedStemBlockBehavior extends BushBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments, false); - int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); - boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist"); + @SuppressWarnings("unchecked") Property facingProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.attached_stem.missing_facing"); Key fruit = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("fruit"), "warning.config.block.behavior.attached_stem.missing_fruit")); Key stem = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("stem"), "warning.config.block.behavior.attached_stem.missing_stem")); - return new AttachedStemBlockBehavior(block, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), facingProperty, fruit, stem); + return new AttachedStemBlockBehavior(block, facingProperty, fruit, stem); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java index 20833c67c..6903885ef 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java @@ -146,7 +146,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { } if (FastNMS.INSTANCE.method$BlockStateBase$isAir(currentState)) { Object chunkGenerator = CoreReflections.method$ServerChunkCache$getGenerator.invoke(FastNMS.INSTANCE.method$ServerLevel$getChunkSource(world)); - Object placedFeature = CoreReflections.method$Holder$value.invoke(holder.get()); + Object placedFeature = FastNMS.INSTANCE.method$Holder$value(holder.get()); CoreReflections.method$PlacedFeature$place.invoke(placedFeature, world, chunkGenerator, random, nmsCurrentPos); } } 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 c56fa4050..fa3535e88 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 @@ -88,7 +88,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { return; } Object chunkGenerator = CoreReflections.method$ServerChunkCache$getGenerator.invoke(FastNMS.INSTANCE.method$ServerLevel$getChunkSource(world)); - Object configuredFeature = CoreReflections.method$Holder$value.invoke(holder.get()); + Object configuredFeature = FastNMS.INSTANCE.method$Holder$value(holder.get()); Object fluidState = FastNMS.INSTANCE.method$BlockGetter$getFluidState(world, blockPos); Object legacyState = CoreReflections.method$FluidState$createLegacyBlock.invoke(fluidState); FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, legacyState, UpdateOption.UPDATE_NONE.flags()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java index d7d1180c3..69755b7ac 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java @@ -17,13 +17,11 @@ import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.util.*; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.Callable; -public class StemBlockBehavior extends BushBlockBehavior { +public class StemBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final IntegerProperty ageProperty; private final Key fruit; @@ -33,18 +31,13 @@ public class StemBlockBehavior extends BushBlockBehavior { private final Object blockMayPlaceFruit; public StemBlockBehavior(CustomBlock customBlock, - int delay, - boolean blacklist, - List tagsCanSurviveOn, - Set blockStatesCanSurviveOn, - Set customBlocksCansSurviveOn, IntegerProperty ageProperty, Key fruit, Key attachedStem, int minGrowLight, Object tagMayPlaceFruit, Object blockMayPlaceFruit) { - super(customBlock, delay, blacklist, false, tagsCanSurviveOn, blockStatesCanSurviveOn, customBlocksCansSurviveOn); + super(customBlock); this.ageProperty = ageProperty; this.fruit = fruit; this.attachedStem = attachedStem; @@ -53,11 +46,6 @@ public class StemBlockBehavior extends BushBlockBehavior { this.blockMayPlaceFruit = blockMayPlaceFruit; } - @Override - public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) { - return FastNMS.INSTANCE.field$FluidState$isEmpty(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(args[0])); - } - @Override public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { return (VersionHelper.isOrAbove1_20_5() ? args[1] : args[3]).equals(CoreReflections.instance$PathComputationType$AIR) @@ -139,16 +127,13 @@ public class StemBlockBehavior extends BushBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments, false); - int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); - boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", false), "blacklist"); IntegerProperty ageProperty = (IntegerProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.stem.missing_age"); Key fruit = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("fruit"), "warning.config.block.behavior.stem.missing_fruit")); Key attachedStem = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("attached-stem"), "warning.config.block.behavior.stem.missing_attached_stem")); int minGrowLight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("light-requirement", 9), "light-requirement"); Object tagMayPlaceFruit = FastNMS.INSTANCE.method$TagKey$create(MRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("may-place-fruit", "minecraft:dirt").toString()))); Object blockMayPlaceFruit = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(Key.of(arguments.getOrDefault("may-place-fruit", "minecraft:farmland").toString()))); - return new StemBlockBehavior(block, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), ageProperty, fruit, attachedStem, minGrowLight, tagMayPlaceFruit, blockMayPlaceFruit); + return new StemBlockBehavior(block, ageProperty, fruit, attachedStem, minGrowLight, tagMayPlaceFruit, blockMayPlaceFruit); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 8e9e4c631..0aab207be 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -381,14 +381,4 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior } FallOnBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); } - - @Override - public boolean propagatesSkylightDown(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - for (AbstractBlockBehavior behavior : this.behaviors) { - if (!behavior.propagatesSkylightDown(thisBlock, args, superMethod)) { - return false; - } - } - return (boolean) superMethod.call(); - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index d54835999..ae5e8c670 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -170,9 +170,6 @@ public final class BlockGenerator { // stepOn .method(ElementMatchers.is(CoreReflections.method$Block$stepOn)) .intercept(MethodDelegation.to(StepOnInterceptor.INSTANCE)) - // propagatesSkylightDown - .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$propagatesSkylightDown)) - .intercept(MethodDelegation.to(PropagatesSkylightDownInterceptor.INSTANCE)) ; // 1.21.5+ if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) { @@ -763,19 +760,4 @@ public final class BlockGenerator { } } } - - public static class PropagatesSkylightDownInterceptor { - public static final PropagatesSkylightDownInterceptor INSTANCE = new PropagatesSkylightDownInterceptor(); - - @RuntimeType - public boolean intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { - ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); - try { - return holder.value().propagatesSkylightDown(thisObj, args, superMethod); - } catch (Exception e) { - CraftEngine.instance().logger().severe("Failed to run propagatesSkylightDown", e); - return false; - } - } - } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index f4b0439d1..0cae0070e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4418,14 +4418,4 @@ public final class CoreReflections { public static final Method method$BlockStateBase$isBlock = requireNonNull( ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Block) ); - - public static final Method method$BlockBehaviour$propagatesSkylightDown = requireNonNull( - VersionHelper.isOrAbove1_21_2() - ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "e_"}, clazz$BlockState) - : VersionHelper.isOrAbove1_20_5() - ? ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) - : VersionHelper.isOrAbove1_20_4() - ? ReflectionUtils.getDeclaredMethod(clazz$Block, boolean.class, new String[]{"propagatesSkylightDown", "a_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) - : ReflectionUtils.getDeclaredMethod(clazz$Block, boolean.class, new String[]{"propagatesSkylightDown", "c"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos) - ); } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml new file mode 100644 index 000000000..dd2b70a6e --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml @@ -0,0 +1,230 @@ +items: + default:hami_melon_slice: + material: melon_slice + custom-model-data: 1000 + data: + item-name: + $$>=1.20.5: + food: + nutrition: 2 + saturation: 1.0 + can-always-eat: false + $$<=1.20.4: + settings: + food: + nutrition: 2 + saturation: 1.0 + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/hami_melon_slice + default:hami_melon: + material: nether_brick + custom-model-data: 3023 + data: + item-name: + model: + path: minecraft:item/custom/hami_melon + generation: + parent: minecraft:block/custom/hami_melon + behavior: + type: block_item + block: default:hami_melon + default:hami_melon_seeds: + material: nether_brick + custom-model-data: 3024 + data: + item-name: + model: + template: default:model/simplified_generated + arguments: + path: minecraft:item/custom/hami_melon_seeds + behavior: + type: block_item + block: default:hami_melon_stem +blocks: + default:hami_melon: + loot: + pools: + - rolls: 1 + entries: + - type: alternatives + children: + - type: item + item: default:hami_melon + conditions: + - type: enchantment + predicate: minecraft:silk_touch>=1 + - type: item + item: default:hami_melon_slice + functions: + - type: set_count + add: false + count: 3~7 + - type: apply_bonus + enchantment: minecraft:fortune + formula: + type: ore_drops + - type: limit_count + max: 9 + - type: explosion_decay + settings: + map-color: 19 + hardness: 1 + resistance: 1 + push-reaction: DESTROY + is-suffocating: true + is-redstone-conductor: true + tags: + - minecraft:enderman_holdable + - minecraft:mineable/axe + - minecraft:sword_efficient + state: + id: 30 + state: note_block:30 + model: + template: default:model/cube + arguments: + model: minecraft:block/custom/hami_melon + particle_texture: minecraft:block/custom/hami_melon + down_texture: minecraft:block/custom/hami_melon_bottom + up_texture: minecraft:block/custom/hami_melon_top + north_texture: minecraft:block/custom/hami_melon + east_texture: minecraft:block/custom/hami_melon + south_texture: minecraft:block/custom/hami_melon + west_texture: minecraft:block/custom/hami_melon + default:hami_melon_stem: + settings: + map-color: 7 + hardness: 0 + resistance: 0 + push-reaction: DESTROY + is-suffocating: false + is-redstone-conductor: false + item: default:hami_melon_item + is-randomly-ticking: true + tags: + - minecraft:bee_growables + - minecraft:crops + - minecraft:maintains_farmland + behaviors: + - type: stem_block + fruit: default:hami_melon + attached-stem: default:attached_hami_melon_stem + - type: bush_block + bottom-blocks: + - minecraft:farmland + states: + properties: + age: + type: int + default: 0 + range: 0~7 + appearances: + age=0: + state: pumpkin_stem[age=0] + age=1: + state: pumpkin_stem[age=1] + age=2: + state: pumpkin_stem[age=2] + age=3: + state: pumpkin_stem[age=3] + age=4: + state: pumpkin_stem[age=4] + age=5: + state: pumpkin_stem[age=5] + age=6: + state: pumpkin_stem[age=6] + age=7: + state: pumpkin_stem[age=7] + variants: + age=0: + appearance: age=0 + id: 0 + age=1: + appearance: age=1 + id: 1 + age=2: + appearance: age=2 + id: 2 + age=3: + appearance: age=3 + id: 3 + age=4: + appearance: age=4 + id: 4 + age=5: + appearance: age=5 + id: 5 + age=6: + appearance: age=6 + id: 6 + age=7: + appearance: age=7 + id: 7 + default:attached_hami_melon_stem: + settings: + map-color: 7 + hardness: 0 + resistance: 0 + push-reaction: DESTROY + is-suffocating: false + is-redstone-conductor: false + item: default:hami_melon_seeds + is-randomly-ticking: true + tags: + - minecraft:maintains_farmland + behaviors: + - type: attached_stem_block + fruit: default:hami_melon + stem: default:hami_melon_stem + - type: bush_block + blacklist: false + bottom-blocks: + - minecraft:farmland + states: + properties: + facing: + type: horizontal_direction + default: north + appearances: + facing=east: + state: attached_pumpkin_stem[facing=east] + facing=south: + state: attached_pumpkin_stem[facing=south] + facing=west: + state: attached_pumpkin_stem[facing=west] + facing=north: + state: attached_pumpkin_stem[facing=north] + variants: + facing=east: + appearance: facing=east + id: 0 + facing=south: + appearance: facing=south + id: 1 + facing=west: + appearance: facing=west + id: 2 + facing=north: + appearance: facing=north + id: 3 +recipes: + default:hami_melon: + type: shaped + pattern: + - AAA + - AAA + - AAA + ingredients: + A: default:hami_melon_slice + result: + id: default:hami_melon + count: 1 + default:hami_melon_seeds: + type: shapeless + ingredients: + - default:hami_melon_slice + result: + id: default:hami_melon_seeds + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml b/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml deleted file mode 100644 index 0291afcbe..000000000 --- a/common-files/src/main/resources/resources/default/configuration/blocks/honeydew.yml +++ /dev/null @@ -1,298 +0,0 @@ -items: - default:honeydew_item: - material: apple - custom-model-data: 1000 - data: - item-name: - model: - template: default:model/simplified_generated - arguments: - path: minecraft:item/custom/honeydew_item - behavior: - type: block_item - block: default:honeydew_stem - default:honeydew: - material: nether_brick - custom-model-data: 3023 - data: - item-name: - model: - path: minecraft:item/custom/honeydew - generation: - parent: minecraft:block/custom/honeydew - behavior: - type: block_item - block: default:honeydew - -blocks: - default:honeydew: - loot: - pools: - - rolls: 1 - entries: - - type: alternatives - children: - - type: item - item: default:honeydew - conditions: - - type: enchantment - predicate: minecraft:silk_touch>=1 - - type: item - item: default:honeydew_item - functions: - - type: set_count - add: false - count: 3~7 - - type: apply_bonus - enchantment: minecraft:fortune - formula: - type: ore_drops - - type: limit_count - max: 9 - - type: explosion_decay - settings: - map-color: 19 - hardness: 1 - resistance: 1 - push-reaction: DESTROY - is-suffocating: true - is-redstone-conductor: true - item: default:honeydew - tags: - - minecraft:enderman_holdable - - minecraft:mineable/axe - - minecraft:sword_efficient - incorrect-tool-dig-speed: 1 - state: - id: 30 - state: note_block:30 - model: - template: default:model/cube - arguments: - model: minecraft:block/custom/honeydew - particle_texture: minecraft:block/custom/honeydew - down_texture: minecraft:block/custom/honeydew_bottom - up_texture: minecraft:block/custom/honeydew_top - north_texture: minecraft:block/custom/honeydew - east_texture: minecraft:block/custom/honeydew - south_texture: minecraft:block/custom/honeydew - west_texture: minecraft:block/custom/honeydew - default:honeydew_stem: - loot: - pools: - - rolls: 1 - entries: - - type: item - item: default:honeydew_item - functions: - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 0 - count: - type: binomial - extra: 3 - probability: 0.06666667 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 1 - count: - type: binomial - extra: 3 - probability: 0.13333334 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 2 - count: - type: binomial - extra: 3 - probability: 0.2 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 3 - count: - type: binomial - extra: 3 - probability: 0.26666668 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 4 - count: - type: binomial - extra: 3 - probability: 0.33333334 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 5 - count: - type: binomial - extra: 3 - probability: 0.4 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 6 - count: - type: binomial - extra: 3 - probability: 0.46666667 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 7 - count: - type: binomial - extra: 3 - probability: 0.53333336 - functions: - - type: explosion_decay - settings: - map-color: 7 - hardness: 0 - resistance: 0 - push-reaction: DESTROY - is-suffocating: false - is-redstone-conductor: false - item: default:honeydew_item - is-randomly-ticking: true - tags: - - minecraft:bee_growables - - minecraft:crops - - minecraft:maintains_farmland - behaviors: - type: stem_block - fruit: default:honeydew - attached-stem: default:attached_honeydew_stem - blacklist: false - bottom-blocks: - - minecraft:farmland - states: - properties: - age: - type: int - default: 0 - range: 0~7 - appearances: - age=0: - state: pumpkin_stem[age=0] - age=1: - state: pumpkin_stem[age=1] - age=2: - state: pumpkin_stem[age=2] - age=3: - state: pumpkin_stem[age=3] - age=4: - state: pumpkin_stem[age=4] - age=5: - state: pumpkin_stem[age=5] - age=6: - state: pumpkin_stem[age=6] - age=7: - state: pumpkin_stem[age=7] - variants: - age=0: - appearance: age=0 - id: 0 - age=1: - appearance: age=1 - id: 1 - age=2: - appearance: age=2 - id: 2 - age=3: - appearance: age=3 - id: 3 - age=4: - appearance: age=4 - id: 4 - age=5: - appearance: age=5 - id: 5 - age=6: - appearance: age=6 - id: 6 - age=7: - appearance: age=7 - id: 7 - default:attached_honeydew_stem: - loot: - pools: - - rolls: 1 - entries: - - type: item - item: default:honeydew_item - functions: - - type: set_count - add: false - count: - type: binomial - extra: 3 - probability: 0.53333336 - functions: - - type: explosion_decay - settings: - map-color: 7 - hardness: 0 - resistance: 0 - push-reaction: DESTROY - is-suffocating: false - is-redstone-conductor: false - item: default:honeydew_item - is-randomly-ticking: true - tags: - - minecraft:maintains_farmland - behaviors: - type: attached_stem_block - fruit: default:honeydew - stem: default:honeydew_stem - blacklist: false - bottom-blocks: - - minecraft:farmland - states: - properties: - facing: - type: horizontal_direction - default: north - appearances: - facing=east: - state: attached_pumpkin_stem[facing=east] - facing=south: - state: attached_pumpkin_stem[facing=south] - facing=west: - state: attached_pumpkin_stem[facing=west] - facing=north: - state: attached_pumpkin_stem[facing=north] - variants: - facing=east: - appearance: facing=east - id: 0 - facing=south: - appearance: facing=south - id: 1 - facing=west: - appearance: facing=west - id: 2 - facing=north: - appearance: facing=north - id: 3 diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 6f02cfd78..7719dacfe 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -79,5 +79,6 @@ categories: - default:wooden_chair - default:flower_basket - default:amethyst_torch - - default:honeydew_item - - default:honeydew \ No newline at end of file + - default:hami_melon_seeds + - default:hami_melon_slice + - default:hami_melon \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index fbd75318d..df4fa5f14 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -49,8 +49,9 @@ i18n: item.safe_block: Safe Block item.sofa: Sofa item.amethyst_torch: Amethyst Torch - item.honeydew_item: Honeydew Slice - item.honeydew: Honeydew + item.hami_melon_slice: Hami Melon Slice + item.hami_melon: Hami Melon + item.hami_melon_seeds: Hami Melon Seeds category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -109,8 +110,9 @@ i18n: item.safe_block: 保险柜 item.sofa: 沙发 item.amethyst_torch: 紫水晶火把 - item.honeydew_item: 哈密瓜片 - item.honeydew: 哈密瓜 + item.hami_melon_slice: 哈密瓜片 + item.hami_melon: 哈密瓜 + item.hami_melon_seeds: 哈密瓜种子 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -153,9 +155,9 @@ lang: block_name:default:sofa: Sofa block_name:default:amethyst_torch: Amethyst Torch block_name:default:amethyst_wall_torch: Amethyst Torch - block_name:default:honeydew: Honeydew - block_name:default:honeydew_stem: Honeydew Stem - block_name:default:default:attached_honeydew_stem: Honeydew Stem + block_name:default:hami_melon: Hami Melon + block_name:default:hami_melon_stem: Hami Melon Stem + block_name:default:default:attached_hami_melon_stem: Hami Melon Stem zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -186,6 +188,6 @@ lang: block_name:default:sofa: 沙发 block_name:default:amethyst_torch: 紫水晶火把 block_name:default:amethyst_wall_torch: 紫水晶火把 - block_name:default:honeydew: 哈密瓜 - block_name:default:honeydew_stem: 哈密瓜茎 - block_name:default:default:attached_honeydew_stem: 哈密瓜茎 \ No newline at end of file + block_name:default:hami_melon: 哈密瓜 + block_name:default:hami_melon_stem: 哈密瓜茎 + block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon.png similarity index 100% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon.png diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_bottom.png similarity index 100% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_bottom.png diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_top.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_top.png similarity index 100% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_top.png rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_top.png diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png new file mode 100644 index 0000000000000000000000000000000000000000..74c7165ec69c1407810bb7cffdc7ffce02d58465 GIT binary patch literal 340 zcmV-a0jvIrP)Px$4oO5oR5*=eV4x5%ViR!0BWMD+PuPyRO4s!h%n8PaOvX0|NuY)9e2kq%|2CCeP=BxdOKrU>cuZ z{}0!^?gTdj!wCk4SFb)XJb(L{fuI+V{m;Mv(FgGbqaPp@ck)4OfR06D-&?!$4LsF9QPuFGJk!ZwwZf{$dL*1}3bc zP|f#tJYsnF=sQYy5N*J_N8cF)v_u&SZvJNw-};_`ftWC0K=O0JO-MANYbF?t`kK;k mKDv$QnlTJuATc9R1poj?yN}oU`=w9-0000 superMethod) throws Exception { - return (boolean) superMethod.call(); - } - public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { return state; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index b3eafb708..586931ea7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -431,7 +431,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/configuration/blocks/topaz_ore.yml"); plugin.saveResource("resources/default/configuration/blocks/netherite_anvil.yml"); plugin.saveResource("resources/default/configuration/blocks/amethyst_torch.yml"); - plugin.saveResource("resources/default/configuration/blocks/honeydew.yml"); + plugin.saveResource("resources/default/configuration/blocks/hami_melon.yml"); // assets plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png"); @@ -538,10 +538,11 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_background.png.mcmeta"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png.mcmeta"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew.png"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_bottom.png"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/honeydew_top.png"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/honeydew_item.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_bottom.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_top.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_slice.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png"); } private TreeMap> updateCachedConfigFiles() { diff --git a/gradle.properties b/gradle.properties index 1aa561ed6..5cae0308f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.4 +project_version=0.0.63.5 config_version=46 lang_version=31 project_group=net.momirealms @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.94 +nms_helper_version=1.0.95 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From f8a1d7a704fd928aadd7a3d95cddf507fdda0599 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 06:40:31 +0800 Subject: [PATCH 176/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/additional-real-blocks.yml | 3 +- .../configuration/blocks/hami_melon.yml | 106 +- .../configuration/blocks/palm_tree.yml | 1175 +++++++++++------ .../default/configuration/categories.yml | 1 + .../resources/default/configuration/i18n.yml | 6 +- 5 files changed, 868 insertions(+), 423 deletions(-) diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 98472f7de..31bea6787 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -86,4 +86,5 @@ minecraft:white_bed: 1 minecraft:redstone_torch: 1 minecraft:redstone_wall_torch: 4 minecraft:pumpkin_stem: 8 -minecraft:attached_pumpkin_stem: 4 \ No newline at end of file +minecraft:attached_pumpkin_stem: 4 +minecraft:birch_button: 24 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml index dd2b70a6e..3d2cb6e37 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml @@ -94,6 +94,95 @@ blocks: south_texture: minecraft:block/custom/hami_melon west_texture: minecraft:block/custom/hami_melon default:hami_melon_stem: + loot: + pools: + - rolls: 1 + entries: + - type: item + item: default:hami_melon_seeds + functions: + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 0 + count: + type: binomial + extra: 3 + probability: 0.06666667 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 1 + count: + type: binomial + extra: 3 + probability: 0.13333334 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 2 + count: + type: binomial + extra: 3 + probability: 0.2 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 3 + count: + type: binomial + extra: 3 + probability: 0.26666668 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 4 + count: + type: binomial + extra: 3 + probability: 0.33333334 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 5 + count: + type: binomial + extra: 3 + probability: 0.4 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 6 + count: + type: binomial + extra: 3 + probability: 0.46666667 + - type: set_count + add: false + conditions: + - type: match_block_property + properties: + age: 7 + count: + type: binomial + extra: 3 + probability: 0.53333336 + functions: + - type: explosion_decay settings: map-color: 7 hardness: 0 @@ -101,7 +190,7 @@ blocks: push-reaction: DESTROY is-suffocating: false is-redstone-conductor: false - item: default:hami_melon_item + item: default:hami_melon_seeds is-randomly-ticking: true tags: - minecraft:bee_growables @@ -163,6 +252,21 @@ blocks: appearance: age=7 id: 7 default:attached_hami_melon_stem: + loot: + pools: + - rolls: 1 + entries: + - type: item + item: default:hami_melon_seeds + functions: + - type: set_count + add: false + count: + type: binomial + extra: 3 + probability: 0.53333336 + functions: + - type: explosion_decay settings: map-color: 7 hardness: 0 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index fe8e46bd6..7e935d5d5 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -17,30 +17,7 @@ items: parent: minecraft:block/custom/palm_log behavior: type: block_item - block: - behavior: - type: strippable_block - stripped: default:stripped_palm_log - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/palm_log_top - texture_side_path: minecraft:block/custom/palm_log - model_vertical_path: minecraft:block/custom/palm_log - model_horizontal_path: minecraft:block/custom/palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 0 - to: 2 - internal_id: - type: self_increase_int - from: 0 - to: 2 + block: default:palm_log default:stripped_palm_log: material: nether_brick custom-model-data: 1001 @@ -59,27 +36,7 @@ items: parent: minecraft:block/custom/stripped_palm_log behavior: type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/stripped_palm_log_top - texture_side_path: minecraft:block/custom/stripped_palm_log - model_vertical_path: minecraft:block/custom/stripped_palm_log - model_horizontal_path: minecraft:block/custom/stripped_palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 3 - to: 5 - internal_id: - type: self_increase_int - from: 3 - to: 5 + block: default:stripped_palm_log default:palm_wood: material: nether_brick custom-model-data: 1002 @@ -98,30 +55,7 @@ items: parent: minecraft:block/custom/palm_wood behavior: type: block_item - block: - behavior: - type: strippable_block - stripped: default:stripped_palm_wood - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/palm_log - texture_side_path: minecraft:block/custom/palm_log - model_vertical_path: minecraft:block/custom/palm_wood - model_horizontal_path: minecraft:block/custom/palm_wood_horizontal - vanilla_id: - type: self_increase_int - from: 6 - to: 8 - internal_id: - type: self_increase_int - from: 6 - to: 8 + block: default:palm_wood default:stripped_palm_wood: material: nether_brick custom-model-data: 1003 @@ -140,27 +74,7 @@ items: parent: minecraft:block/custom/stripped_palm_wood behavior: type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/stripped_palm_log - texture_side_path: minecraft:block/custom/stripped_palm_log - model_vertical_path: minecraft:block/custom/stripped_palm_wood - model_horizontal_path: minecraft:block/custom/stripped_palm_wood_horizontal - vanilla_id: - type: self_increase_int - from: 9 - to: 11 - internal_id: - type: self_increase_int - from: 9 - to: 11 + block: default:stripped_palm_wood default:palm_planks: material: nether_brick custom-model-data: 1004 @@ -178,18 +92,7 @@ items: parent: minecraft:block/custom/palm_planks behavior: type: block_item - block: - settings: - template: default:settings/planks - loot: - template: default:loot_table/self - state: - model: - template: default:model/simplified_cube_all - arguments: - path: minecraft:block/custom/palm_planks - id: 12 - state: note_block:12 + block: default:palm_planks default:palm_sapling: material: nether_brick custom-model-data: 1005 @@ -204,42 +107,7 @@ items: texture: minecraft:block/custom/palm_sapling behavior: type: block_item - block: - settings: - template: default:settings/sapling - behaviors: - - type: bush_block - bottom-block-tags: - - minecraft:dirt - - minecraft:farmland - - minecraft:sand - - type: sapling_block - feature: minecraft:fancy_oak - bone-meal-success-chance: 0.45 - loot: - template: default:loot_table/self - states: - properties: - stage: - type: int - default-value: 0 - range: 0~1 - appearances: - default: - state: oak_sapling:0 - model: - path: minecraft:block/custom/palm_sapling - generation: - parent: minecraft:block/cross - textures: - cross: minecraft:block/custom/palm_sapling - variants: - stage=0: - appearance: default - id: 0 - stage=1: - appearance: default - id: 1 + block: default:palm_sapling default:palm_leaves: material: oak_leaves custom-model-data: 1000 @@ -247,9 +115,9 @@ items: item-name: components: minecraft:block_state: - distance: '1' - persistent: 'false' - waterlogged: 'false' + distance: "1" + persistent: "false" + waterlogged: "false" model: type: minecraft:model path: minecraft:item/custom/palm_leaves @@ -260,27 +128,7 @@ items: value: -12012264 behavior: type: block_item - block: - behavior: - type: leaves_block - loot: - template: default:loot_table/leaves - arguments: - leaves: default:palm_leaves - sapling: default:palm_sapling - settings: - template: default:settings/leaves - states: - template: default:block_state/leaves - arguments: - default_state: oak_leaves[distance=1,persistent=false,waterlogged=false] - waterlogged_state: oak_leaves[distance=1,persistent=false,waterlogged=true] - model_path: minecraft:block/custom/palm_leaves - texture_path: minecraft:block/custom/palm_leaves - internal_id: - type: self_increase_int - from: 0 - to: 27 + block: default:palm_leaves default:palm_trapdoor: material: nether_brick custom-model-data: 1006 @@ -295,47 +143,7 @@ items: parent: minecraft:block/custom/palm_trapdoor_bottom behavior: type: block_item - block: - behavior: - type: trapdoor_block - can-open-with-hand: true - can-open-by-wind-charge: true - sounds: - open: minecraft:block.wooden_trapdoor.open - close: minecraft:block.wooden_trapdoor.close - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - overrides: - map-color: 2 - instrument: bass - hardness: 3.0 - resistance: 3.0 - burnable: true - tags: - - minecraft:mineable/axe - - minecraft:trapdoors - states: - template: default:block_state/trapdoor - arguments: - base_block: acacia_trapdoor - model_bottom_path: minecraft:block/custom/palm_trapdoor_bottom - model_bottom_generation: - parent: minecraft:block/template_orientable_trapdoor_bottom - textures: - texture: minecraft:block/custom/palm_trapdoor - model_open_path: minecraft:block/custom/palm_trapdoor_open - model_open_generation: - parent: minecraft:block/template_orientable_trapdoor_open - textures: - texture: minecraft:block/custom/palm_trapdoor - model_top_path: minecraft:block/custom/palm_trapdoor_top - model_top_generation: - parent: minecraft:block/template_orientable_trapdoor_top - textures: - texture: minecraft:block/custom/palm_trapdoor + block: default:palm_trapdoor default:palm_door: material: nether_brick custom-model-data: 1007 @@ -349,68 +157,7 @@ items: path: minecraft:item/custom/palm_door behavior: type: double_high_block_item - block: - behavior: - type: door_block - can-open-with-hand: true - can-open-by-wind-charge: true - sounds: - open: minecraft:block.wooden_door.open - close: minecraft:block.wooden_door.close - loot: - template: default:loot_table/door - settings: - template: - - default:sound/wood - overrides: - push-reaction: destroy - map-color: 2 - instrument: bass - hardness: 3.0 - resistance: 3.0 - burnable: true - tags: - - minecraft:wooden_doors - - minecraft:doors - - minecraft:mineable/axe - states: - template: default:block_state/door - arguments: - base_block: oak_door - model_top_left_path: minecraft:block/custom/palm_door_top_left - model_top_left_generation: - parent: minecraft:block/door_top_left - textures: &textures - bottom: minecraft:block/custom/palm_door_bottom - top: minecraft:block/custom/palm_door_top - model_top_right_path: minecraft:block/custom/palm_door_top_right - model_top_right_generation: - parent: minecraft:block/door_top_right - textures: *textures - model_top_left_open_path: minecraft:block/custom/palm_door_top_left_open - model_top_left_open_generation: - parent: minecraft:block/door_top_left_open - textures: *textures - model_top_right_open_path: minecraft:block/custom/palm_door_top_right_open - model_top_right_open_generation: - parent: minecraft:block/door_top_right_open - textures: *textures - model_bottom_left_path: minecraft:block/custom/palm_door_bottom_left - model_bottom_left_generation: - parent: minecraft:block/door_bottom_left - textures: *textures - model_bottom_right_path: minecraft:block/custom/palm_door_bottom_right - model_bottom_right_generation: - parent: minecraft:block/door_bottom_right - textures: *textures - model_bottom_left_open_path: minecraft:block/custom/palm_door_bottom_left_open - model_bottom_left_open_generation: - parent: minecraft:block/door_bottom_left_open - textures: *textures - model_bottom_right_open_path: minecraft:block/custom/palm_door_bottom_right_open - model_bottom_right_open_generation: - parent: minecraft:block/door_bottom_right_open - textures: *textures + block: default:palm_door default:palm_fence_gate: material: nether_brick custom-model-data: 1008 @@ -425,49 +172,7 @@ items: parent: minecraft:block/custom/palm_fence_gate behavior: type: block_item - block: - behaviors: - type: fence_gate_block - can-open-with-hand: true - can-open-by-wind-charge: true - sounds: - open: minecraft:block.fence_gate.open - close: minecraft:block.fence_gate.close - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/planks - overrides: - map-color: 2 - instrument: bass - burnable: true - tags: - - minecraft:fence_gates - - minecraft:mineable/axe - - minecraft:unstable_bottom_center - states: - template: default:block_state/fence_gate - arguments: - base_block: oak_fence_gate - model_fence_gate_path: minecraft:block/custom/palm_fence_gate - model_fence_gate_generation: - parent: minecraft:block/template_fence_gate - textures: &textures - texture: minecraft:block/custom/palm_planks - model_fence_gate_open_path: minecraft:block/custom/palm_fence_gate_open - model_fence_gate_open_generation: - parent: minecraft:block/template_fence_gate_open - textures: *textures - model_fence_gate_wall_path: minecraft:block/custom/palm_fence_gate_wall - model_fence_gate_wall_generation: - parent: minecraft:block/template_fence_gate_wall - textures: *textures - model_fence_gate_wall_open_path: minecraft:block/custom/palm_fence_gate_wall_open - model_fence_gate_wall_open_generation: - parent: minecraft:block/template_fence_gate_wall_open - textures: *textures + block: default:palm_fence_gate default:palm_slab: material: nether_brick custom-model-data: 1009 @@ -482,39 +187,7 @@ items: parent: minecraft:block/custom/palm_slab behavior: type: block_item - block: - behaviors: - type: slab_block - loot: - template: default:loot_table/slab - settings: - template: - - default:sound/wood - - default:burn_data/planks - - default:hardness/planks - overrides: - map-color: 2 - instrument: bass - tags: - - minecraft:wooden_slabs - - minecraft:slabs - - minecraft:mineable/axe - states: - template: default:block_state/slab - arguments: - base_block: petrified_oak_slab - model_bottom_path: minecraft:block/custom/palm_slab - model_bottom_generation: - parent: minecraft:block/slab - textures: &textures - bottom: minecraft:block/custom/palm_planks - side: minecraft:block/custom/palm_planks - top: minecraft:block/custom/palm_planks - model_top_path: minecraft:block/custom/palm_slab_top - model_top_generation: - parent: minecraft:block/slab_top - textures: *textures - model_double_path: minecraft:block/custom/palm_planks + block: default:palm_slab default:palm_stairs: material: nether_brick custom-model-data: 1013 @@ -529,42 +202,7 @@ items: fuel-time: 300 behavior: type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/planks - - default:burn_data/planks - overrides: - map-color: 2 - instrument: bass - tags: - - minecraft:mineable/axe - - minecraft:stairs - - minecraft:wooden_stairs - behavior: - type: stairs_block - states: - template: default:block_state/stairs - arguments: - base_block: cut_copper_stairs - model_stairs_inner_path: minecraft:block/custom/palm_stairs_inner - model_stairs_inner_generation: - parent: minecraft:block/inner_stairs - textures: &textures - bottom: &block_texture minecraft:block/custom/palm_planks - side: *block_texture - top: *block_texture - model_stairs_outer_path: minecraft:block/custom/palm_stairs_outer - model_stairs_outer_generation: - parent: minecraft:block/outer_stairs - textures: *textures - model_stairs_path: minecraft:block/custom/palm_stairs - model_stairs_generation: - parent: minecraft:block/stairs - textures: *textures + block: default:palm_stairs default:palm_pressure_plate: material: nether_brick custom-model-data: 1014 @@ -579,47 +217,737 @@ items: fuel-time: 300 behavior: type: block_item - block: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/planks - overrides: - burnable: true - push-reaction: destroy - map-color: 2 - instrument: bass - tags: - - minecraft:mineable/axe - - minecraft:wall_post_override - - minecraft:wooden_pressure_plates - - minecraft:pressure_plates - behaviors: - type: pressure_plate_block - sensitivity: all - pressed-time: 20 - sounds: - on: minecraft:block.wooden_pressure_plate.click_on - off: minecraft:block.wooden_pressure_plate.click_off - states: - template: default:block_state/pressure_plate - arguments: - normal_state: light_weighted_pressure_plate:0 - powered_state: light_weighted_pressure_plate:1 - normal_id: 0 - powered_id: 1 - model_normal_path: minecraft:block/custom/palm_pressure_plate - model_normal_generation: - parent: minecraft:block/pressure_plate_up + block: default:palm_pressure_plate + default:palm_button: + material: nether_brick + custom-model-data: 1015 + model: + type: minecraft:model + path: minecraft:item/custom/palm_button + generation: + parent: minecraft:block/button_inventory + textures: + texture: minecraft:block/custom/palm_planks + data: + item-name: + settings: + fuel-time: 100 + behavior: + type: block_item + block: default:palm_button + default:palm_button_pressed: + material: nether_brick + custom-model-data: 1016 + model: + type: minecraft:model + path: minecraft:block/custom/palm_button_pressed + generation: + parent: minecraft:block/button_pressed + textures: + texture: minecraft:block/custom/palm_planks + default:palm_button_not_pressed: + material: nether_brick + custom-model-data: 1017 + model: + type: minecraft:model + path: minecraft:block/custom/palm_button_not_pressed + generation: + parent: minecraft:block/button + textures: + texture: minecraft:block/custom/palm_planks + +blocks: + default:palm_log: + behavior: + type: strippable_block + stripped: default:stripped_palm_log + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/palm_log_top + texture_side_path: minecraft:block/custom/palm_log + model_vertical_path: minecraft:block/custom/palm_log + model_horizontal_path: minecraft:block/custom/palm_log_horizontal + vanilla_id: + type: self_increase_int + from: 0 + to: 2 + internal_id: + type: self_increase_int + from: 0 + to: 2 + default:stripped_palm_log: + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/stripped_palm_log_top + texture_side_path: minecraft:block/custom/stripped_palm_log + model_vertical_path: minecraft:block/custom/stripped_palm_log + model_horizontal_path: minecraft:block/custom/stripped_palm_log_horizontal + vanilla_id: + type: self_increase_int + from: 3 + to: 5 + internal_id: + type: self_increase_int + from: 3 + to: 5 + default:palm_wood: + behavior: + type: strippable_block + stripped: default:stripped_palm_wood + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/palm_log + texture_side_path: minecraft:block/custom/palm_log + model_vertical_path: minecraft:block/custom/palm_wood + model_horizontal_path: minecraft:block/custom/palm_wood_horizontal + vanilla_id: + type: self_increase_int + from: 6 + to: 8 + internal_id: + type: self_increase_int + from: 6 + to: 8 + default:stripped_palm_wood: + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/stripped_palm_log + texture_side_path: minecraft:block/custom/stripped_palm_log + model_vertical_path: minecraft:block/custom/stripped_palm_wood + model_horizontal_path: minecraft:block/custom/stripped_palm_wood_horizontal + vanilla_id: + type: self_increase_int + from: 9 + to: 11 + internal_id: + type: self_increase_int + from: 9 + to: 11 + default:palm_planks: + settings: + template: default:settings/planks + loot: + template: default:loot_table/self + state: + model: + template: default:model/simplified_cube_all + arguments: + path: minecraft:block/custom/palm_planks + id: 12 + state: note_block:12 + default:palm_sapling: + settings: + template: default:settings/sapling + behaviors: + - type: bush_block + bottom-block-tags: + - minecraft:dirt + - minecraft:farmland + - minecraft:sand + - type: sapling_block + feature: minecraft:fancy_oak + bone-meal-success-chance: 0.45 + loot: + template: default:loot_table/self + states: + properties: + stage: + type: int + default-value: 0 + range: 0~1 + appearances: + default: + state: oak_sapling:0 + model: + path: minecraft:block/custom/palm_sapling + generation: + parent: minecraft:block/cross textures: - texture: minecraft:block/custom/palm_planks - model_powered_path: minecraft:block/custom/palm_pressure_plate_down - model_powered_generation: - parent: minecraft:block/pressure_plate_down - textures: - texture: minecraft:block/custom/palm_planks + cross: minecraft:block/custom/palm_sapling + variants: + stage=0: + appearance: default + id: 0 + stage=1: + appearance: default + id: 1 + default:palm_leaves: + behavior: + type: leaves_block + loot: + template: default:loot_table/leaves + arguments: + leaves: default:palm_leaves + sapling: default:palm_sapling + settings: + template: default:settings/leaves + states: + template: default:block_state/leaves + arguments: + default_state: oak_leaves[distance=1,persistent=false,waterlogged=false] + waterlogged_state: oak_leaves[distance=1,persistent=false,waterlogged=true] + model_path: minecraft:block/custom/palm_leaves + texture_path: minecraft:block/custom/palm_leaves + internal_id: + type: self_increase_int + from: 0 + to: 27 + default:palm_trapdoor: + behavior: + type: trapdoor_block + can-open-with-hand: true + can-open-by-wind-charge: true + sounds: + open: minecraft:block.wooden_trapdoor.open + close: minecraft:block.wooden_trapdoor.close + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + overrides: + map-color: 2 + instrument: bass + hardness: 3.0 + resistance: 3.0 + burnable: true + tags: + - minecraft:mineable/axe + - minecraft:trapdoors + states: + template: default:block_state/trapdoor + arguments: + base_block: acacia_trapdoor + model_bottom_path: minecraft:block/custom/palm_trapdoor_bottom + model_bottom_generation: + parent: minecraft:block/template_orientable_trapdoor_bottom + textures: + texture: minecraft:block/custom/palm_trapdoor + model_open_path: minecraft:block/custom/palm_trapdoor_open + model_open_generation: + parent: minecraft:block/template_orientable_trapdoor_open + textures: + texture: minecraft:block/custom/palm_trapdoor + model_top_path: minecraft:block/custom/palm_trapdoor_top + model_top_generation: + parent: minecraft:block/template_orientable_trapdoor_top + textures: + texture: minecraft:block/custom/palm_trapdoor + default:palm_door: + behavior: + type: door_block + can-open-with-hand: true + can-open-by-wind-charge: true + sounds: + open: minecraft:block.wooden_door.open + close: minecraft:block.wooden_door.close + loot: + template: default:loot_table/door + settings: + template: + - default:sound/wood + overrides: + push-reaction: destroy + map-color: 2 + instrument: bass + hardness: 3.0 + resistance: 3.0 + burnable: true + tags: + - minecraft:wooden_doors + - minecraft:doors + - minecraft:mineable/axe + states: + template: default:block_state/door + arguments: + base_block: oak_door + model_top_left_path: minecraft:block/custom/palm_door_top_left + model_top_left_generation: + parent: minecraft:block/door_top_left + textures: &textures + bottom: minecraft:block/custom/palm_door_bottom + top: minecraft:block/custom/palm_door_top + model_top_right_path: minecraft:block/custom/palm_door_top_right + model_top_right_generation: + parent: minecraft:block/door_top_right + textures: *textures + model_top_left_open_path: minecraft:block/custom/palm_door_top_left_open + model_top_left_open_generation: + parent: minecraft:block/door_top_left_open + textures: *textures + model_top_right_open_path: minecraft:block/custom/palm_door_top_right_open + model_top_right_open_generation: + parent: minecraft:block/door_top_right_open + textures: *textures + model_bottom_left_path: minecraft:block/custom/palm_door_bottom_left + model_bottom_left_generation: + parent: minecraft:block/door_bottom_left + textures: *textures + model_bottom_right_path: minecraft:block/custom/palm_door_bottom_right + model_bottom_right_generation: + parent: minecraft:block/door_bottom_right + textures: *textures + model_bottom_left_open_path: minecraft:block/custom/palm_door_bottom_left_open + model_bottom_left_open_generation: + parent: minecraft:block/door_bottom_left_open + textures: *textures + model_bottom_right_open_path: minecraft:block/custom/palm_door_bottom_right_open + model_bottom_right_open_generation: + parent: minecraft:block/door_bottom_right_open + textures: *textures + default:palm_fence_gate: + behaviors: + type: fence_gate_block + can-open-with-hand: true + can-open-by-wind-charge: true + sounds: + open: minecraft:block.fence_gate.open + close: minecraft:block.fence_gate.close + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/planks + overrides: + map-color: 2 + instrument: bass + burnable: true + tags: + - minecraft:fence_gates + - minecraft:mineable/axe + - minecraft:unstable_bottom_center + states: + template: default:block_state/fence_gate + arguments: + base_block: oak_fence_gate + model_fence_gate_path: minecraft:block/custom/palm_fence_gate + model_fence_gate_generation: + parent: minecraft:block/template_fence_gate + textures: &textures + texture: minecraft:block/custom/palm_planks + model_fence_gate_open_path: minecraft:block/custom/palm_fence_gate_open + model_fence_gate_open_generation: + parent: minecraft:block/template_fence_gate_open + textures: *textures + model_fence_gate_wall_path: minecraft:block/custom/palm_fence_gate_wall + model_fence_gate_wall_generation: + parent: minecraft:block/template_fence_gate_wall + textures: *textures + model_fence_gate_wall_open_path: minecraft:block/custom/palm_fence_gate_wall_open + model_fence_gate_wall_open_generation: + parent: minecraft:block/template_fence_gate_wall_open + textures: *textures + default:palm_slab: + behaviors: + type: slab_block + loot: + template: default:loot_table/slab + settings: + template: + - default:sound/wood + - default:burn_data/planks + - default:hardness/planks + overrides: + map-color: 2 + instrument: bass + tags: + - minecraft:wooden_slabs + - minecraft:slabs + - minecraft:mineable/axe + states: + template: default:block_state/slab + arguments: + base_block: petrified_oak_slab + model_bottom_path: minecraft:block/custom/palm_slab + model_bottom_generation: + parent: minecraft:block/slab + textures: &textures + bottom: minecraft:block/custom/palm_planks + side: minecraft:block/custom/palm_planks + top: minecraft:block/custom/palm_planks + model_top_path: minecraft:block/custom/palm_slab_top + model_top_generation: + parent: minecraft:block/slab_top + textures: *textures + model_double_path: minecraft:block/custom/palm_planks + default:palm_stairs: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/planks + - default:burn_data/planks + overrides: + map-color: 2 + instrument: bass + tags: + - minecraft:mineable/axe + - minecraft:stairs + - minecraft:wooden_stairs + behavior: + type: stairs_block + states: + template: default:block_state/stairs + arguments: + base_block: cut_copper_stairs + model_stairs_inner_path: minecraft:block/custom/palm_stairs_inner + model_stairs_inner_generation: + parent: minecraft:block/inner_stairs + textures: &textures + bottom: &block_texture minecraft:block/custom/palm_planks + side: *block_texture + top: *block_texture + model_stairs_outer_path: minecraft:block/custom/palm_stairs_outer + model_stairs_outer_generation: + parent: minecraft:block/outer_stairs + textures: *textures + model_stairs_path: minecraft:block/custom/palm_stairs + model_stairs_generation: + parent: minecraft:block/stairs + textures: *textures + default:palm_pressure_plate: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/planks + overrides: + burnable: true + push-reaction: destroy + map-color: 2 + instrument: bass + tags: + - minecraft:mineable/axe + - minecraft:wall_post_override + - minecraft:wooden_pressure_plates + - minecraft:pressure_plates + behaviors: + type: pressure_plate_block + sensitivity: all + pressed-time: 20 + sounds: + on: minecraft:block.wooden_pressure_plate.click_on + off: minecraft:block.wooden_pressure_plate.click_off + states: + template: default:block_state/pressure_plate + arguments: + normal_state: light_weighted_pressure_plate:0 + powered_state: light_weighted_pressure_plate:1 + normal_id: 0 + powered_id: 1 + model_normal_path: minecraft:block/custom/palm_pressure_plate + model_normal_generation: + parent: minecraft:block/pressure_plate_up + textures: + texture: minecraft:block/custom/palm_planks + model_powered_path: minecraft:block/custom/palm_pressure_plate_down + model_powered_generation: + parent: minecraft:block/pressure_plate_down + textures: + texture: minecraft:block/custom/palm_planks + default:palm_button: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/button + overrides: + burnable: true + push-reaction: destroy + map-color: 2 + instrument: harp + tags: + - minecraft:buttons + - minecraft:mineable/axe + - minecraft:wooden_buttons + behaviors: + - type: face_attached_horizontal_directional_block + - type: button_block + ticks-to-stay-pressed: 30 + can-button-be-activated-by-arrows: true + sounds: + on: minecraft:block.wooden_button.click_on + off: minecraft:block.wooden_button.click_off + states: + properties: + powered: + type: boolean + default: false + face: + type: anchor_type + default: floor + facing: + type: 4-direction + default: north + appearances: + face=floor,facing=east,powered=true: + state: birch_button[face=floor,facing=east,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=west,powered=true: + state: birch_button[face=floor,facing=west,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,-90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=east,powered=false: + state: birch_button[face=floor,facing=east,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=west,powered=false: + state: birch_button[face=floor,facing=west,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,-90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=south,powered=true: + state: birch_button[face=floor,facing=south,powered=true] + entity-renderer: + item: default:palm_button_pressed + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=north,powered=true: + state: birch_button[face=floor,facing=north,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,180,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=south,powered=false: + state: birch_button[face=floor,facing=south,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=north,powered=false: + state: birch_button[face=floor,facing=north,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,180,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=wall,facing=north,powered=true: + state: birch_button[face=wall,facing=north,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: -90,0,0 + scale: 1.0001 + translation: 0,0,-0.0001 + face=wall,facing=south,powered=true: + state: birch_button[face=wall,facing=south,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 90,0,180 + scale: 1.0001 + translation: 0,0,0.0001 + face=wall,facing=north,powered=false: + state: birch_button[face=wall,facing=north,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: -90,0,0 + scale: 1.0001 + translation: 0,0,-0.0001 + face=wall,facing=south,powered=false: + state: birch_button[face=wall,facing=south,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 90,0,180 + scale: 1.0001 + translation: 0,0,0.0001 + face=wall,facing=west,powered=true: + state: birch_button[face=wall,facing=west,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,90,90 + scale: 1.0001 + translation: -0.0001,0,0 + face=wall,facing=east,powered=true: + state: birch_button[face=wall,facing=east,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,270,-90 + scale: 1.0001 + translation: 0.0001,0,0 + face=wall,facing=west,powered=false: + state: birch_button[face=wall,facing=west,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,90,90 + scale: 1.0001 + translation: -0.0001,0,0 + face=wall,facing=east,powered=false: + state: birch_button[face=wall,facing=east,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,270,-90 + scale: 1.0001 + translation: 0.0001,0,0 + face=ceiling,facing=north,powered=true: + state: birch_button[face=ceiling,facing=north,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,180,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=south,powered=true: + state: birch_button[face=ceiling,facing=south,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,0,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=north,powered=false: + state: birch_button[face=ceiling,facing=north,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,180,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=south,powered=false: + state: birch_button[face=ceiling,facing=south,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,0,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=west,powered=true: + state: birch_button[face=ceiling,facing=west,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: 0,90,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=east,powered=true: + state: birch_button[face=ceiling,facing=east,powered=true] + entity-renderer: + item: default:palm_button_pressed + rotation: -90,-90,-90 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=west,powered=false: + state: birch_button[face=ceiling,facing=west,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: 0,90,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=east,powered=false: + state: birch_button[face=ceiling,facing=east,powered=false] + entity-renderer: + item: default:palm_button_not_pressed + rotation: -90,-90,-90 + scale: 1.0001 + translation: 0,-0.0001,0 + variants: + face=floor,facing=east,powered=true: + appearance: face=floor,facing=east,powered=true + id: 0 + face=floor,facing=west,powered=true: + appearance: face=floor,facing=west,powered=true + id: 1 + face=floor,facing=east,powered=false: + appearance: face=floor,facing=east,powered=false + id: 2 + face=floor,facing=west,powered=false: + appearance: face=floor,facing=west,powered=false + id: 3 + face=floor,facing=south,powered=true: + appearance: face=floor,facing=south,powered=true + id: 4 + face=floor,facing=north,powered=true: + appearance: face=floor,facing=north,powered=true + id: 5 + face=floor,facing=south,powered=false: + appearance: face=floor,facing=south,powered=false + id: 6 + face=floor,facing=north,powered=false: + appearance: face=floor,facing=north,powered=false + id: 7 + face=wall,facing=north,powered=true: + appearance: face=wall,facing=north,powered=true + id: 8 + face=wall,facing=south,powered=true: + appearance: face=wall,facing=south,powered=true + id: 9 + face=wall,facing=north,powered=false: + appearance: face=wall,facing=north,powered=false + id: 10 + face=wall,facing=south,powered=false: + appearance: face=wall,facing=south,powered=false + id: 11 + face=wall,facing=west,powered=true: + appearance: face=wall,facing=west,powered=true + id: 12 + face=wall,facing=east,powered=true: + appearance: face=wall,facing=east,powered=true + id: 13 + face=wall,facing=west,powered=false: + appearance: face=wall,facing=west,powered=false + id: 14 + face=wall,facing=east,powered=false: + appearance: face=wall,facing=east,powered=false + id: 15 + face=ceiling,facing=north,powered=true: + appearance: face=ceiling,facing=north,powered=true + id: 16 + face=ceiling,facing=south,powered=true: + appearance: face=ceiling,facing=south,powered=true + id: 17 + face=ceiling,facing=north,powered=false: + appearance: face=ceiling,facing=north,powered=false + id: 18 + face=ceiling,facing=south,powered=false: + appearance: face=ceiling,facing=south,powered=false + id: 19 + face=ceiling,facing=west,powered=true: + appearance: face=ceiling,facing=west,powered=true + id: 20 + face=ceiling,facing=east,powered=true: + appearance: face=ceiling,facing=east,powered=true + id: 21 + face=ceiling,facing=west,powered=false: + appearance: face=ceiling,facing=west,powered=false + id: 22 + face=ceiling,facing=east,powered=false: + appearance: face=ceiling,facing=east,powered=false + id: 23 + recipes: default:palm_planks: template: default:recipe/planks @@ -687,11 +1015,18 @@ recipes: default:palm_stairs: type: shaped pattern: - - 'A ' - - 'AA ' - - 'AAA' + - "A " + - "AA " + - "AAA" ingredients: A: default:palm_planks result: id: default:palm_stairs count: 4 + default:palm_button: + type: shapeless + ingredients: + - default:palm_planks + result: + id: default:palm_button + count: 1 diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 7719dacfe..56580ba5c 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -27,6 +27,7 @@ categories: - default:palm_slab - default:palm_stairs - default:palm_pressure_plate + - default:palm_button default:topaz: name: <#FF8C00> hidden: true diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index df4fa5f14..c6d5d3482 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -52,6 +52,7 @@ i18n: item.hami_melon_slice: Hami Melon Slice item.hami_melon: Hami Melon item.hami_melon_seeds: Hami Melon Seeds + item.palm_button: Palm Button category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -113,6 +114,7 @@ i18n: item.hami_melon_slice: 哈密瓜片 item.hami_melon: 哈密瓜 item.hami_melon_seeds: 哈密瓜种子 + item.palm_button: 棕榈木按钮 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -158,6 +160,7 @@ lang: block_name:default:hami_melon: Hami Melon block_name:default:hami_melon_stem: Hami Melon Stem block_name:default:default:attached_hami_melon_stem: Hami Melon Stem + block_name:default:palm_button: Palm Button zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -190,4 +193,5 @@ lang: block_name:default:amethyst_wall_torch: 紫水晶火把 block_name:default:hami_melon: 哈密瓜 block_name:default:hami_melon_stem: 哈密瓜茎 - block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 \ No newline at end of file + block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 + block_name:default:palm_button: 棕榈木按钮 From 383d296ce4f541541cde845a5e02218a8d982f10 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 06:44:00 +0800 Subject: [PATCH 177/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/resources/default/configuration/templates.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 8e2458b32..6b7a0464b 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -693,6 +693,9 @@ templates#settings#hardness: default:hardness/planks: hardness: 2.0 resistance: 3.0 + default:hardness/button: + hardness: 0.5 + resistance: 0.5 # break level templates#settings#break_level: From 7d90c9bb95128dd5a052ab5fb9d2fc9d8ca1fccf Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 06:54:13 +0800 Subject: [PATCH 178/226] =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=88=B0=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/blocks/palm_tree.yml | 259 +----------------- .../default/configuration/templates.yml | 251 +++++++++++++++++ 2 files changed, 260 insertions(+), 250 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 7e935d5d5..5f8971688 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -697,256 +697,15 @@ blocks: on: minecraft:block.wooden_button.click_on off: minecraft:block.wooden_button.click_off states: - properties: - powered: - type: boolean - default: false - face: - type: anchor_type - default: floor - facing: - type: 4-direction - default: north - appearances: - face=floor,facing=east,powered=true: - state: birch_button[face=floor,facing=east,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,90,0 - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=west,powered=true: - state: birch_button[face=floor,facing=west,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,-90,0 - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=east,powered=false: - state: birch_button[face=floor,facing=east,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,90,0 - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=west,powered=false: - state: birch_button[face=floor,facing=west,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,-90,0 - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=south,powered=true: - state: birch_button[face=floor,facing=south,powered=true] - entity-renderer: - item: default:palm_button_pressed - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=north,powered=true: - state: birch_button[face=floor,facing=north,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,180,0 - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=south,powered=false: - state: birch_button[face=floor,facing=south,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - scale: 1.0001 - translation: 0,0.0001,0 - face=floor,facing=north,powered=false: - state: birch_button[face=floor,facing=north,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,180,0 - scale: 1.0001 - translation: 0,0.0001,0 - face=wall,facing=north,powered=true: - state: birch_button[face=wall,facing=north,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: -90,0,0 - scale: 1.0001 - translation: 0,0,-0.0001 - face=wall,facing=south,powered=true: - state: birch_button[face=wall,facing=south,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 90,0,180 - scale: 1.0001 - translation: 0,0,0.0001 - face=wall,facing=north,powered=false: - state: birch_button[face=wall,facing=north,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: -90,0,0 - scale: 1.0001 - translation: 0,0,-0.0001 - face=wall,facing=south,powered=false: - state: birch_button[face=wall,facing=south,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 90,0,180 - scale: 1.0001 - translation: 0,0,0.0001 - face=wall,facing=west,powered=true: - state: birch_button[face=wall,facing=west,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,90,90 - scale: 1.0001 - translation: -0.0001,0,0 - face=wall,facing=east,powered=true: - state: birch_button[face=wall,facing=east,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,270,-90 - scale: 1.0001 - translation: 0.0001,0,0 - face=wall,facing=west,powered=false: - state: birch_button[face=wall,facing=west,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,90,90 - scale: 1.0001 - translation: -0.0001,0,0 - face=wall,facing=east,powered=false: - state: birch_button[face=wall,facing=east,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,270,-90 - scale: 1.0001 - translation: 0.0001,0,0 - face=ceiling,facing=north,powered=true: - state: birch_button[face=ceiling,facing=north,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,180,180 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=south,powered=true: - state: birch_button[face=ceiling,facing=south,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,0,180 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=north,powered=false: - state: birch_button[face=ceiling,facing=north,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,180,180 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=south,powered=false: - state: birch_button[face=ceiling,facing=south,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,0,180 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=west,powered=true: - state: birch_button[face=ceiling,facing=west,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: 0,90,180 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=east,powered=true: - state: birch_button[face=ceiling,facing=east,powered=true] - entity-renderer: - item: default:palm_button_pressed - rotation: -90,-90,-90 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=west,powered=false: - state: birch_button[face=ceiling,facing=west,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: 0,90,180 - scale: 1.0001 - translation: 0,-0.0001,0 - face=ceiling,facing=east,powered=false: - state: birch_button[face=ceiling,facing=east,powered=false] - entity-renderer: - item: default:palm_button_not_pressed - rotation: -90,-90,-90 - scale: 1.0001 - translation: 0,-0.0001,0 - variants: - face=floor,facing=east,powered=true: - appearance: face=floor,facing=east,powered=true - id: 0 - face=floor,facing=west,powered=true: - appearance: face=floor,facing=west,powered=true - id: 1 - face=floor,facing=east,powered=false: - appearance: face=floor,facing=east,powered=false - id: 2 - face=floor,facing=west,powered=false: - appearance: face=floor,facing=west,powered=false - id: 3 - face=floor,facing=south,powered=true: - appearance: face=floor,facing=south,powered=true - id: 4 - face=floor,facing=north,powered=true: - appearance: face=floor,facing=north,powered=true - id: 5 - face=floor,facing=south,powered=false: - appearance: face=floor,facing=south,powered=false - id: 6 - face=floor,facing=north,powered=false: - appearance: face=floor,facing=north,powered=false - id: 7 - face=wall,facing=north,powered=true: - appearance: face=wall,facing=north,powered=true - id: 8 - face=wall,facing=south,powered=true: - appearance: face=wall,facing=south,powered=true - id: 9 - face=wall,facing=north,powered=false: - appearance: face=wall,facing=north,powered=false - id: 10 - face=wall,facing=south,powered=false: - appearance: face=wall,facing=south,powered=false - id: 11 - face=wall,facing=west,powered=true: - appearance: face=wall,facing=west,powered=true - id: 12 - face=wall,facing=east,powered=true: - appearance: face=wall,facing=east,powered=true - id: 13 - face=wall,facing=west,powered=false: - appearance: face=wall,facing=west,powered=false - id: 14 - face=wall,facing=east,powered=false: - appearance: face=wall,facing=east,powered=false - id: 15 - face=ceiling,facing=north,powered=true: - appearance: face=ceiling,facing=north,powered=true - id: 16 - face=ceiling,facing=south,powered=true: - appearance: face=ceiling,facing=south,powered=true - id: 17 - face=ceiling,facing=north,powered=false: - appearance: face=ceiling,facing=north,powered=false - id: 18 - face=ceiling,facing=south,powered=false: - appearance: face=ceiling,facing=south,powered=false - id: 19 - face=ceiling,facing=west,powered=true: - appearance: face=ceiling,facing=west,powered=true - id: 20 - face=ceiling,facing=east,powered=true: - appearance: face=ceiling,facing=east,powered=true - id: 21 - face=ceiling,facing=west,powered=false: - appearance: face=ceiling,facing=west,powered=false - id: 22 - face=ceiling,facing=east,powered=false: - appearance: face=ceiling,facing=east,powered=false - id: 23 + template: default:block_state/button + arguments: + base_block: birch_button + pressed_item: default:palm_button_pressed + not_pressed_item: default:palm_button_not_pressed + internal_id: + type: self_increase_int + from: 0 + to: 23 recipes: default:palm_planks: diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 6b7a0464b..b204df7ba 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -3138,6 +3138,257 @@ templates#block_states: powered=true: appearance: powered id: ${powered_id} + default:block_state/button: + properties: + powered: + type: boolean + default: false + face: + type: anchor_type + default: floor + facing: + type: 4-direction + default: north + appearances: + face=floor,facing=east,powered=true: + state: ${base_block}[face=floor,facing=east,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=west,powered=true: + state: ${base_block}[face=floor,facing=west,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,-90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=east,powered=false: + state: ${base_block}[face=floor,facing=east,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=west,powered=false: + state: ${base_block}[face=floor,facing=west,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,-90,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=south,powered=true: + state: ${base_block}[face=floor,facing=south,powered=true] + entity-renderer: + item: ${pressed_item} + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=north,powered=true: + state: ${base_block}[face=floor,facing=north,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,180,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=south,powered=false: + state: ${base_block}[face=floor,facing=south,powered=false] + entity-renderer: + item: ${not_pressed_item} + scale: 1.0001 + translation: 0,0.0001,0 + face=floor,facing=north,powered=false: + state: ${base_block}[face=floor,facing=north,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,180,0 + scale: 1.0001 + translation: 0,0.0001,0 + face=wall,facing=north,powered=true: + state: ${base_block}[face=wall,facing=north,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: -90,0,0 + scale: 1.0001 + translation: 0,0,-0.0001 + face=wall,facing=south,powered=true: + state: ${base_block}[face=wall,facing=south,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 90,0,180 + scale: 1.0001 + translation: 0,0,0.0001 + face=wall,facing=north,powered=false: + state: ${base_block}[face=wall,facing=north,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: -90,0,0 + scale: 1.0001 + translation: 0,0,-0.0001 + face=wall,facing=south,powered=false: + state: ${base_block}[face=wall,facing=south,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 90,0,180 + scale: 1.0001 + translation: 0,0,0.0001 + face=wall,facing=west,powered=true: + state: ${base_block}[face=wall,facing=west,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,90,90 + scale: 1.0001 + translation: -0.0001,0,0 + face=wall,facing=east,powered=true: + state: ${base_block}[face=wall,facing=east,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,270,-90 + scale: 1.0001 + translation: 0.0001,0,0 + face=wall,facing=west,powered=false: + state: ${base_block}[face=wall,facing=west,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,90,90 + scale: 1.0001 + translation: -0.0001,0,0 + face=wall,facing=east,powered=false: + state: ${base_block}[face=wall,facing=east,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,270,-90 + scale: 1.0001 + translation: 0.0001,0,0 + face=ceiling,facing=north,powered=true: + state: ${base_block}[face=ceiling,facing=north,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,180,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=south,powered=true: + state: ${base_block}[face=ceiling,facing=south,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,0,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=north,powered=false: + state: ${base_block}[face=ceiling,facing=north,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,180,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=south,powered=false: + state: ${base_block}[face=ceiling,facing=south,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,0,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=west,powered=true: + state: ${base_block}[face=ceiling,facing=west,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: 0,90,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=east,powered=true: + state: ${base_block}[face=ceiling,facing=east,powered=true] + entity-renderer: + item: ${pressed_item} + rotation: -90,-90,-90 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=west,powered=false: + state: ${base_block}[face=ceiling,facing=west,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: 0,90,180 + scale: 1.0001 + translation: 0,-0.0001,0 + face=ceiling,facing=east,powered=false: + state: ${base_block}[face=ceiling,facing=east,powered=false] + entity-renderer: + item: ${not_pressed_item} + rotation: -90,-90,-90 + scale: 1.0001 + translation: 0,-0.0001,0 + variants: + face=floor,facing=east,powered=true: + appearance: face=floor,facing=east,powered=true + id: ${internal_id} + face=floor,facing=west,powered=true: + appearance: face=floor,facing=west,powered=true + id: ${internal_id} + face=floor,facing=east,powered=false: + appearance: face=floor,facing=east,powered=false + id: ${internal_id} + face=floor,facing=west,powered=false: + appearance: face=floor,facing=west,powered=false + id: ${internal_id} + face=floor,facing=south,powered=true: + appearance: face=floor,facing=south,powered=true + id: ${internal_id} + face=floor,facing=north,powered=true: + appearance: face=floor,facing=north,powered=true + id: ${internal_id} + face=floor,facing=south,powered=false: + appearance: face=floor,facing=south,powered=false + id: ${internal_id} + face=floor,facing=north,powered=false: + appearance: face=floor,facing=north,powered=false + id: ${internal_id} + face=wall,facing=north,powered=true: + appearance: face=wall,facing=north,powered=true + id: ${internal_id} + face=wall,facing=south,powered=true: + appearance: face=wall,facing=south,powered=true + id: ${internal_id} + face=wall,facing=north,powered=false: + appearance: face=wall,facing=north,powered=false + id: ${internal_id} + face=wall,facing=south,powered=false: + appearance: face=wall,facing=south,powered=false + id: ${internal_id} + face=wall,facing=west,powered=true: + appearance: face=wall,facing=west,powered=true + id: ${internal_id} + face=wall,facing=east,powered=true: + appearance: face=wall,facing=east,powered=true + id: ${internal_id} + face=wall,facing=west,powered=false: + appearance: face=wall,facing=west,powered=false + id: ${internal_id} + face=wall,facing=east,powered=false: + appearance: face=wall,facing=east,powered=false + id: ${internal_id} + face=ceiling,facing=north,powered=true: + appearance: face=ceiling,facing=north,powered=true + id: ${internal_id} + face=ceiling,facing=south,powered=true: + appearance: face=ceiling,facing=south,powered=true + id: ${internal_id} + face=ceiling,facing=north,powered=false: + appearance: face=ceiling,facing=north,powered=false + id: ${internal_id} + face=ceiling,facing=south,powered=false: + appearance: face=ceiling,facing=south,powered=false + id: ${internal_id} + face=ceiling,facing=west,powered=true: + appearance: face=ceiling,facing=west,powered=true + id: ${internal_id} + face=ceiling,facing=east,powered=true: + appearance: face=ceiling,facing=east,powered=true + id: ${internal_id} + face=ceiling,facing=west,powered=false: + appearance: face=ceiling,facing=west,powered=false + id: ${internal_id} + face=ceiling,facing=east,powered=false: + appearance: face=ceiling,facing=east,powered=false + id: ${internal_id} # recipes templates#recipes: default:recipe/planks: From ff98255791a511380bbef90bf1f9c360484d05e1 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 08:52:39 +0800 Subject: [PATCH 179/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=85=E6=A0=8F?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/blocks/hami_melon.yml | 70 +- .../configuration/blocks/palm_tree.yml | 76 +++ .../default/configuration/categories.yml | 1 + .../resources/default/configuration/i18n.yml | 4 + .../default/configuration/templates.yml | 637 ++++++++++++++++-- .../models/block/custom/fence_side.json | 29 + .../core/pack/AbstractPackManager.java | 1 + 7 files changed, 738 insertions(+), 80 deletions(-) create mode 100644 common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml index 3d2cb6e37..356d62b63 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml @@ -69,16 +69,18 @@ blocks: max: 9 - type: explosion_decay settings: - map-color: 19 - hardness: 1 - resistance: 1 - push-reaction: DESTROY - is-suffocating: true - is-redstone-conductor: true - tags: - - minecraft:enderman_holdable - - minecraft:mineable/axe - - minecraft:sword_efficient + template: + - default:sound/wood + - default:hardness/melon + overrides: + map-color: 19 + push-reaction: DESTROY + is-suffocating: true + is-redstone-conductor: true + tags: + - minecraft:enderman_holdable + - minecraft:mineable/axe + - minecraft:sword_efficient state: id: 30 state: note_block:30 @@ -184,18 +186,20 @@ blocks: functions: - type: explosion_decay settings: - map-color: 7 - hardness: 0 - resistance: 0 - push-reaction: DESTROY - is-suffocating: false - is-redstone-conductor: false - item: default:hami_melon_seeds - is-randomly-ticking: true - tags: - - minecraft:bee_growables - - minecraft:crops - - minecraft:maintains_farmland + template: + - default:sound/stem_crop + - default:hardness/none + overrides: + map-color: 7 + push-reaction: DESTROY + is-suffocating: false + is-redstone-conductor: false + item: default:hami_melon_seeds + is-randomly-ticking: true + tags: + - minecraft:bee_growables + - minecraft:crops + - minecraft:maintains_farmland behaviors: - type: stem_block fruit: default:hami_melon @@ -268,16 +272,18 @@ blocks: functions: - type: explosion_decay settings: - map-color: 7 - hardness: 0 - resistance: 0 - push-reaction: DESTROY - is-suffocating: false - is-redstone-conductor: false - item: default:hami_melon_seeds - is-randomly-ticking: true - tags: - - minecraft:maintains_farmland + template: + - default:sound/stem_crop + - default:hardness/none + overrides: + map-color: 7 + push-reaction: DESTROY + is-suffocating: false + is-redstone-conductor: false + item: default:hami_melon_seeds + is-randomly-ticking: true + tags: + - minecraft:maintains_farmland behaviors: - type: attached_stem_block fruit: default:hami_melon diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 5f8971688..47523c47b 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -255,6 +255,38 @@ items: parent: minecraft:block/button textures: texture: minecraft:block/custom/palm_planks + default:palm_fence: + material: nether_brick + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/palm_fence_inventory + generation: + parent: minecraft:block/fence_inventory + textures: + texture: minecraft:block/custom/palm_planks + behavior: + type: block_item + block: default:palm_fence + default:palm_fence_post: + material: nether_brick + model: + type: minecraft:model + path: minecraft:block/custom/palm_fence_post + generation: + parent: minecraft:block/fence_post + textures: + texture: minecraft:block/custom/palm_planks + default:palm_fence_side: + material: nether_brick + model: + type: minecraft:model + path: minecraft:block/custom/palm_fence_side + generation: + parent: minecraft:block/custom/fence_side + textures: + texture: minecraft:block/custom/palm_planks blocks: default:palm_log: @@ -706,6 +738,39 @@ blocks: type: self_increase_int from: 0 to: 23 + default:palm_fence: + settings: + template: + - default:hardness/planks + - default:sound/wood + overrides: + burn-chance: 5 + fire-spread-chance: 20 + burnable: true + is-suffocating: false + is-redstone-conductor: false + push-reaction: NORMAL + instrument: BASS + map-color: 2 + tags: + - minecraft:fences + - minecraft:mineable/axe + - minecraft:wooden_fences + behavior: + type: fence_block + self-tag: fences + connectable-tag: wooden_fences + can-leash: true + states: + template: default:block_state/fence + arguments: + base_block: oak_fence + fence_post_item: default:palm_fence_post + fence_side_item: default:palm_fence_side + internal_id: + type: self_increase_int + from: 0 + to: 31 recipes: default:palm_planks: @@ -789,3 +854,14 @@ recipes: result: id: default:palm_button count: 1 + default:palm_fence: + type: shaped + pattern: + - ABA + - ABA + ingredients: + A: default:palm_planks + B: minecraft:stick + result: + id: default:palm_fence + count: 1 diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 56580ba5c..2d911abe4 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -28,6 +28,7 @@ categories: - default:palm_stairs - default:palm_pressure_plate - default:palm_button + - default:palm_fence default:topaz: name: <#FF8C00> hidden: true diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index c6d5d3482..71d6e9e30 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -53,6 +53,7 @@ i18n: item.hami_melon: Hami Melon item.hami_melon_seeds: Hami Melon Seeds item.palm_button: Palm Button + item.palm_fence: Palm Fence category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -115,6 +116,7 @@ i18n: item.hami_melon: 哈密瓜 item.hami_melon_seeds: 哈密瓜种子 item.palm_button: 棕榈木按钮 + item.palm_fence: 棕榈木栅栏 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -161,6 +163,7 @@ lang: block_name:default:hami_melon_stem: Hami Melon Stem block_name:default:default:attached_hami_melon_stem: Hami Melon Stem block_name:default:palm_button: Palm Button + block_name:default:palm_fence: Palm Fence zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -195,3 +198,4 @@ lang: block_name:default:hami_melon_stem: 哈密瓜茎 block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 block_name:default:palm_button: 棕榈木按钮 + block_name:default:palm_fence: 棕榈木栅栏 diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index b204df7ba..fa284b068 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -576,6 +576,13 @@ templates#settings#sounds: place: minecraft:item.crop.plant hit: minecraft:block.grass.hit fall: minecraft:block.grass.fall + default:sound/stem_crop: + sounds: + break: minecraft:block.crop.break + step: minecraft:block.wood.step + place: minecraft:item.crop.plant + hit: minecraft:block.wood.hit + fall: minecraft:block.wood.fall default:sound/grass: template: default:sound/block_template arguments: @@ -696,6 +703,9 @@ templates#settings#hardness: default:hardness/button: hardness: 0.5 resistance: 0.5 + default:hardness/melon: + hardness: 1.0 + resistance: 1.0 # break level templates#settings#break_level: @@ -3155,167 +3165,167 @@ templates#block_states: entity-renderer: item: ${pressed_item} rotation: 0,90,0 - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=west,powered=true: state: ${base_block}[face=floor,facing=west,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,-90,0 - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=east,powered=false: state: ${base_block}[face=floor,facing=east,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,90,0 - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=west,powered=false: state: ${base_block}[face=floor,facing=west,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,-90,0 - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=south,powered=true: state: ${base_block}[face=floor,facing=south,powered=true] entity-renderer: item: ${pressed_item} - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=north,powered=true: state: ${base_block}[face=floor,facing=north,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,180,0 - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=south,powered=false: state: ${base_block}[face=floor,facing=south,powered=false] entity-renderer: item: ${not_pressed_item} - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=floor,facing=north,powered=false: state: ${base_block}[face=floor,facing=north,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,180,0 - scale: 1.0001 - translation: 0,0.0001,0 + scale: 1.0005 + translation: 0,0.00023,0 face=wall,facing=north,powered=true: state: ${base_block}[face=wall,facing=north,powered=true] entity-renderer: item: ${pressed_item} rotation: -90,0,0 - scale: 1.0001 - translation: 0,0,-0.0001 + scale: 1.0005 + translation: 0,0,-0.00023 face=wall,facing=south,powered=true: state: ${base_block}[face=wall,facing=south,powered=true] entity-renderer: item: ${pressed_item} rotation: 90,0,180 - scale: 1.0001 - translation: 0,0,0.0001 + scale: 1.0005 + translation: 0,0,0.00023 face=wall,facing=north,powered=false: state: ${base_block}[face=wall,facing=north,powered=false] entity-renderer: item: ${not_pressed_item} rotation: -90,0,0 - scale: 1.0001 - translation: 0,0,-0.0001 + scale: 1.0005 + translation: 0,0,-0.00023 face=wall,facing=south,powered=false: state: ${base_block}[face=wall,facing=south,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 90,0,180 - scale: 1.0001 - translation: 0,0,0.0001 + scale: 1.0005 + translation: 0,0,0.00023 face=wall,facing=west,powered=true: state: ${base_block}[face=wall,facing=west,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,90,90 - scale: 1.0001 - translation: -0.0001,0,0 + scale: 1.0005 + translation: -0.00023,0,0 face=wall,facing=east,powered=true: state: ${base_block}[face=wall,facing=east,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,270,-90 - scale: 1.0001 - translation: 0.0001,0,0 + scale: 1.0005 + translation: 0.00023,0,0 face=wall,facing=west,powered=false: state: ${base_block}[face=wall,facing=west,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,90,90 - scale: 1.0001 - translation: -0.0001,0,0 + scale: 1.0005 + translation: -0.00023,0,0 face=wall,facing=east,powered=false: state: ${base_block}[face=wall,facing=east,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,270,-90 - scale: 1.0001 - translation: 0.0001,0,0 + scale: 1.0005 + translation: 0.00023,0,0 face=ceiling,facing=north,powered=true: state: ${base_block}[face=ceiling,facing=north,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,180,180 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=south,powered=true: state: ${base_block}[face=ceiling,facing=south,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,0,180 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=north,powered=false: state: ${base_block}[face=ceiling,facing=north,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,180,180 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=south,powered=false: state: ${base_block}[face=ceiling,facing=south,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,0,180 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=west,powered=true: state: ${base_block}[face=ceiling,facing=west,powered=true] entity-renderer: item: ${pressed_item} rotation: 0,90,180 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=east,powered=true: state: ${base_block}[face=ceiling,facing=east,powered=true] entity-renderer: item: ${pressed_item} rotation: -90,-90,-90 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=west,powered=false: state: ${base_block}[face=ceiling,facing=west,powered=false] entity-renderer: item: ${not_pressed_item} rotation: 0,90,180 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 face=ceiling,facing=east,powered=false: state: ${base_block}[face=ceiling,facing=east,powered=false] entity-renderer: item: ${not_pressed_item} rotation: -90,-90,-90 - scale: 1.0001 - translation: 0,-0.0001,0 + scale: 1.0005 + translation: 0,-0.00023,0 variants: face=floor,facing=east,powered=true: appearance: face=floor,facing=east,powered=true @@ -3389,6 +3399,537 @@ templates#block_states: face=ceiling,facing=east,powered=false: appearance: face=ceiling,facing=east,powered=false id: ${internal_id} + default:block_state/fence: + properties: + north: + type: boolean + default: false + east: + type: boolean + default: false + south: + type: boolean + default: false + west: + type: boolean + default: false + waterlogged: + type: boolean + default: false + appearances: + east=false,north=false,south=false,waterlogged=false,west=false: + state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + east=true,north=false,south=false,waterlogged=false,west=false: + state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 270 + east=false,north=true,south=false,waterlogged=false,west=false: + state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 180 + east=false,north=false,south=true,waterlogged=false,west=false: + state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + east=false,north=false,south=false,waterlogged=false,west=true: + state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + east=true,north=true,south=false,waterlogged=false,west=false: + state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=true,north=false,south=true,waterlogged=false,west=false: + state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 270 + east=true,north=false,south=false,waterlogged=false,west=true: + state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 270 + east=false,north=true,south=true,waterlogged=false,west=false: + state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 180 + east=false,north=true,south=false,waterlogged=false,west=true: + state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + east=false,north=false,south=true,waterlogged=false,west=true: + state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + east=true,north=true,south=true,waterlogged=false,west=false: + state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=true,north=true,south=false,waterlogged=false,west=true: + state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=true,north=false,south=true,waterlogged=false,west=true: + state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 270 + east=false,north=true,south=true,waterlogged=false,west=true: + state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + east=true,north=true,south=true,waterlogged=false,west=true: + state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=false,north=false,south=false,waterlogged=true,west=false: + state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + east=true,north=false,south=false,waterlogged=true,west=false: + state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 270 + east=false,north=true,south=false,waterlogged=true,west=false: + state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 180 + east=false,north=false,south=true,waterlogged=true,west=false: + state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + east=false,north=false,south=false,waterlogged=true,west=true: + state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + east=true,north=true,south=false,waterlogged=true,west=false: + state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=true,north=false,south=true,waterlogged=true,west=false: + state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 270 + east=true,north=false,south=false,waterlogged=true,west=true: + state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 270 + east=false,north=true,south=true,waterlogged=true,west=false: + state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 180 + east=false,north=true,south=false,waterlogged=true,west=true: + state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + east=false,north=false,south=true,waterlogged=true,west=true: + state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + east=true,north=true,south=true,waterlogged=true,west=false: + state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=false] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=true,north=true,south=false,waterlogged=true,west=true: + state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + east=true,north=false,south=true,waterlogged=true,west=true: + state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 270 + east=false,north=true,south=true,waterlogged=true,west=true: + state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + east=true,north=true,south=true,waterlogged=true,west=true: + state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=true] + entity-renderer: + - item: ${fence_post_item} + rotation: 180 + scale: 1.0003 + translation: 0,0.0001,0 + - item: ${fence_side_item} + rotation: 0 + - item: ${fence_side_item} + rotation: 90 + - item: ${fence_side_item} + rotation: 180 + - item: ${fence_side_item} + rotation: 270 + variants: + east=false,north=false,south=false,waterlogged=false,west=false: + appearance: east=false,north=false,south=false,waterlogged=false,west=false + id: ${internal_id} + east=true,north=false,south=false,waterlogged=false,west=false: + appearance: east=true,north=false,south=false,waterlogged=false,west=false + id: ${internal_id} + east=false,north=true,south=false,waterlogged=false,west=false: + appearance: east=false,north=true,south=false,waterlogged=false,west=false + id: ${internal_id} + east=false,north=false,south=true,waterlogged=false,west=false: + appearance: east=false,north=false,south=true,waterlogged=false,west=false + id: ${internal_id} + east=false,north=false,south=false,waterlogged=false,west=true: + appearance: east=false,north=false,south=false,waterlogged=false,west=true + id: ${internal_id} + east=true,north=true,south=false,waterlogged=false,west=false: + appearance: east=true,north=true,south=false,waterlogged=false,west=false + id: ${internal_id} + east=true,north=false,south=true,waterlogged=false,west=false: + appearance: east=true,north=false,south=true,waterlogged=false,west=false + id: ${internal_id} + east=true,north=false,south=false,waterlogged=false,west=true: + appearance: east=true,north=false,south=false,waterlogged=false,west=true + id: ${internal_id} + east=false,north=true,south=true,waterlogged=false,west=false: + appearance: east=false,north=true,south=true,waterlogged=false,west=false + id: ${internal_id} + east=false,north=true,south=false,waterlogged=false,west=true: + appearance: east=false,north=true,south=false,waterlogged=false,west=true + id: ${internal_id} + east=false,north=false,south=true,waterlogged=false,west=true: + appearance: east=false,north=false,south=true,waterlogged=false,west=true + id: ${internal_id} + east=true,north=true,south=true,waterlogged=false,west=false: + appearance: east=true,north=true,south=true,waterlogged=false,west=false + id: ${internal_id} + east=true,north=true,south=false,waterlogged=false,west=true: + appearance: east=true,north=true,south=false,waterlogged=false,west=true + id: ${internal_id} + east=true,north=false,south=true,waterlogged=false,west=true: + appearance: east=true,north=false,south=true,waterlogged=false,west=true + id: ${internal_id} + east=false,north=true,south=true,waterlogged=false,west=true: + appearance: east=false,north=true,south=true,waterlogged=false,west=true + id: ${internal_id} + east=true,north=true,south=true,waterlogged=false,west=true: + appearance: east=true,north=true,south=true,waterlogged=false,west=true + id: ${internal_id} + east=false,north=false,south=false,waterlogged=true,west=false: + appearance: east=false,north=false,south=false,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=false,south=false,waterlogged=true,west=false: + appearance: east=true,north=false,south=false,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=true,south=false,waterlogged=true,west=false: + appearance: east=false,north=true,south=false,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=false,south=true,waterlogged=true,west=false: + appearance: east=false,north=false,south=true,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=false,south=false,waterlogged=true,west=true: + appearance: east=false,north=false,south=false,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=true,south=false,waterlogged=true,west=false: + appearance: east=true,north=true,south=false,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=false,south=true,waterlogged=true,west=false: + appearance: east=true,north=false,south=true,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=false,south=false,waterlogged=true,west=true: + appearance: east=true,north=false,south=false,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=true,south=true,waterlogged=true,west=false: + appearance: east=false,north=true,south=true,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=true,south=false,waterlogged=true,west=true: + appearance: east=false,north=true,south=false,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=false,south=true,waterlogged=true,west=true: + appearance: east=false,north=false,south=true,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=true,south=true,waterlogged=true,west=false: + appearance: east=true,north=true,south=true,waterlogged=true,west=false + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=true,south=false,waterlogged=true,west=true: + appearance: east=true,north=true,south=false,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=false,south=true,waterlogged=true,west=true: + appearance: east=true,north=false,south=true,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=false,north=true,south=true,waterlogged=true,west=true: + appearance: east=false,north=true,south=true,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + east=true,north=true,south=true,waterlogged=true,west=true: + appearance: east=true,north=true,south=true,waterlogged=true,west=true + id: ${internal_id} + settings: + resistance: 1200.0 + burnable: false + fluid-state: water # recipes templates#recipes: default:recipe/planks: diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json new file mode 100644 index 000000000..2c3c53932 --- /dev/null +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json @@ -0,0 +1,29 @@ +{ + "textures": { + "particle": "#texture" + }, + "elements": [ + { + "from": [6.9989, 11.99835, -0.00495], + "to": [9.0011, 15.00165, 9.00495], + "rotation": {"angle": 0, "axis": "y", "origin": [-0.0088, -0.01485, -0.00495]}, + "faces": { + "east": {"uv": [0, 1, 9, 4], "texture": "#texture"}, + "west": {"uv": [0, 1, 9, 4], "texture": "#texture"}, + "up": {"uv": [7, 0, 9, 9], "texture": "#texture"}, + "down": {"uv": [7, 0, 9, 9], "texture": "#texture"} + } + }, + { + "from": [6.9989, 5.99835, -0.00495], + "to": [9.0011, 9.00165, 9.00495], + "rotation": {"angle": 0, "axis": "y", "origin": [-0.0088, -0.00825, -0.00495]}, + "faces": { + "east": {"uv": [0, 7, 9, 10], "texture": "#texture"}, + "west": {"uv": [0, 7, 9, 10], "texture": "#texture"}, + "up": {"uv": [7, 0, 9, 9], "texture": "#texture"}, + "down": {"uv": [7, 0, 9, 9], "texture": "#texture"} + } + } + ] +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 586931ea7..2777d7c88 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -543,6 +543,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/hami_melon_top.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_slice.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json"); } private TreeMap> updateCachedConfigFiles() { From b2c73852c3f2f8d0314a03e004ca4644dadc08c0 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 08:55:31 +0800 Subject: [PATCH 180/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=85=E6=A0=8F?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/additional-real-blocks.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml index 31bea6787..6ca380ee7 100644 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ b/common-files/src/main/resources/additional-real-blocks.yml @@ -87,4 +87,5 @@ minecraft:redstone_torch: 1 minecraft:redstone_wall_torch: 4 minecraft:pumpkin_stem: 8 minecraft:attached_pumpkin_stem: 4 -minecraft:birch_button: 24 \ No newline at end of file +minecraft:birch_button: 24 +minecraft:oak_fence: 32 \ No newline at end of file From 403880d53151150f291b128cefb8e9e7d6e05f04 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 10:46:23 +0800 Subject: [PATCH 181/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=93=83=E5=A3=B0?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 留给水晶母岩用 --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../block/behavior/ChimeBlockBehavior.java | 56 +++++++++++++++++++ .../UnsafeCompositeBlockBehavior.java | 14 +++++ .../LiquidCollisionBlockItemBehavior.java | 4 +- .../plugin/injector/BlockGenerator.java | 17 ++++++ .../reflection/minecraft/CoreReflections.java | 19 +++++-- .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + .../craftengine/core/block/BlockBehavior.java | 5 +- gradle.properties | 2 +- 10 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index ce5a59520..921fee717 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -41,6 +41,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK = Key.from("craftengine:face_attached_horizontal_directional_block"); public static final Key STEM_BLOCK = Key.from("craftengine:stem_block"); public static final Key ATTACHED_STEM_BLOCK = Key.from("craftengine:attached_stem_block"); + public static final Key CHIME_BLOCK = Key.from("craftengine:chime_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -80,5 +81,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK, FaceAttachedHorizontalDirectionalBlockBehavior.FACTORY); register(STEM_BLOCK, StemBlockBehavior.FACTORY); register(ATTACHED_STEM_BLOCK, AttachedStemBlockBehavior.FACTORY); + register(CHIME_BLOCK, ChimeBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java new file mode 100644 index 000000000..f008b81e1 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -0,0 +1,56 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.util.RandomUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class ChimeBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final SoundData hitSound; + private final boolean randomPitch; + private final float randomMultiplier; + + public ChimeBlockBehavior(CustomBlock customBlock, SoundData hitSound, boolean randomPitch, float randomMultiplier) { + super(customBlock); + this.hitSound = hitSound; + this.randomPitch = randomPitch; + this.randomMultiplier = randomMultiplier; + } + + @Override + public void onProjectileHit(Object thisBlock, Object[] args, Callable superMethod) { + Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(args[2]); + Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.id()), Optional.empty()); + float pitch = hitSound.pitch().get(); + if (randomPitch) { + pitch = pitch + RandomUtils.generateRandomInt(0, 1) * this.randomMultiplier; + } + FastNMS.INSTANCE.method$LevelAccessor$playSound(args[0], null, blockPos, sound, CoreReflections.instance$SoundSource$BLOCKS, hitSound.volume().get(), pitch); + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("hit-sound"), "warning.config.block.behavior.chime.missing_hit_sound"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); + Map randomPitch = ResourceConfigUtils.getAsMapOrNull(arguments.get("random-pitch"), "random-pitch"); + boolean enableRandomPitch = false; + float randomMultiplier = 1f; + if (randomPitch != null) { + enableRandomPitch = true; + randomMultiplier = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("multiplier", 1f), "multiplier"); + } + return new ChimeBlockBehavior(block, hitSound, enableRandomPitch, randomMultiplier); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index 0aab207be..e2671bc94 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -381,4 +381,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior } FallOnBlockBehavior.super.updateEntityMovementAfterFallOn(thisBlock, args, superMethod); } + + @Override + public void stepOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.stepOn(thisBlock, args, superMethod); + } + } + + @Override + public void onProjectileHit(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.onProjectileHit(thisBlock, args, superMethod); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java index 051838931..36a62b18d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java @@ -47,7 +47,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior { try { if (player == null) return InteractionResult.FAIL; Object blockHitResult = CoreReflections.method$Item$getPlayerPOVHitResult.invoke(null, world.serverWorld(), player.serverPlayer(), CoreReflections.instance$ClipContext$Fluid$SOURCE_ONLY); - Object blockPos = FastNMS.INSTANCE.field$BlockHitResul$blockPos(blockHitResult); + Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(blockHitResult); BlockPos above = new BlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) + offsetY, FastNMS.INSTANCE.field$Vec3i$z(blockPos)); Direction direction = DirectionUtils.fromNMSDirection(FastNMS.INSTANCE.field$BlockHitResul$direction(blockHitResult)); boolean miss = FastNMS.INSTANCE.field$BlockHitResul$miss(blockHitResult); @@ -59,7 +59,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior { if (miss) { return super.useOnBlock(new UseOnContext(player, hand, BlockHitResult.miss(hitPos, direction, above))); } else { - boolean inside = CoreReflections.field$BlockHitResul$inside.getBoolean(blockHitResult); + boolean inside = CoreReflections.field$BlockHitResult$inside.getBoolean(blockHitResult); return super.useOnBlock(new UseOnContext(player, hand, new BlockHitResult(hitPos, direction, above, inside))); } } catch (Exception e) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index ae5e8c670..2b3792085 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -170,6 +170,9 @@ public final class BlockGenerator { // stepOn .method(ElementMatchers.is(CoreReflections.method$Block$stepOn)) .intercept(MethodDelegation.to(StepOnInterceptor.INSTANCE)) + // onProjectileHit + .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$onProjectileHit)) + .intercept(MethodDelegation.to(OnProjectileHitInterceptor.INSTANCE)) ; // 1.21.5+ if (CoreReflections.method$BlockBehaviour$affectNeighborsAfterRemoval != null) { @@ -760,4 +763,18 @@ public final class BlockGenerator { } } } + + public static class OnProjectileHitInterceptor { + public static final OnProjectileHitInterceptor INSTANCE = new OnProjectileHitInterceptor(); + + @RuntimeType + public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { + ObjectHolder holder = ((DelegatingBlock) thisObj).behaviorDelegate(); + try { + holder.value().onProjectileHit(thisObj, args, superMethod); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run onProjectileHit", e); + } + } + } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 0cae0070e..04fd97cec 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -2559,19 +2559,19 @@ public final class CoreReflections { ReflectionUtils.getMethod(clazz$BlockHitResult, clazz$BlockHitResult, clazz$BlockPos) ); - public static final Field field$BlockHitResul$blockPos = requireNonNull( + public static final Field field$BlockHitResult$blockPos = requireNonNull( ReflectionUtils.getDeclaredField(clazz$BlockHitResult, clazz$BlockPos, 0) ); - public static final Field field$BlockHitResul$direction = requireNonNull( + public static final Field field$BlockHitResult$direction = requireNonNull( ReflectionUtils.getDeclaredField(clazz$BlockHitResult, clazz$Direction, 0) ); - public static final Field field$BlockHitResul$miss = requireNonNull( + public static final Field field$BlockHitResult$miss = requireNonNull( ReflectionUtils.getDeclaredField(clazz$BlockHitResult, boolean.class, 0) ); - public static final Field field$BlockHitResul$inside = requireNonNull( + public static final Field field$BlockHitResult$inside = requireNonNull( ReflectionUtils.getDeclaredField(clazz$BlockHitResult, boolean.class, 1) ); @@ -4418,4 +4418,15 @@ public final class CoreReflections { public static final Method method$BlockStateBase$isBlock = requireNonNull( ReflectionUtils.getDeclaredMethod(clazz$BlockStateBase, boolean.class, new String[]{"is", "a"}, clazz$Block) ); + + public static final Class clazz$Projectile = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.entity.projectile.IProjectile", + "world.entity.projectile.Projectile" + ) + ); + + public static final Method method$BlockBehaviour$onProjectileHit = requireNonNull( + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, new String[]{"onProjectileHit", "a"}, clazz$Level, clazz$BlockState, clazz$BlockHitResult, clazz$Projectile) + ); } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 3abacd999..6c88ca4a0 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -336,6 +336,7 @@ warning.config.block.behavior.stem.missing_attached_stem: "Issue found i warning.config.block.behavior.attached_stem.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'attached_stem_block' behavior." warning.config.block.behavior.attached_stem.missing_fruit: "Issue found in file - The block '' is missing the required 'fruit' argument for 'attached_stem_block' behavior." warning.config.block.behavior.attached_stem.missing_stem: "Issue found in file - The block '' is missing the required 'stem' argument for 'attached_stem_block' behavior." +warning.config.block.behavior.chime.missing_hit_sound: "Issue found in file - The block '' is missing the required 'hit-sound' argument for 'chime_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 6f5171988..4072ac068 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -330,6 +330,7 @@ warning.config.block.behavior.stem.missing_attached_stem: "在文件 在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.attached_stem.missing_fruit: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'fruit' 选项" warning.config.block.behavior.attached_stem.missing_stem: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'stem' 选项" +warning.config.block.behavior.chime.missing_hit_sound: "在文件 发现问题 - 方块 '' 的 'chime_block' 行为缺少必需的 'hit-sound' 选项" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index e94b5322e..738dd94ea 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -181,7 +181,10 @@ public abstract class BlockBehavior { // Level level, BlockPos pos, BlockState state, Entity entity public void stepOn(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - superMethod.call(); + } + + // Level level, BlockState state, BlockHitResult hit, Projectile projectile + public void onProjectileHit(Object thisBlock, Object[] args, Callable superMethod) throws Exception { } public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { diff --git a/gradle.properties b/gradle.properties index 5cae0308f..e5de2af92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.95 +nms_helper_version=1.0.96 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From aad894c5f0e8d122da10bc9de8249c77e3d935ae Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 15:35:41 +0800 Subject: [PATCH 182/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=98=91=E8=8F=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BuddingBlockBehavior.java | 99 +++++++++++ .../block/behavior/BukkitBlockBehaviors.java | 2 + .../reflection/minecraft/CoreReflections.java | 5 + .../minecraft/MBlockStateProperties.java | 10 ++ common-files/src/main/resources/mappings.yml | 24 +-- .../default/configuration/blocks/mushroom.yml | 155 ++++++++++++++++++ .../configuration/blocks/palm_tree.yml | 12 ++ .../default/configuration/categories.yml | 6 +- .../resources/default/configuration/i18n.yml | 16 ++ .../default/configuration/items/gui_head.yml | 2 + .../default/configuration/templates.yml | 37 +++++ .../{mushroom_2.json => large_mushroom.json} | 23 ++- .../{mushroom_3.json => medium_mushroom.json} | 23 ++- .../{mushroom_1.json => small_mushroom.json} | 23 ++- .../core/block/properties/Property.java | 6 + .../core/pack/AbstractPackManager.java | 5 + 16 files changed, 432 insertions(+), 16 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java create mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/{mushroom_2.json => large_mushroom.json} (89%) rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/{mushroom_3.json => medium_mushroom.json} (82%) rename common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/{mushroom_1.json => small_mushroom.json} (79%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java new file mode 100644 index 000000000..a42367d2d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java @@ -0,0 +1,99 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.BooleanProperty; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.*; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +public class BuddingBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final int growthChance; + private final List blocks; + + public BuddingBlockBehavior(CustomBlock customBlock, int growthChance, List blocks) { + super(customBlock); + this.growthChance = growthChance; + this.blocks = blocks; + } + + @Override + public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + if (RandomUtils.generateRandomInt(0, this.growthChance) != 0) return; + Object nmsDirection = CoreReflections.instance$Direction$values[RandomUtils.generateRandomInt(0, 6)]; + Direction direction = DirectionUtils.fromNMSDirection(nmsDirection); + Object blockPos = FastNMS.INSTANCE.method$BlockPos$relative(args[2], nmsDirection); + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], blockPos); + if (canClusterGrowAtState(blockState)) { + Key blockId = blocks.getFirst(); + CustomBlock firstBlock = BukkitBlockManager.instance().blockById(blockId).orElse(null); + placeWithPropertyBlock(firstBlock, blockId, direction, nmsDirection, args[1], blockPos, blockState); + } else { + Key blockId = BlockStateUtils.getOptionalCustomBlockState(blockState) + .map(it -> it.owner().value().id()) + .orElseGet(() -> BlockStateUtils.getBlockOwnerIdFromState(blockState)); + int blockIdIndex = blocks.indexOf(blockId); + if (blockIdIndex < 0 || blockIdIndex == blocks.size() - 1) return; + Key nextBlockId = blocks.get(blockIdIndex + 1); + CustomBlock nextBlock = BukkitBlockManager.instance().blockById(nextBlockId).orElse(null); + placeWithPropertyBlock(nextBlock, nextBlockId, direction, nmsDirection, args[1], blockPos, blockState); + } + } + + @SuppressWarnings("unchecked") + private void placeWithPropertyBlock(CustomBlock customBlock, Key blockId, Direction direction, Object nmsDirection, Object level, Object blockPos, Object blockState) { + if (customBlock != null) { + ImmutableBlockState newState = customBlock.defaultState(); + Property facing = customBlock.getProperty("facing"); + if (facing != null) { + if (facing.valueClass() == Direction.class) { + newState = newState.with((Property) facing, direction); + } else if (facing.valueClass() == HorizontalDirection.class) { + if (!direction.axis().isHorizontal()) return; + newState = newState.with((Property) facing, direction.toHorizontalDirection()); + } + } + BooleanProperty waterlogged = (BooleanProperty) customBlock.getProperty("waterlogged"); + if (waterlogged != null) { + newState = newState.with(waterlogged, FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(blockState)) == MFluids.WATER); + } + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState.customBlockState().literalObject(), 3); + } else if (blockId.namespace().equals("minecraft")) { + Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", blockId.value())); + if (block == null) return; + Object newState = FastNMS.INSTANCE.method$Block$defaultState(block); + newState = FastNMS.INSTANCE.method$StateHolder$trySetValue(newState, MBlockStateProperties.WATERLOGGED, FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(blockState)) == MFluids.WATER); + newState = FastNMS.INSTANCE.method$StateHolder$trySetValue(newState, MBlockStateProperties.FACING, (Comparable) nmsDirection); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState, 3); + } + } + + public static boolean canClusterGrowAtState(Object state) { + return FastNMS.INSTANCE.method$BlockStateBase$isAir(state) + || FastNMS.INSTANCE.method$BlockStateBase$isBlock(state, MBlocks.WATER) + && FastNMS.INSTANCE.field$FluidState$amount(FastNMS.INSTANCE.field$BlockBehaviour$BlockStateBase$fluidState(state)) == 8; + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + int growthChance = ResourceConfigUtils.getAsInt(arguments.getOrDefault("growth-chance", 5), "growth-chance"); + List blocks = new ObjectArrayList<>(); + MiscUtils.getAsStringList(arguments.get("blocks")).forEach(s -> blocks.add(Key.of(s))); + return new BuddingBlockBehavior(block, growthChance, blocks); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 921fee717..000ecaccd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -42,6 +42,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key STEM_BLOCK = Key.from("craftengine:stem_block"); public static final Key ATTACHED_STEM_BLOCK = Key.from("craftengine:attached_stem_block"); public static final Key CHIME_BLOCK = Key.from("craftengine:chime_block"); + public static final Key BUDDING_BLOCK = Key.from("craftengine:budding_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -82,5 +83,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(STEM_BLOCK, StemBlockBehavior.FACTORY); register(ATTACHED_STEM_BLOCK, AttachedStemBlockBehavior.FACTORY); register(CHIME_BLOCK, ChimeBlockBehavior.FACTORY); + register(BUDDING_BLOCK, BuddingBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 04fd97cec..bbc030da9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.gson.JsonElement; import com.mojang.serialization.Codec; import com.mojang.serialization.DynamicOps; @@ -4429,4 +4430,8 @@ public final class CoreReflections { public static final Method method$BlockBehaviour$onProjectileHit = requireNonNull( ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, new String[]{"onProjectileHit", "a"}, clazz$Level, clazz$BlockState, clazz$BlockHitResult, clazz$Projectile) ); + + public static final Field field$EnumProperty$values = requireNonNull( + ReflectionUtils.getDeclaredField(clazz$EnumProperty, VersionHelper.isOrAbove1_21_2() ? List.class : ImmutableSet.class, 0) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlockStateProperties.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlockStateProperties.java index 0bec500a3..452f4efb3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlockStateProperties.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBlockStateProperties.java @@ -4,24 +4,34 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.ReflectionInitExcepti import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Collection; import static java.util.Objects.requireNonNull; public final class MBlockStateProperties { public static final Object WATERLOGGED; + public static final Object FACING; static { try { Object waterlogged = null; + Object facing = null; for (Field field : CoreReflections.clazz$BlockStateProperties.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { Object instance = field.get(null); if (CoreReflections.clazz$Property.isInstance(instance) && CoreReflections.field$Property$name.get(instance).equals("waterlogged")) { waterlogged = instance; + } else if (CoreReflections.clazz$EnumProperty.isInstance(instance) && CoreReflections.field$Property$name.get(instance).equals("facing")) { + @SuppressWarnings("unchecked") + Collection values = (Collection) CoreReflections.field$EnumProperty$values.get(instance); + if (values.size() == CoreReflections.instance$Direction$values.length) { + facing = instance; + } } } } WATERLOGGED = requireNonNull(waterlogged); + FACING = requireNonNull(facing); } catch (ReflectiveOperationException e) { throw new ReflectionInitException("Failed to init MBlockStateProperties", e); } diff --git a/common-files/src/main/resources/mappings.yml b/common-files/src/main/resources/mappings.yml index d3f9f58f2..a3a0b28b1 100644 --- a/common-files/src/main/resources/mappings.yml +++ b/common-files/src/main/resources/mappings.yml @@ -4340,10 +4340,10 @@ minecraft:heavy_weighted_pressure_plate[power=15]: minecraft:heavy_weighted_pres # minecraft:dead_bubble_coral[waterlogged=true]: minecraft:bubble_coral[waterlogged=true] # minecraft:dead_bubble_coral_fan[waterlogged=false]: minecraft:bubble_coral_fan[waterlogged=false] # minecraft:dead_bubble_coral_fan[waterlogged=true]: minecraft:bubble_coral_fan[waterlogged=true] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=west] +minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=east] +minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=north] +minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=south] +minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=west] # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=east] # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=north] # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=south] @@ -4352,10 +4352,10 @@ minecraft:heavy_weighted_pressure_plate[power=15]: minecraft:heavy_weighted_pres # minecraft:dead_fire_coral[waterlogged=true]: minecraft:fire_coral[waterlogged=true] # minecraft:dead_fire_coral_fan[waterlogged=false]: minecraft:fire_coral_fan[waterlogged=false] # minecraft:dead_fire_coral_fan[waterlogged=true]: minecraft:fire_coral_fan[waterlogged=true] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=west] +minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=east] +minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=north] +minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=south] +minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=west] # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=east] # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=north] # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=south] @@ -4364,10 +4364,10 @@ minecraft:heavy_weighted_pressure_plate[power=15]: minecraft:heavy_weighted_pres # minecraft:dead_horn_coral[waterlogged=true]: minecraft:horn_coral[waterlogged=true] # minecraft:dead_horn_coral_fan[waterlogged=false]: minecraft:horn_coral_fan[waterlogged=false] # minecraft:dead_horn_coral_fan[waterlogged=true]: minecraft:horn_coral_fan[waterlogged=true] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=west] +minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=east] +minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=north] +minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=south] +minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=west] # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=east] # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=north] # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=south] diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml b/common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml new file mode 100644 index 000000000..436b71872 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml @@ -0,0 +1,155 @@ +items: + default:infected_palm_log: + material: nether_brick + custom-model-data: 3025 + settings: + fuel-time: 300 + tags: + - default:palm_logs + - minecraft:logs + - minecraft:logs_that_burn + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/palm_log + generation: + parent: minecraft:block/custom/palm_log + behavior: + type: block_item + block: default:infected_palm_log + default:small_mushroom: + material: nether_brick + custom-model-data: 3026 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/small_mushroom + generation: + parent: minecraft:block/custom/small_mushroom + behavior: + type: block_item + block: default:small_mushroom + default:medium_mushroom: + material: nether_brick + custom-model-data: 3027 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/medium_mushroom + generation: + parent: minecraft:block/custom/medium_mushroom + behavior: + type: block_item + block: default:medium_mushroom + default:large_mushroom: + material: nether_brick + custom-model-data: 3028 + data: + item-name: + model: + type: minecraft:model + path: minecraft:item/custom/large_mushroom + generation: + parent: minecraft:block/custom/large_mushroom + behavior: + type: block_item + block: default:large_mushroom + +blocks: + default:infected_palm_log: + behavior: + - type: strippable_block + stripped: default:stripped_palm_log + - type: budding_block + growth-chance: 5 + blocks: + - default:small_mushroom + - default:medium_mushroom + - default:large_mushroom + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + overrides: + is-randomly-ticking: true + map-color: 2 + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/palm_log_top + texture_side_path: minecraft:block/custom/palm_log + model_vertical_path: minecraft:block/custom/palm_log + model_horizontal_path: minecraft:block/custom/palm_log_horizontal + vanilla_id: + type: self_increase_int + from: 31 + to: 33 + internal_id: + type: self_increase_int + from: 31 + to: 33 + default:small_mushroom: + settings: + template: + - default:sound/crop + - default:hardness/none + overrides: + push-reaction: destroy + map-color: 26 + luminance: 1 + tags: + - minecraft:enderman_holdable + - minecraft:replaceable_by_mushrooms + behavior: + type: directional_attached_block + states: + template: default:block_state/mushroom + arguments: + base_block: dead_bubble_coral_wall_fan + model_path: minecraft:block/custom/small_mushroom + default:medium_mushroom: + behavior: + type: directional_attached_block + settings: + template: + - default:sound/crop + - default:hardness/none + overrides: + push-reaction: destroy + map-color: 26 + luminance: 1 + tags: + - minecraft:enderman_holdable + - minecraft:replaceable_by_mushrooms + states: + template: default:block_state/mushroom + arguments: + base_block: dead_horn_coral_wall_fan + model_path: minecraft:block/custom/medium_mushroom + default:large_mushroom: + loot: + template: default:loot_table/basic + arguments: + item: minecraft:brown_mushroom + behavior: + type: directional_attached_block + settings: + template: + - default:sound/crop + - default:hardness/none + overrides: + push-reaction: destroy + map-color: 26 + luminance: 1 + tags: + - minecraft:enderman_holdable + - minecraft:replaceable_by_mushrooms + states: + template: default:block_state/mushroom + arguments: + base_block: dead_fire_coral_wall_fan + model_path: minecraft:block/custom/large_mushroom diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 47523c47b..2589481da 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -297,6 +297,8 @@ blocks: template: default:loot_table/self settings: template: default:settings/wood + overrides: + map-color: 2 states: template: default:block_state/pillar arguments: @@ -318,6 +320,8 @@ blocks: template: default:loot_table/self settings: template: default:settings/wood + overrides: + map-color: 2 states: template: default:block_state/pillar arguments: @@ -342,6 +346,8 @@ blocks: template: default:loot_table/self settings: template: default:settings/wood + overrides: + map-color: 2 states: template: default:block_state/pillar arguments: @@ -363,6 +369,8 @@ blocks: template: default:loot_table/self settings: template: default:settings/wood + overrides: + map-color: 2 states: template: default:block_state/pillar arguments: @@ -382,6 +390,8 @@ blocks: default:palm_planks: settings: template: default:settings/planks + overrides: + map-color: 2 loot: template: default:loot_table/self state: @@ -437,6 +447,8 @@ blocks: sapling: default:palm_sapling settings: template: default:settings/leaves + overrides: + map-color: 19 states: template: default:block_state/leaves arguments: diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 2d911abe4..f73618373 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -29,6 +29,7 @@ categories: - default:palm_pressure_plate - default:palm_button - default:palm_fence + - default:infected_palm_log default:topaz: name: <#FF8C00> hidden: true @@ -83,4 +84,7 @@ categories: - default:amethyst_torch - default:hami_melon_seeds - default:hami_melon_slice - - default:hami_melon \ No newline at end of file + - default:hami_melon + - default:small_mushroom + - default:medium_mushroom + - default:large_mushroom \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index 71d6e9e30..b8ad2e75d 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -54,6 +54,10 @@ i18n: item.hami_melon_seeds: Hami Melon Seeds item.palm_button: Palm Button item.palm_fence: Palm Fence + item.infected_palm_log: Infected Palm Log + item.small_mushroom: Small Mushroom + item.medium_mushroom: Medium Mushroom + item.large_mushroom: Large Mushroom category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -117,6 +121,10 @@ i18n: item.hami_melon_seeds: 哈密瓜种子 item.palm_button: 棕榈木按钮 item.palm_fence: 棕榈木栅栏 + item.infected_palm_log: 菌蚀棕榈原木 + item.small_mushroom: 小型蘑菇 + item.medium_mushroom: 中型蘑菇 + item.large_mushroom: 大型蘑菇 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -164,6 +172,10 @@ lang: block_name:default:default:attached_hami_melon_stem: Hami Melon Stem block_name:default:palm_button: Palm Button block_name:default:palm_fence: Palm Fence + block_name:default:infected_palm_log: Infected Palm Log + block_name:default:small_mushroom: Small Mushroom + block_name:default:medium_mushroom: Medium Mushroom + block_name:default:large_mushroom: Large Mushroom zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -199,3 +211,7 @@ lang: block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 block_name:default:palm_button: 棕榈木按钮 block_name:default:palm_fence: 棕榈木栅栏 + block_name:default:infected_palm_log: 菌蚀棕榈原木 + block_name:default:small_mushroom: 小型蘑菇 + block_name:default:medium_mushroom: 中型蘑菇 + block_name:default:large_mushroom: 大型蘑菇 diff --git a/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml b/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml index effc996c8..9a6b67a8b 100644 --- a/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml @@ -2,6 +2,7 @@ items: default:gui_head_size_1: material: player_head custom-model-data: 1000 + item-model: default:gui_head_size_1 model: type: minecraft:special path: minecraft:item/custom/gui_head_size_1 @@ -17,6 +18,7 @@ items: default:gui_head_size_4: material: player_head custom-model-data: 1001 + item-model: default:gui_head_size_4 model: type: minecraft:special path: minecraft:item/custom/gui_head_size_4 diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index fa284b068..20392707e 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -3930,6 +3930,43 @@ templates#block_states: resistance: 1200.0 burnable: false fluid-state: water + default:block_state/mushroom: + properties: + facing: + type: horizontal_direction + appearances: + north: + state: ${base_block}[waterlogged=false,facing=north] + model: + path: ${model_path} + east: + state: ${base_block}[waterlogged=false,facing=east] + model: + path: ${model_path} + y: 90 + west: + state: ${base_block}[waterlogged=false,facing=west] + model: + path: ${model_path} + y: 270 + south: + state: ${base_block}[waterlogged=false,facing=south] + model: + path: ${model_path} + y: 180 + variants: + facing=north: + appearance: north + id: 0 + facing=east: + appearance: east + id: 1 + facing=west: + appearance: west + id: 2 + facing=south: + appearance: south + id: 3 # recipes templates#recipes: default:recipe/planks: diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/large_mushroom.json similarity index 89% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/large_mushroom.json index 5c75b3c6e..356812b0e 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_2.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/large_mushroom.json @@ -1,6 +1,7 @@ { "textures": { - "0": "block/custom/mushroom" + "0": "block/custom/mushroom", + "particle": "block/custom/mushroom" }, "elements": [ { @@ -146,6 +147,26 @@ "fixed": { "translation": [0, 0, -16], "scale": [2, 2, 2] + }, + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 2.5, 1], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 2.5, 1], + "scale": [0.4, 0.4, 0.4] } } } \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/medium_mushroom.json similarity index 82% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/medium_mushroom.json index 79459425c..e00ea901d 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_3.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/medium_mushroom.json @@ -1,6 +1,7 @@ { "textures": { - "0": "block/custom/mushroom" + "0": "block/custom/mushroom", + "particle": "block/custom/mushroom" }, "elements": [ { @@ -81,6 +82,26 @@ "fixed": { "translation": [0, 0, -16], "scale": [2, 2, 2] + }, + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 2.5, 1], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 2.5, 1], + "scale": [0.4, 0.4, 0.4] } } } \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/small_mushroom.json similarity index 79% rename from common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json rename to common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/small_mushroom.json index f70b470fe..bbc245d23 100644 --- a/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/mushroom_1.json +++ b/common-files/src/main/resources/resources/default/resourcepack/assets/minecraft/models/block/custom/small_mushroom.json @@ -1,7 +1,8 @@ { "texture_size": [32, 32], "textures": { - "0": "block/custom/mushroom" + "0": "block/custom/mushroom", + "particle": "block/custom/mushroom" }, "elements": [ { @@ -69,6 +70,26 @@ "fixed": { "translation": [0, 0, -16.25], "scale": [2, 2, 2] + }, + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 2.5, 1], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 2.5, 1], + "scale": [0.4, 0.4, 0.4] } } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java index 84bbfda65..8ff91b9a8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.block.properties; +import com.google.common.base.MoreObjects; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; @@ -167,4 +168,9 @@ public abstract class Property> { public static > String formatValue(Property property, Comparable value) { return property.valueName((T) value); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("name", this.name).add("clazz", this.clazz).add("values", this.possibleValues()).toString(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 2777d7c88..91b25f19c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -432,6 +432,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/configuration/blocks/netherite_anvil.yml"); plugin.saveResource("resources/default/configuration/blocks/amethyst_torch.yml"); plugin.saveResource("resources/default/configuration/blocks/hami_melon.yml"); + plugin.saveResource("resources/default/configuration/blocks/mushroom.yml"); // assets plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png"); @@ -544,6 +545,10 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_slice.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/mushroom.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/small_mushroom.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/medium_mushroom.json"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/large_mushroom.json"); } private TreeMap> updateCachedConfigFiles() { From 98ef6441888c3e71e89b50bf0ee4c903e31ef848 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 15:43:44 +0800 Subject: [PATCH 183/226] =?UTF-8?q?=E8=A1=A5=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/default/configuration/blocks/palm_tree.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 2589481da..789f9768a 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -257,6 +257,7 @@ items: texture: minecraft:block/custom/palm_planks default:palm_fence: material: nether_brick + custom-model-data: 1018 data: item-name: model: @@ -271,6 +272,7 @@ items: block: default:palm_fence default:palm_fence_post: material: nether_brick + custom-model-data: 1019 model: type: minecraft:model path: minecraft:block/custom/palm_fence_post @@ -280,6 +282,7 @@ items: texture: minecraft:block/custom/palm_planks default:palm_fence_side: material: nether_brick + custom-model-data: 1020 model: type: minecraft:model path: minecraft:block/custom/palm_fence_side @@ -751,6 +754,8 @@ blocks: from: 0 to: 23 default:palm_fence: + loot: + template: default:loot_table/self settings: template: - default:hardness/planks From 10dbf3d590a6c583397249ded179c2533e16ecaf Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 16:02:36 +0800 Subject: [PATCH 184/226] =?UTF-8?q?=E5=88=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/mappings.yml | 24 +-- .../configuration/blocks/hami_melon.yml | 104 ------------ .../default/configuration/blocks/mushroom.yml | 155 ------------------ .../default/configuration/categories.yml | 6 +- .../core/pack/AbstractPackManager.java | 5 - 5 files changed, 13 insertions(+), 281 deletions(-) delete mode 100644 common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml diff --git a/common-files/src/main/resources/mappings.yml b/common-files/src/main/resources/mappings.yml index a3a0b28b1..d3f9f58f2 100644 --- a/common-files/src/main/resources/mappings.yml +++ b/common-files/src/main/resources/mappings.yml @@ -4340,10 +4340,10 @@ minecraft:heavy_weighted_pressure_plate[power=15]: minecraft:heavy_weighted_pres # minecraft:dead_bubble_coral[waterlogged=true]: minecraft:bubble_coral[waterlogged=true] # minecraft:dead_bubble_coral_fan[waterlogged=false]: minecraft:bubble_coral_fan[waterlogged=false] # minecraft:dead_bubble_coral_fan[waterlogged=true]: minecraft:bubble_coral_fan[waterlogged=true] -minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=east] -minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=north] -minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=south] -minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=west] +# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=east] +# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=north] +# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=south] +# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=west] # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=east] # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=north] # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=south] @@ -4352,10 +4352,10 @@ minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:b # minecraft:dead_fire_coral[waterlogged=true]: minecraft:fire_coral[waterlogged=true] # minecraft:dead_fire_coral_fan[waterlogged=false]: minecraft:fire_coral_fan[waterlogged=false] # minecraft:dead_fire_coral_fan[waterlogged=true]: minecraft:fire_coral_fan[waterlogged=true] -minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=east] -minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=north] -minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=south] -minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=west] +# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=east] +# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=north] +# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=south] +# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=west] # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=east] # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=north] # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=south] @@ -4364,10 +4364,10 @@ minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fir # minecraft:dead_horn_coral[waterlogged=true]: minecraft:horn_coral[waterlogged=true] # minecraft:dead_horn_coral_fan[waterlogged=false]: minecraft:horn_coral_fan[waterlogged=false] # minecraft:dead_horn_coral_fan[waterlogged=true]: minecraft:horn_coral_fan[waterlogged=true] -minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=east] -minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=north] -minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=south] -minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=west] +# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=east] +# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=north] +# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=south] +# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=west] # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=east] # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=north] # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=south] diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml index 356d62b63..f9407d1f2 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml @@ -96,95 +96,6 @@ blocks: south_texture: minecraft:block/custom/hami_melon west_texture: minecraft:block/custom/hami_melon default:hami_melon_stem: - loot: - pools: - - rolls: 1 - entries: - - type: item - item: default:hami_melon_seeds - functions: - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 0 - count: - type: binomial - extra: 3 - probability: 0.06666667 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 1 - count: - type: binomial - extra: 3 - probability: 0.13333334 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 2 - count: - type: binomial - extra: 3 - probability: 0.2 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 3 - count: - type: binomial - extra: 3 - probability: 0.26666668 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 4 - count: - type: binomial - extra: 3 - probability: 0.33333334 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 5 - count: - type: binomial - extra: 3 - probability: 0.4 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 6 - count: - type: binomial - extra: 3 - probability: 0.46666667 - - type: set_count - add: false - conditions: - - type: match_block_property - properties: - age: 7 - count: - type: binomial - extra: 3 - probability: 0.53333336 - functions: - - type: explosion_decay settings: template: - default:sound/stem_crop @@ -256,21 +167,6 @@ blocks: appearance: age=7 id: 7 default:attached_hami_melon_stem: - loot: - pools: - - rolls: 1 - entries: - - type: item - item: default:hami_melon_seeds - functions: - - type: set_count - add: false - count: - type: binomial - extra: 3 - probability: 0.53333336 - functions: - - type: explosion_decay settings: template: - default:sound/stem_crop diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml b/common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml deleted file mode 100644 index 436b71872..000000000 --- a/common-files/src/main/resources/resources/default/configuration/blocks/mushroom.yml +++ /dev/null @@ -1,155 +0,0 @@ -items: - default:infected_palm_log: - material: nether_brick - custom-model-data: 3025 - settings: - fuel-time: 300 - tags: - - default:palm_logs - - minecraft:logs - - minecraft:logs_that_burn - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/palm_log - generation: - parent: minecraft:block/custom/palm_log - behavior: - type: block_item - block: default:infected_palm_log - default:small_mushroom: - material: nether_brick - custom-model-data: 3026 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/small_mushroom - generation: - parent: minecraft:block/custom/small_mushroom - behavior: - type: block_item - block: default:small_mushroom - default:medium_mushroom: - material: nether_brick - custom-model-data: 3027 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/medium_mushroom - generation: - parent: minecraft:block/custom/medium_mushroom - behavior: - type: block_item - block: default:medium_mushroom - default:large_mushroom: - material: nether_brick - custom-model-data: 3028 - data: - item-name: - model: - type: minecraft:model - path: minecraft:item/custom/large_mushroom - generation: - parent: minecraft:block/custom/large_mushroom - behavior: - type: block_item - block: default:large_mushroom - -blocks: - default:infected_palm_log: - behavior: - - type: strippable_block - stripped: default:stripped_palm_log - - type: budding_block - growth-chance: 5 - blocks: - - default:small_mushroom - - default:medium_mushroom - - default:large_mushroom - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - overrides: - is-randomly-ticking: true - map-color: 2 - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/palm_log_top - texture_side_path: minecraft:block/custom/palm_log - model_vertical_path: minecraft:block/custom/palm_log - model_horizontal_path: minecraft:block/custom/palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 31 - to: 33 - internal_id: - type: self_increase_int - from: 31 - to: 33 - default:small_mushroom: - settings: - template: - - default:sound/crop - - default:hardness/none - overrides: - push-reaction: destroy - map-color: 26 - luminance: 1 - tags: - - minecraft:enderman_holdable - - minecraft:replaceable_by_mushrooms - behavior: - type: directional_attached_block - states: - template: default:block_state/mushroom - arguments: - base_block: dead_bubble_coral_wall_fan - model_path: minecraft:block/custom/small_mushroom - default:medium_mushroom: - behavior: - type: directional_attached_block - settings: - template: - - default:sound/crop - - default:hardness/none - overrides: - push-reaction: destroy - map-color: 26 - luminance: 1 - tags: - - minecraft:enderman_holdable - - minecraft:replaceable_by_mushrooms - states: - template: default:block_state/mushroom - arguments: - base_block: dead_horn_coral_wall_fan - model_path: minecraft:block/custom/medium_mushroom - default:large_mushroom: - loot: - template: default:loot_table/basic - arguments: - item: minecraft:brown_mushroom - behavior: - type: directional_attached_block - settings: - template: - - default:sound/crop - - default:hardness/none - overrides: - push-reaction: destroy - map-color: 26 - luminance: 1 - tags: - - minecraft:enderman_holdable - - minecraft:replaceable_by_mushrooms - states: - template: default:block_state/mushroom - arguments: - base_block: dead_fire_coral_wall_fan - model_path: minecraft:block/custom/large_mushroom diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index f73618373..2d911abe4 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -29,7 +29,6 @@ categories: - default:palm_pressure_plate - default:palm_button - default:palm_fence - - default:infected_palm_log default:topaz: name: <#FF8C00> hidden: true @@ -84,7 +83,4 @@ categories: - default:amethyst_torch - default:hami_melon_seeds - default:hami_melon_slice - - default:hami_melon - - default:small_mushroom - - default:medium_mushroom - - default:large_mushroom \ No newline at end of file + - default:hami_melon \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 91b25f19c..2777d7c88 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -432,7 +432,6 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/configuration/blocks/netherite_anvil.yml"); plugin.saveResource("resources/default/configuration/blocks/amethyst_torch.yml"); plugin.saveResource("resources/default/configuration/blocks/hami_melon.yml"); - plugin.saveResource("resources/default/configuration/blocks/mushroom.yml"); // assets plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/emojis.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/chinese_lantern.png"); @@ -545,10 +544,6 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_slice.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/hami_melon_seeds.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/mushroom.png"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/small_mushroom.json"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/medium_mushroom.json"); - plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/large_mushroom.json"); } private TreeMap> updateCachedConfigFiles() { From d522cf105dd2d7cb124c6129c1f648ed2cbe5cfc Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 16:31:48 +0800 Subject: [PATCH 185/226] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/ChimeBlockBehavior.java | 37 +- .../configuration/blocks/palm_tree.yml | 936 ++++++++---------- .../resources/default/configuration/i18n.yml | 16 - .../core/block/properties/Property.java | 2 +- 4 files changed, 437 insertions(+), 554 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index f008b81e1..b7897e76a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -7,50 +7,43 @@ import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.util.Pair; import net.momirealms.craftengine.core.util.RandomUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; public class ChimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final SoundData hitSound; - private final boolean randomPitch; - private final float randomMultiplier; + private final List> hitSounds; - public ChimeBlockBehavior(CustomBlock customBlock, SoundData hitSound, boolean randomPitch, float randomMultiplier) { + public ChimeBlockBehavior(CustomBlock customBlock, List> hitSounds) { super(customBlock); - this.hitSound = hitSound; - this.randomPitch = randomPitch; - this.randomMultiplier = randomMultiplier; + this.hitSounds = hitSounds; } @Override public void onProjectileHit(Object thisBlock, Object[] args, Callable superMethod) { + Pair hitSound = hitSounds.get(RandomUtils.generateRandomInt(0, hitSounds.size())); Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(args[2]); - Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.id()), Optional.empty()); - float pitch = hitSound.pitch().get(); - if (randomPitch) { - pitch = pitch + RandomUtils.generateRandomInt(0, 1) * this.randomMultiplier; - } - FastNMS.INSTANCE.method$LevelAccessor$playSound(args[0], null, blockPos, sound, CoreReflections.instance$SoundSource$BLOCKS, hitSound.volume().get(), pitch); + Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.left().id()), Optional.empty()); + float pitch = hitSound.left().pitch().get() + RandomUtils.generateRandomInt(0, 1) * hitSound.right(); + FastNMS.INSTANCE.method$LevelAccessor$playSound(args[0], null, blockPos, sound, CoreReflections.instance$SoundSource$BLOCKS, hitSound.left().volume().get(), pitch); } public static class Factory implements BlockBehaviorFactory { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("hit-sound"), "warning.config.block.behavior.chime.missing_hit_sound"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); - Map randomPitch = ResourceConfigUtils.getAsMapOrNull(arguments.get("random-pitch"), "random-pitch"); - boolean enableRandomPitch = false; - float randomMultiplier = 1f; - if (randomPitch != null) { - enableRandomPitch = true; - randomMultiplier = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("multiplier", 1f), "multiplier"); - } - return new ChimeBlockBehavior(block, hitSound, enableRandomPitch, randomMultiplier); + List> hitSounds = ResourceConfigUtils.parseConfigAsList(arguments.get("hit-sounds"), map -> { + SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("sound"), "warning.config.block.behavior.chime.missing_hit_sound"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); + float randomMultiplier = ResourceConfigUtils.getAsFloat(arguments.get("random-pitch-multiplier"), "random-pitch-multiplier"); + return Pair.of(hitSound, randomMultiplier); + }); + return new ChimeBlockBehavior(block, hitSounds); } } } diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 789f9768a..7abfca063 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -17,7 +17,30 @@ items: parent: minecraft:block/custom/palm_log behavior: type: block_item - block: default:palm_log + block: + behavior: + type: strippable_block + stripped: default:stripped_palm_log + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/palm_log_top + texture_side_path: minecraft:block/custom/palm_log + model_vertical_path: minecraft:block/custom/palm_log + model_horizontal_path: minecraft:block/custom/palm_log_horizontal + vanilla_id: + type: self_increase_int + from: 0 + to: 2 + internal_id: + type: self_increase_int + from: 0 + to: 2 default:stripped_palm_log: material: nether_brick custom-model-data: 1001 @@ -36,7 +59,27 @@ items: parent: minecraft:block/custom/stripped_palm_log behavior: type: block_item - block: default:stripped_palm_log + block: + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/stripped_palm_log_top + texture_side_path: minecraft:block/custom/stripped_palm_log + model_vertical_path: minecraft:block/custom/stripped_palm_log + model_horizontal_path: minecraft:block/custom/stripped_palm_log_horizontal + vanilla_id: + type: self_increase_int + from: 3 + to: 5 + internal_id: + type: self_increase_int + from: 3 + to: 5 default:palm_wood: material: nether_brick custom-model-data: 1002 @@ -55,7 +98,30 @@ items: parent: minecraft:block/custom/palm_wood behavior: type: block_item - block: default:palm_wood + block: + behavior: + type: strippable_block + stripped: default:stripped_palm_wood + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/palm_log + texture_side_path: minecraft:block/custom/palm_log + model_vertical_path: minecraft:block/custom/palm_wood + model_horizontal_path: minecraft:block/custom/palm_wood_horizontal + vanilla_id: + type: self_increase_int + from: 6 + to: 8 + internal_id: + type: self_increase_int + from: 6 + to: 8 default:stripped_palm_wood: material: nether_brick custom-model-data: 1003 @@ -74,7 +140,27 @@ items: parent: minecraft:block/custom/stripped_palm_wood behavior: type: block_item - block: default:stripped_palm_wood + block: + loot: + template: default:loot_table/self + settings: + template: default:settings/wood + states: + template: default:block_state/pillar + arguments: + base_block: note_block + texture_top_path: minecraft:block/custom/stripped_palm_log + texture_side_path: minecraft:block/custom/stripped_palm_log + model_vertical_path: minecraft:block/custom/stripped_palm_wood + model_horizontal_path: minecraft:block/custom/stripped_palm_wood_horizontal + vanilla_id: + type: self_increase_int + from: 9 + to: 11 + internal_id: + type: self_increase_int + from: 9 + to: 11 default:palm_planks: material: nether_brick custom-model-data: 1004 @@ -92,7 +178,18 @@ items: parent: minecraft:block/custom/palm_planks behavior: type: block_item - block: default:palm_planks + block: + settings: + template: default:settings/planks + loot: + template: default:loot_table/self + state: + model: + template: default:model/simplified_cube_all + arguments: + path: minecraft:block/custom/palm_planks + id: 12 + state: note_block:12 default:palm_sapling: material: nether_brick custom-model-data: 1005 @@ -107,7 +204,42 @@ items: texture: minecraft:block/custom/palm_sapling behavior: type: block_item - block: default:palm_sapling + block: + settings: + template: default:settings/sapling + behaviors: + - type: bush_block + bottom-block-tags: + - minecraft:dirt + - minecraft:farmland + - minecraft:sand + - type: sapling_block + feature: minecraft:fancy_oak + bone-meal-success-chance: 0.45 + loot: + template: default:loot_table/self + states: + properties: + stage: + type: int + default-value: 0 + range: 0~1 + appearances: + default: + state: oak_sapling:0 + model: + path: minecraft:block/custom/palm_sapling + generation: + parent: minecraft:block/cross + textures: + cross: minecraft:block/custom/palm_sapling + variants: + stage=0: + appearance: default + id: 0 + stage=1: + appearance: default + id: 1 default:palm_leaves: material: oak_leaves custom-model-data: 1000 @@ -115,9 +247,9 @@ items: item-name: components: minecraft:block_state: - distance: "1" - persistent: "false" - waterlogged: "false" + distance: '1' + persistent: 'false' + waterlogged: 'false' model: type: minecraft:model path: minecraft:item/custom/palm_leaves @@ -128,7 +260,27 @@ items: value: -12012264 behavior: type: block_item - block: default:palm_leaves + block: + behavior: + type: leaves_block + loot: + template: default:loot_table/leaves + arguments: + leaves: default:palm_leaves + sapling: default:palm_sapling + settings: + template: default:settings/leaves + states: + template: default:block_state/leaves + arguments: + default_state: oak_leaves[distance=1,persistent=false,waterlogged=false] + waterlogged_state: oak_leaves[distance=1,persistent=false,waterlogged=true] + model_path: minecraft:block/custom/palm_leaves + texture_path: minecraft:block/custom/palm_leaves + internal_id: + type: self_increase_int + from: 0 + to: 27 default:palm_trapdoor: material: nether_brick custom-model-data: 1006 @@ -143,7 +295,47 @@ items: parent: minecraft:block/custom/palm_trapdoor_bottom behavior: type: block_item - block: default:palm_trapdoor + block: + behavior: + type: trapdoor_block + can-open-with-hand: true + can-open-by-wind-charge: true + sounds: + open: minecraft:block.wooden_trapdoor.open + close: minecraft:block.wooden_trapdoor.close + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + overrides: + map-color: 2 + instrument: bass + hardness: 3.0 + resistance: 3.0 + burnable: true + tags: + - minecraft:mineable/axe + - minecraft:trapdoors + states: + template: default:block_state/trapdoor + arguments: + base_block: acacia_trapdoor + model_bottom_path: minecraft:block/custom/palm_trapdoor_bottom + model_bottom_generation: + parent: minecraft:block/template_orientable_trapdoor_bottom + textures: + texture: minecraft:block/custom/palm_trapdoor + model_open_path: minecraft:block/custom/palm_trapdoor_open + model_open_generation: + parent: minecraft:block/template_orientable_trapdoor_open + textures: + texture: minecraft:block/custom/palm_trapdoor + model_top_path: minecraft:block/custom/palm_trapdoor_top + model_top_generation: + parent: minecraft:block/template_orientable_trapdoor_top + textures: + texture: minecraft:block/custom/palm_trapdoor default:palm_door: material: nether_brick custom-model-data: 1007 @@ -157,7 +349,68 @@ items: path: minecraft:item/custom/palm_door behavior: type: double_high_block_item - block: default:palm_door + block: + behavior: + type: door_block + can-open-with-hand: true + can-open-by-wind-charge: true + sounds: + open: minecraft:block.wooden_door.open + close: minecraft:block.wooden_door.close + loot: + template: default:loot_table/door + settings: + template: + - default:sound/wood + overrides: + push-reaction: destroy + map-color: 2 + instrument: bass + hardness: 3.0 + resistance: 3.0 + burnable: true + tags: + - minecraft:wooden_doors + - minecraft:doors + - minecraft:mineable/axe + states: + template: default:block_state/door + arguments: + base_block: oak_door + model_top_left_path: minecraft:block/custom/palm_door_top_left + model_top_left_generation: + parent: minecraft:block/door_top_left + textures: &textures + bottom: minecraft:block/custom/palm_door_bottom + top: minecraft:block/custom/palm_door_top + model_top_right_path: minecraft:block/custom/palm_door_top_right + model_top_right_generation: + parent: minecraft:block/door_top_right + textures: *textures + model_top_left_open_path: minecraft:block/custom/palm_door_top_left_open + model_top_left_open_generation: + parent: minecraft:block/door_top_left_open + textures: *textures + model_top_right_open_path: minecraft:block/custom/palm_door_top_right_open + model_top_right_open_generation: + parent: minecraft:block/door_top_right_open + textures: *textures + model_bottom_left_path: minecraft:block/custom/palm_door_bottom_left + model_bottom_left_generation: + parent: minecraft:block/door_bottom_left + textures: *textures + model_bottom_right_path: minecraft:block/custom/palm_door_bottom_right + model_bottom_right_generation: + parent: minecraft:block/door_bottom_right + textures: *textures + model_bottom_left_open_path: minecraft:block/custom/palm_door_bottom_left_open + model_bottom_left_open_generation: + parent: minecraft:block/door_bottom_left_open + textures: *textures + model_bottom_right_open_path: minecraft:block/custom/palm_door_bottom_right_open + model_bottom_right_open_generation: + parent: minecraft:block/door_bottom_right_open + textures: *textures default:palm_fence_gate: material: nether_brick custom-model-data: 1008 @@ -172,7 +425,49 @@ items: parent: minecraft:block/custom/palm_fence_gate behavior: type: block_item - block: default:palm_fence_gate + block: + behaviors: + type: fence_gate_block + can-open-with-hand: true + can-open-by-wind-charge: true + sounds: + open: minecraft:block.fence_gate.open + close: minecraft:block.fence_gate.close + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/planks + overrides: + map-color: 2 + instrument: bass + burnable: true + tags: + - minecraft:fence_gates + - minecraft:mineable/axe + - minecraft:unstable_bottom_center + states: + template: default:block_state/fence_gate + arguments: + base_block: oak_fence_gate + model_fence_gate_path: minecraft:block/custom/palm_fence_gate + model_fence_gate_generation: + parent: minecraft:block/template_fence_gate + textures: &textures + texture: minecraft:block/custom/palm_planks + model_fence_gate_open_path: minecraft:block/custom/palm_fence_gate_open + model_fence_gate_open_generation: + parent: minecraft:block/template_fence_gate_open + textures: *textures + model_fence_gate_wall_path: minecraft:block/custom/palm_fence_gate_wall + model_fence_gate_wall_generation: + parent: minecraft:block/template_fence_gate_wall + textures: *textures + model_fence_gate_wall_open_path: minecraft:block/custom/palm_fence_gate_wall_open + model_fence_gate_wall_open_generation: + parent: minecraft:block/template_fence_gate_wall_open + textures: *textures default:palm_slab: material: nether_brick custom-model-data: 1009 @@ -187,7 +482,39 @@ items: parent: minecraft:block/custom/palm_slab behavior: type: block_item - block: default:palm_slab + block: + behaviors: + type: slab_block + loot: + template: default:loot_table/slab + settings: + template: + - default:sound/wood + - default:burn_data/planks + - default:hardness/planks + overrides: + map-color: 2 + instrument: bass + tags: + - minecraft:wooden_slabs + - minecraft:slabs + - minecraft:mineable/axe + states: + template: default:block_state/slab + arguments: + base_block: petrified_oak_slab + model_bottom_path: minecraft:block/custom/palm_slab + model_bottom_generation: + parent: minecraft:block/slab + textures: &textures + bottom: minecraft:block/custom/palm_planks + side: minecraft:block/custom/palm_planks + top: minecraft:block/custom/palm_planks + model_top_path: minecraft:block/custom/palm_slab_top + model_top_generation: + parent: minecraft:block/slab_top + textures: *textures + model_double_path: minecraft:block/custom/palm_planks default:palm_stairs: material: nether_brick custom-model-data: 1013 @@ -202,7 +529,42 @@ items: fuel-time: 300 behavior: type: block_item - block: default:palm_stairs + block: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/planks + - default:burn_data/planks + overrides: + map-color: 2 + instrument: bass + tags: + - minecraft:mineable/axe + - minecraft:stairs + - minecraft:wooden_stairs + behavior: + type: stairs_block + states: + template: default:block_state/stairs + arguments: + base_block: cut_copper_stairs + model_stairs_inner_path: minecraft:block/custom/palm_stairs_inner + model_stairs_inner_generation: + parent: minecraft:block/inner_stairs + textures: &textures + bottom: &block_texture minecraft:block/custom/palm_planks + side: *block_texture + top: *block_texture + model_stairs_outer_path: minecraft:block/custom/palm_stairs_outer + model_stairs_outer_generation: + parent: minecraft:block/outer_stairs + textures: *textures + model_stairs_path: minecraft:block/custom/palm_stairs + model_stairs_generation: + parent: minecraft:block/stairs + textures: *textures default:palm_pressure_plate: material: nether_brick custom-model-data: 1014 @@ -217,44 +579,49 @@ items: fuel-time: 300 behavior: type: block_item - block: default:palm_pressure_plate - default:palm_button: - material: nether_brick - custom-model-data: 1015 - model: - type: minecraft:model - path: minecraft:item/custom/palm_button - generation: - parent: minecraft:block/button_inventory - textures: - texture: minecraft:block/custom/palm_planks - data: - item-name: - settings: - fuel-time: 100 - behavior: - type: block_item - block: default:palm_button - default:palm_button_pressed: - material: nether_brick - custom-model-data: 1016 - model: - type: minecraft:model - path: minecraft:block/custom/palm_button_pressed - generation: - parent: minecraft:block/button_pressed - textures: - texture: minecraft:block/custom/palm_planks - default:palm_button_not_pressed: - material: nether_brick - custom-model-data: 1017 - model: - type: minecraft:model - path: minecraft:block/custom/palm_button_not_pressed - generation: - parent: minecraft:block/button - textures: - texture: minecraft:block/custom/palm_planks + block: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/planks + overrides: + burnable: true + push-reaction: destroy + map-color: 2 + instrument: bass + tags: + - minecraft:mineable/axe + - minecraft:wall_post_override + - minecraft:wooden_pressure_plates + - minecraft:pressure_plates + behaviors: + type: pressure_plate_block + sensitivity: all + pressed-time: 20 + sounds: + on: minecraft:block.wooden_pressure_plate.click_on + off: minecraft:block.wooden_pressure_plate.click_off + states: + template: default:block_state/pressure_plate + arguments: + normal_state: light_weighted_pressure_plate:0 + powered_state: light_weighted_pressure_plate:1 + normal_id: 0 + powered_id: 1 + model_normal_path: minecraft:block/custom/palm_pressure_plate + model_normal_generation: + parent: minecraft:block/pressure_plate_up + textures: + texture: minecraft:block/custom/palm_planks + model_powered_path: minecraft:block/custom/palm_pressure_plate_down + model_powered_generation: + parent: minecraft:block/pressure_plate_down + textures: + texture: minecraft:block/custom/palm_planks + +items#palm_fence: default:palm_fence: material: nether_brick custom-model-data: 1018 @@ -291,468 +658,7 @@ items: textures: texture: minecraft:block/custom/palm_planks -blocks: - default:palm_log: - behavior: - type: strippable_block - stripped: default:stripped_palm_log - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - overrides: - map-color: 2 - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/palm_log_top - texture_side_path: minecraft:block/custom/palm_log - model_vertical_path: minecraft:block/custom/palm_log - model_horizontal_path: minecraft:block/custom/palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 0 - to: 2 - internal_id: - type: self_increase_int - from: 0 - to: 2 - default:stripped_palm_log: - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - overrides: - map-color: 2 - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/stripped_palm_log_top - texture_side_path: minecraft:block/custom/stripped_palm_log - model_vertical_path: minecraft:block/custom/stripped_palm_log - model_horizontal_path: minecraft:block/custom/stripped_palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 3 - to: 5 - internal_id: - type: self_increase_int - from: 3 - to: 5 - default:palm_wood: - behavior: - type: strippable_block - stripped: default:stripped_palm_wood - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - overrides: - map-color: 2 - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/palm_log - texture_side_path: minecraft:block/custom/palm_log - model_vertical_path: minecraft:block/custom/palm_wood - model_horizontal_path: minecraft:block/custom/palm_wood_horizontal - vanilla_id: - type: self_increase_int - from: 6 - to: 8 - internal_id: - type: self_increase_int - from: 6 - to: 8 - default:stripped_palm_wood: - loot: - template: default:loot_table/self - settings: - template: default:settings/wood - overrides: - map-color: 2 - states: - template: default:block_state/pillar - arguments: - base_block: note_block - texture_top_path: minecraft:block/custom/stripped_palm_log - texture_side_path: minecraft:block/custom/stripped_palm_log - model_vertical_path: minecraft:block/custom/stripped_palm_wood - model_horizontal_path: minecraft:block/custom/stripped_palm_wood_horizontal - vanilla_id: - type: self_increase_int - from: 9 - to: 11 - internal_id: - type: self_increase_int - from: 9 - to: 11 - default:palm_planks: - settings: - template: default:settings/planks - overrides: - map-color: 2 - loot: - template: default:loot_table/self - state: - model: - template: default:model/simplified_cube_all - arguments: - path: minecraft:block/custom/palm_planks - id: 12 - state: note_block:12 - default:palm_sapling: - settings: - template: default:settings/sapling - behaviors: - - type: bush_block - bottom-block-tags: - - minecraft:dirt - - minecraft:farmland - - minecraft:sand - - type: sapling_block - feature: minecraft:fancy_oak - bone-meal-success-chance: 0.45 - loot: - template: default:loot_table/self - states: - properties: - stage: - type: int - default-value: 0 - range: 0~1 - appearances: - default: - state: oak_sapling:0 - model: - path: minecraft:block/custom/palm_sapling - generation: - parent: minecraft:block/cross - textures: - cross: minecraft:block/custom/palm_sapling - variants: - stage=0: - appearance: default - id: 0 - stage=1: - appearance: default - id: 1 - default:palm_leaves: - behavior: - type: leaves_block - loot: - template: default:loot_table/leaves - arguments: - leaves: default:palm_leaves - sapling: default:palm_sapling - settings: - template: default:settings/leaves - overrides: - map-color: 19 - states: - template: default:block_state/leaves - arguments: - default_state: oak_leaves[distance=1,persistent=false,waterlogged=false] - waterlogged_state: oak_leaves[distance=1,persistent=false,waterlogged=true] - model_path: minecraft:block/custom/palm_leaves - texture_path: minecraft:block/custom/palm_leaves - internal_id: - type: self_increase_int - from: 0 - to: 27 - default:palm_trapdoor: - behavior: - type: trapdoor_block - can-open-with-hand: true - can-open-by-wind-charge: true - sounds: - open: minecraft:block.wooden_trapdoor.open - close: minecraft:block.wooden_trapdoor.close - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - overrides: - map-color: 2 - instrument: bass - hardness: 3.0 - resistance: 3.0 - burnable: true - tags: - - minecraft:mineable/axe - - minecraft:trapdoors - states: - template: default:block_state/trapdoor - arguments: - base_block: acacia_trapdoor - model_bottom_path: minecraft:block/custom/palm_trapdoor_bottom - model_bottom_generation: - parent: minecraft:block/template_orientable_trapdoor_bottom - textures: - texture: minecraft:block/custom/palm_trapdoor - model_open_path: minecraft:block/custom/palm_trapdoor_open - model_open_generation: - parent: minecraft:block/template_orientable_trapdoor_open - textures: - texture: minecraft:block/custom/palm_trapdoor - model_top_path: minecraft:block/custom/palm_trapdoor_top - model_top_generation: - parent: minecraft:block/template_orientable_trapdoor_top - textures: - texture: minecraft:block/custom/palm_trapdoor - default:palm_door: - behavior: - type: door_block - can-open-with-hand: true - can-open-by-wind-charge: true - sounds: - open: minecraft:block.wooden_door.open - close: minecraft:block.wooden_door.close - loot: - template: default:loot_table/door - settings: - template: - - default:sound/wood - overrides: - push-reaction: destroy - map-color: 2 - instrument: bass - hardness: 3.0 - resistance: 3.0 - burnable: true - tags: - - minecraft:wooden_doors - - minecraft:doors - - minecraft:mineable/axe - states: - template: default:block_state/door - arguments: - base_block: oak_door - model_top_left_path: minecraft:block/custom/palm_door_top_left - model_top_left_generation: - parent: minecraft:block/door_top_left - textures: &textures - bottom: minecraft:block/custom/palm_door_bottom - top: minecraft:block/custom/palm_door_top - model_top_right_path: minecraft:block/custom/palm_door_top_right - model_top_right_generation: - parent: minecraft:block/door_top_right - textures: *textures - model_top_left_open_path: minecraft:block/custom/palm_door_top_left_open - model_top_left_open_generation: - parent: minecraft:block/door_top_left_open - textures: *textures - model_top_right_open_path: minecraft:block/custom/palm_door_top_right_open - model_top_right_open_generation: - parent: minecraft:block/door_top_right_open - textures: *textures - model_bottom_left_path: minecraft:block/custom/palm_door_bottom_left - model_bottom_left_generation: - parent: minecraft:block/door_bottom_left - textures: *textures - model_bottom_right_path: minecraft:block/custom/palm_door_bottom_right - model_bottom_right_generation: - parent: minecraft:block/door_bottom_right - textures: *textures - model_bottom_left_open_path: minecraft:block/custom/palm_door_bottom_left_open - model_bottom_left_open_generation: - parent: minecraft:block/door_bottom_left_open - textures: *textures - model_bottom_right_open_path: minecraft:block/custom/palm_door_bottom_right_open - model_bottom_right_open_generation: - parent: minecraft:block/door_bottom_right_open - textures: *textures - default:palm_fence_gate: - behaviors: - type: fence_gate_block - can-open-with-hand: true - can-open-by-wind-charge: true - sounds: - open: minecraft:block.fence_gate.open - close: minecraft:block.fence_gate.close - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/planks - overrides: - map-color: 2 - instrument: bass - burnable: true - tags: - - minecraft:fence_gates - - minecraft:mineable/axe - - minecraft:unstable_bottom_center - states: - template: default:block_state/fence_gate - arguments: - base_block: oak_fence_gate - model_fence_gate_path: minecraft:block/custom/palm_fence_gate - model_fence_gate_generation: - parent: minecraft:block/template_fence_gate - textures: &textures - texture: minecraft:block/custom/palm_planks - model_fence_gate_open_path: minecraft:block/custom/palm_fence_gate_open - model_fence_gate_open_generation: - parent: minecraft:block/template_fence_gate_open - textures: *textures - model_fence_gate_wall_path: minecraft:block/custom/palm_fence_gate_wall - model_fence_gate_wall_generation: - parent: minecraft:block/template_fence_gate_wall - textures: *textures - model_fence_gate_wall_open_path: minecraft:block/custom/palm_fence_gate_wall_open - model_fence_gate_wall_open_generation: - parent: minecraft:block/template_fence_gate_wall_open - textures: *textures - default:palm_slab: - behaviors: - type: slab_block - loot: - template: default:loot_table/slab - settings: - template: - - default:sound/wood - - default:burn_data/planks - - default:hardness/planks - overrides: - map-color: 2 - instrument: bass - tags: - - minecraft:wooden_slabs - - minecraft:slabs - - minecraft:mineable/axe - states: - template: default:block_state/slab - arguments: - base_block: petrified_oak_slab - model_bottom_path: minecraft:block/custom/palm_slab - model_bottom_generation: - parent: minecraft:block/slab - textures: &textures - bottom: minecraft:block/custom/palm_planks - side: minecraft:block/custom/palm_planks - top: minecraft:block/custom/palm_planks - model_top_path: minecraft:block/custom/palm_slab_top - model_top_generation: - parent: minecraft:block/slab_top - textures: *textures - model_double_path: minecraft:block/custom/palm_planks - default:palm_stairs: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/planks - - default:burn_data/planks - overrides: - map-color: 2 - instrument: bass - tags: - - minecraft:mineable/axe - - minecraft:stairs - - minecraft:wooden_stairs - behavior: - type: stairs_block - states: - template: default:block_state/stairs - arguments: - base_block: cut_copper_stairs - model_stairs_inner_path: minecraft:block/custom/palm_stairs_inner - model_stairs_inner_generation: - parent: minecraft:block/inner_stairs - textures: &textures - bottom: &block_texture minecraft:block/custom/palm_planks - side: *block_texture - top: *block_texture - model_stairs_outer_path: minecraft:block/custom/palm_stairs_outer - model_stairs_outer_generation: - parent: minecraft:block/outer_stairs - textures: *textures - model_stairs_path: minecraft:block/custom/palm_stairs - model_stairs_generation: - parent: minecraft:block/stairs - textures: *textures - default:palm_pressure_plate: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/planks - overrides: - burnable: true - push-reaction: destroy - map-color: 2 - instrument: bass - tags: - - minecraft:mineable/axe - - minecraft:wall_post_override - - minecraft:wooden_pressure_plates - - minecraft:pressure_plates - behaviors: - type: pressure_plate_block - sensitivity: all - pressed-time: 20 - sounds: - on: minecraft:block.wooden_pressure_plate.click_on - off: minecraft:block.wooden_pressure_plate.click_off - states: - template: default:block_state/pressure_plate - arguments: - normal_state: light_weighted_pressure_plate:0 - powered_state: light_weighted_pressure_plate:1 - normal_id: 0 - powered_id: 1 - model_normal_path: minecraft:block/custom/palm_pressure_plate - model_normal_generation: - parent: minecraft:block/pressure_plate_up - textures: - texture: minecraft:block/custom/palm_planks - model_powered_path: minecraft:block/custom/palm_pressure_plate_down - model_powered_generation: - parent: minecraft:block/pressure_plate_down - textures: - texture: minecraft:block/custom/palm_planks - default:palm_button: - loot: - template: default:loot_table/self - settings: - template: - - default:sound/wood - - default:hardness/button - overrides: - burnable: true - push-reaction: destroy - map-color: 2 - instrument: harp - tags: - - minecraft:buttons - - minecraft:mineable/axe - - minecraft:wooden_buttons - behaviors: - - type: face_attached_horizontal_directional_block - - type: button_block - ticks-to-stay-pressed: 30 - can-button-be-activated-by-arrows: true - sounds: - on: minecraft:block.wooden_button.click_on - off: minecraft:block.wooden_button.click_off - states: - template: default:block_state/button - arguments: - base_block: birch_button - pressed_item: default:palm_button_pressed - not_pressed_item: default:palm_button_not_pressed - internal_id: - type: self_increase_int - from: 0 - to: 23 +blocks#palm_fence: default:palm_fence: loot: template: default:loot_table/self diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index b8ad2e75d..71d6e9e30 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -54,10 +54,6 @@ i18n: item.hami_melon_seeds: Hami Melon Seeds item.palm_button: Palm Button item.palm_fence: Palm Fence - item.infected_palm_log: Infected Palm Log - item.small_mushroom: Small Mushroom - item.medium_mushroom: Medium Mushroom - item.large_mushroom: Large Mushroom category.default.name: Default Assets category.default.lore: Contains the default configuration of CraftEngine category.palm_tree: Palm Tree @@ -121,10 +117,6 @@ i18n: item.hami_melon_seeds: 哈密瓜种子 item.palm_button: 棕榈木按钮 item.palm_fence: 棕榈木栅栏 - item.infected_palm_log: 菌蚀棕榈原木 - item.small_mushroom: 小型蘑菇 - item.medium_mushroom: 中型蘑菇 - item.large_mushroom: 大型蘑菇 category.default.name: 默认资产 category.default.lore: 包含了CraftEngine的默认配置 category.palm_tree: 棕榈树 @@ -172,10 +164,6 @@ lang: block_name:default:default:attached_hami_melon_stem: Hami Melon Stem block_name:default:palm_button: Palm Button block_name:default:palm_fence: Palm Fence - block_name:default:infected_palm_log: Infected Palm Log - block_name:default:small_mushroom: Small Mushroom - block_name:default:medium_mushroom: Medium Mushroom - block_name:default:large_mushroom: Large Mushroom zh_cn: block_name:default:chinese_lantern: 灯笼 block_name:default:netherite_anvil: 下界合金砧 @@ -211,7 +199,3 @@ lang: block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 block_name:default:palm_button: 棕榈木按钮 block_name:default:palm_fence: 棕榈木栅栏 - block_name:default:infected_palm_log: 菌蚀棕榈原木 - block_name:default:small_mushroom: 小型蘑菇 - block_name:default:medium_mushroom: 中型蘑菇 - block_name:default:large_mushroom: 大型蘑菇 diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java index 8ff91b9a8..233ad526b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java @@ -171,6 +171,6 @@ public abstract class Property> { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("name", this.name).add("clazz", this.clazz).add("values", this.possibleValues()).toString(); + return this.getClass().getSimpleName() + "{clazz=" + this.clazz + ", name='" + this.name + "', values=" + this.possibleValues() + '}'; } } From 154779cdf49fc4dd68728bf69335060036b46c9b Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 16:32:27 +0800 Subject: [PATCH 186/226] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/behavior/ChimeBlockBehavior.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index b7897e76a..b61aa1243 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -27,6 +27,7 @@ public class ChimeBlockBehavior extends BukkitBlockBehavior { @Override public void onProjectileHit(Object thisBlock, Object[] args, Callable superMethod) { + if (hitSounds.isEmpty()) return; Pair hitSound = hitSounds.get(RandomUtils.generateRandomInt(0, hitSounds.size())); Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(args[2]); Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.left().id()), Optional.empty()); From a3e9e7491324dd427baa63d83afdd8fb5e85ea76 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 16:35:43 +0800 Subject: [PATCH 187/226] =?UTF-8?q?=E7=AE=80=E5=8C=96=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/ChimeBlockBehavior.java | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index b61aa1243..fffedcef1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -7,44 +7,34 @@ import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.sound.SoundData; -import net.momirealms.craftengine.core.util.Pair; -import net.momirealms.craftengine.core.util.RandomUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; public class ChimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final List> hitSounds; + private final SoundData hitSound; - public ChimeBlockBehavior(CustomBlock customBlock, List> hitSounds) { + public ChimeBlockBehavior(CustomBlock customBlock, SoundData hitSound) { super(customBlock); - this.hitSounds = hitSounds; + this.hitSound = hitSound; } @Override public void onProjectileHit(Object thisBlock, Object[] args, Callable superMethod) { - if (hitSounds.isEmpty()) return; - Pair hitSound = hitSounds.get(RandomUtils.generateRandomInt(0, hitSounds.size())); Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(args[2]); - Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.left().id()), Optional.empty()); - float pitch = hitSound.left().pitch().get() + RandomUtils.generateRandomInt(0, 1) * hitSound.right(); - FastNMS.INSTANCE.method$LevelAccessor$playSound(args[0], null, blockPos, sound, CoreReflections.instance$SoundSource$BLOCKS, hitSound.left().volume().get(), pitch); + Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(hitSound.id()), Optional.empty()); + FastNMS.INSTANCE.method$LevelAccessor$playSound(args[0], null, blockPos, sound, CoreReflections.instance$SoundSource$BLOCKS, hitSound.volume().get(), hitSound.pitch().get()); } public static class Factory implements BlockBehaviorFactory { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - List> hitSounds = ResourceConfigUtils.parseConfigAsList(arguments.get("hit-sounds"), map -> { - SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("sound"), "warning.config.block.behavior.chime.missing_hit_sound"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); - float randomMultiplier = ResourceConfigUtils.getAsFloat(arguments.get("random-pitch-multiplier"), "random-pitch-multiplier"); - return Pair.of(hitSound, randomMultiplier); - }); - return new ChimeBlockBehavior(block, hitSounds); + SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("hit-sound"), "warning.config.block.behavior.chime.missing_hit_sound"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); + return new ChimeBlockBehavior(block, hitSound); } } } From 71915dce84bd913debba8d66a34205ad28da4cd4 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 16:43:20 +0800 Subject: [PATCH 188/226] =?UTF-8?q?=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/ChimeBlockBehavior.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index fffedcef1..cadbbbcf7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -33,7 +33,12 @@ public class ChimeBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("hit-sound"), "warning.config.block.behavior.chime.missing_hit_sound"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); + SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow( + Optional.ofNullable(arguments.get("sounds")) + .map(o -> ResourceConfigUtils.getAsMap(o , "hit").get("hit")) + .orElse(null), + "warning.config.block.behavior.chime.missing_hit_sound" + ), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); return new ChimeBlockBehavior(block, hitSound); } } From 80eebec58626b8f46e3026cb81a85adb28cb8857 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 16:52:34 +0800 Subject: [PATCH 189/226] =?UTF-8?q?=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BuddingBlockBehavior.java | 8 ++-- .../block/behavior/ChimeBlockBehavior.java | 2 +- .../resources/default/configuration/i18n.yml | 4 +- .../default/configuration/templates.yml | 37 ------------------- .../src/main/resources/translations/en.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 2 +- 6 files changed, 9 insertions(+), 46 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java index a42367d2d..0fab2a5ca 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BuddingBlockBehavior.java @@ -20,10 +20,10 @@ import java.util.concurrent.Callable; public class BuddingBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final int growthChance; + private final float growthChance; private final List blocks; - public BuddingBlockBehavior(CustomBlock customBlock, int growthChance, List blocks) { + public BuddingBlockBehavior(CustomBlock customBlock, float growthChance, List blocks) { super(customBlock); this.growthChance = growthChance; this.blocks = blocks; @@ -31,7 +31,7 @@ public class BuddingBlockBehavior extends BukkitBlockBehavior { @Override public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - if (RandomUtils.generateRandomInt(0, this.growthChance) != 0) return; + if (RandomUtils.generateRandomFloat(0, 1) >= growthChance) return; Object nmsDirection = CoreReflections.instance$Direction$values[RandomUtils.generateRandomInt(0, 6)]; Direction direction = DirectionUtils.fromNMSDirection(nmsDirection); Object blockPos = FastNMS.INSTANCE.method$BlockPos$relative(args[2], nmsDirection); @@ -90,7 +90,7 @@ public class BuddingBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - int growthChance = ResourceConfigUtils.getAsInt(arguments.getOrDefault("growth-chance", 5), "growth-chance"); + float growthChance = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("growth-chance", 0.2), "growth-chance"); List blocks = new ObjectArrayList<>(); MiscUtils.getAsStringList(arguments.get("blocks")).forEach(s -> blocks.add(Key.of(s))); return new BuddingBlockBehavior(block, growthChance, blocks); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index cadbbbcf7..e11eb1d3b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -37,7 +37,7 @@ public class ChimeBlockBehavior extends BukkitBlockBehavior { Optional.ofNullable(arguments.get("sounds")) .map(o -> ResourceConfigUtils.getAsMap(o , "hit").get("hit")) .orElse(null), - "warning.config.block.behavior.chime.missing_hit_sound" + "warning.config.block.behavior.chime.missing_sounds_hit" ), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); return new ChimeBlockBehavior(block, hitSound); } diff --git a/common-files/src/main/resources/resources/default/configuration/i18n.yml b/common-files/src/main/resources/resources/default/configuration/i18n.yml index 71d6e9e30..a0c7eea40 100644 --- a/common-files/src/main/resources/resources/default/configuration/i18n.yml +++ b/common-files/src/main/resources/resources/default/configuration/i18n.yml @@ -161,7 +161,7 @@ lang: block_name:default:amethyst_wall_torch: Amethyst Torch block_name:default:hami_melon: Hami Melon block_name:default:hami_melon_stem: Hami Melon Stem - block_name:default:default:attached_hami_melon_stem: Hami Melon Stem + block_name:default:attached_hami_melon_stem: Hami Melon Stem block_name:default:palm_button: Palm Button block_name:default:palm_fence: Palm Fence zh_cn: @@ -196,6 +196,6 @@ lang: block_name:default:amethyst_wall_torch: 紫水晶火把 block_name:default:hami_melon: 哈密瓜 block_name:default:hami_melon_stem: 哈密瓜茎 - block_name:default:default:attached_hami_melon_stem: 哈密瓜茎 + block_name:default:attached_hami_melon_stem: 哈密瓜茎 block_name:default:palm_button: 棕榈木按钮 block_name:default:palm_fence: 棕榈木栅栏 diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 20392707e..fa284b068 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -3930,43 +3930,6 @@ templates#block_states: resistance: 1200.0 burnable: false fluid-state: water - default:block_state/mushroom: - properties: - facing: - type: horizontal_direction - appearances: - north: - state: ${base_block}[waterlogged=false,facing=north] - model: - path: ${model_path} - east: - state: ${base_block}[waterlogged=false,facing=east] - model: - path: ${model_path} - y: 90 - west: - state: ${base_block}[waterlogged=false,facing=west] - model: - path: ${model_path} - y: 270 - south: - state: ${base_block}[waterlogged=false,facing=south] - model: - path: ${model_path} - y: 180 - variants: - facing=north: - appearance: north - id: 0 - facing=east: - appearance: east - id: 1 - facing=west: - appearance: west - id: 2 - facing=south: - appearance: south - id: 3 # recipes templates#recipes: default:recipe/planks: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 6c88ca4a0..33f9fde42 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -336,7 +336,7 @@ warning.config.block.behavior.stem.missing_attached_stem: "Issue found i warning.config.block.behavior.attached_stem.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'attached_stem_block' behavior." warning.config.block.behavior.attached_stem.missing_fruit: "Issue found in file - The block '' is missing the required 'fruit' argument for 'attached_stem_block' behavior." warning.config.block.behavior.attached_stem.missing_stem: "Issue found in file - The block '' is missing the required 'stem' argument for 'attached_stem_block' behavior." -warning.config.block.behavior.chime.missing_hit_sound: "Issue found in file - The block '' is missing the required 'hit-sound' argument for 'chime_block' behavior." +warning.config.block.behavior.chime.missing_sounds_hit: "Issue found in file - The block '' is missing the required 'sounds.hit' argument for 'chime_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 4072ac068..0bc2572c9 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -330,7 +330,7 @@ warning.config.block.behavior.stem.missing_attached_stem: "在文件 在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.attached_stem.missing_fruit: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'fruit' 选项" warning.config.block.behavior.attached_stem.missing_stem: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'stem' 选项" -warning.config.block.behavior.chime.missing_hit_sound: "在文件 发现问题 - 方块 '' 的 'chime_block' 行为缺少必需的 'hit-sound' 选项" +warning.config.block.behavior.chime.missing_sounds_hit: "在文件 发现问题 - 方块 '' 的 'chime_block' 行为缺少必需的 'sounds.hit' 选项" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" From 760c0bd560d76802a6e1ccf748f1bc91cf035332 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 17:02:36 +0800 Subject: [PATCH 190/226] =?UTF-8?q?=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/ChimeBlockBehavior.java | 9 +++++++-- common-files/src/main/resources/translations/en.yml | 2 +- common-files/src/main/resources/translations/zh_cn.yml | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index e11eb1d3b..3ddc5b8df 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -10,8 +10,10 @@ import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.stream.Stream; public class ChimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); @@ -35,9 +37,12 @@ public class ChimeBlockBehavior extends BukkitBlockBehavior { public BlockBehavior create(CustomBlock block, Map arguments) { SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow( Optional.ofNullable(arguments.get("sounds")) - .map(o -> ResourceConfigUtils.getAsMap(o , "hit").get("hit")) + .flatMap(sounds -> Stream.of("projectile-hit", "chime") + .map(type -> ResourceConfigUtils.getAsMap(sounds, type).get(type)) + .filter(Objects::nonNull) + .findFirst()) .orElse(null), - "warning.config.block.behavior.chime.missing_sounds_hit" + "warning.config.block.behavior.chime.missing_sounds_projectile_hit" ), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); return new ChimeBlockBehavior(block, hitSound); } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 33f9fde42..092ca02ce 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -336,7 +336,7 @@ warning.config.block.behavior.stem.missing_attached_stem: "Issue found i warning.config.block.behavior.attached_stem.missing_facing: "Issue found in file - The block '' is missing the required 'facing' property for 'attached_stem_block' behavior." warning.config.block.behavior.attached_stem.missing_fruit: "Issue found in file - The block '' is missing the required 'fruit' argument for 'attached_stem_block' behavior." warning.config.block.behavior.attached_stem.missing_stem: "Issue found in file - The block '' is missing the required 'stem' argument for 'attached_stem_block' behavior." -warning.config.block.behavior.chime.missing_sounds_hit: "Issue found in file - The block '' is missing the required 'sounds.hit' argument for 'chime_block' behavior." +warning.config.block.behavior.chime.missing_sounds_projectile_hit: "Issue found in file - The block '' is missing the required 'sounds.projectile-hit' argument for 'chime_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 0bc2572c9..8b6fc7227 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -330,7 +330,7 @@ warning.config.block.behavior.stem.missing_attached_stem: "在文件 在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'facing' 属性" warning.config.block.behavior.attached_stem.missing_fruit: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'fruit' 选项" warning.config.block.behavior.attached_stem.missing_stem: "在文件 发现问题 - 方块 '' 的 'attached_stem_block' 行为缺少必需的 'stem' 选项" -warning.config.block.behavior.chime.missing_sounds_hit: "在文件 发现问题 - 方块 '' 的 'chime_block' 行为缺少必需的 'sounds.hit' 选项" +warning.config.block.behavior.chime.missing_sounds_projectile_hit: "在文件 发现问题 - 方块 '' 的 'chime_block' 行为缺少必需的 'sounds.projectile-hit' 选项" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" From d5da77f0764de5ef3ee875206fbdfd222d896133 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Wed, 24 Sep 2025 17:06:02 +0800 Subject: [PATCH 191/226] =?UTF-8?q?=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/ChimeBlockBehavior.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index 3ddc5b8df..56e68b810 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -36,11 +36,8 @@ public class ChimeBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { SoundData hitSound = SoundData.create(ResourceConfigUtils.requireNonNullOrThrow( - Optional.ofNullable(arguments.get("sounds")) - .flatMap(sounds -> Stream.of("projectile-hit", "chime") - .map(type -> ResourceConfigUtils.getAsMap(sounds, type).get(type)) - .filter(Objects::nonNull) - .findFirst()) + Optional.ofNullable(ResourceConfigUtils.getAsMap(arguments.get("sounds"), "sounds")) + .map(sounds -> ResourceConfigUtils.get(sounds, "projectile-hit", "chime")) .orElse(null), "warning.config.block.behavior.chime.missing_sounds_projectile_hit" ), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); From 28f30b1e5b84f15ca714189b81e57d7683e65373 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Wed, 24 Sep 2025 19:04:07 +0800 Subject: [PATCH 192/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/blocks/palm_tree.yml | 81 ++++++++++++++++++- .../default/configuration/categories.yml | 12 +-- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 7abfca063..82036eea1 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -621,7 +621,7 @@ items: textures: texture: minecraft:block/custom/palm_planks -items#palm_fence: +items#pfence: default:palm_fence: material: nether_brick custom-model-data: 1018 @@ -657,8 +657,7 @@ items#palm_fence: parent: minecraft:block/custom/fence_side textures: texture: minecraft:block/custom/palm_planks - -blocks#palm_fence: +blocks#fence: default:palm_fence: loot: template: default:loot_table/self @@ -695,6 +694,80 @@ blocks#palm_fence: from: 0 to: 31 +items#button: + default:palm_button: + material: nether_brick + custom-model-data: 1015 + model: + type: minecraft:model + path: minecraft:item/custom/palm_button + generation: + parent: minecraft:block/button_inventory + textures: + texture: minecraft:block/custom/palm_planks + data: + item-name: + settings: + fuel-time: 100 + behavior: + type: block_item + block: default:palm_button + default:palm_button_pressed: + material: nether_brick + custom-model-data: 1016 + model: + type: minecraft:model + path: minecraft:block/custom/palm_button_pressed + generation: + parent: minecraft:block/button_pressed + textures: + texture: minecraft:block/custom/palm_planks + default:palm_button_not_pressed: + material: nether_brick + custom-model-data: 1017 + model: + type: minecraft:model + path: minecraft:block/custom/palm_button_not_pressed + generation: + parent: minecraft:block/button + textures: + texture: minecraft:block/custom/palm_planks +blocks#button: + default:palm_button: + loot: + template: default:loot_table/self + settings: + template: + - default:sound/wood + - default:hardness/button + overrides: + burnable: true + push-reaction: destroy + map-color: 2 + instrument: harp + tags: + - minecraft:buttons + - minecraft:mineable/axe + - minecraft:wooden_buttons + behaviors: + - type: face_attached_horizontal_directional_block + - type: button_block + ticks-to-stay-pressed: 30 + can-button-be-activated-by-arrows: true + sounds: + on: minecraft:block.wooden_button.click_on + off: minecraft:block.wooden_button.click_off + states: + template: default:block_state/button + arguments: + base_block: birch_button + pressed_item: default:palm_button_pressed + not_pressed_item: default:palm_button_not_pressed + internal_id: + type: self_increase_int + from: 0 + to: 23 + recipes: default:palm_planks: template: default:recipe/planks @@ -787,4 +860,4 @@ recipes: B: minecraft:stick result: id: default:palm_fence - count: 1 + count: 1 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/categories.yml b/common-files/src/main/resources/resources/default/configuration/categories.yml index 2d911abe4..a1a9a71c8 100644 --- a/common-files/src/main/resources/resources/default/configuration/categories.yml +++ b/common-files/src/main/resources/resources/default/configuration/categories.yml @@ -17,18 +17,18 @@ categories: - default:palm_sapling - default:palm_leaves - default:palm_log - - default:stripped_palm_log - default:palm_wood + - default:stripped_palm_log - default:stripped_palm_wood - default:palm_planks - - default:palm_trapdoor - - default:palm_door - - default:palm_fence_gate - - default:palm_slab - default:palm_stairs + - default:palm_slab + - default:palm_fence + - default:palm_fence_gate + - default:palm_door + - default:palm_trapdoor - default:palm_pressure_plate - default:palm_button - - default:palm_fence default:topaz: name: <#FF8C00> hidden: true From ee6e0b83567f5fff27c225917e23a6f4a084a066 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Wed, 24 Sep 2025 19:04:19 +0800 Subject: [PATCH 193/226] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e5de2af92..f078e28e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.5 +project_version=0.0.63.6 config_version=46 lang_version=31 project_group=net.momirealms From f9448bfbffcbe555fa6f1df47d6dec550ddda0a3 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Wed, 24 Sep 2025 19:12:36 +0800 Subject: [PATCH 194/226] Update palm_tree.yml --- .../resources/default/configuration/blocks/palm_tree.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 82036eea1..7e5fcef3a 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -860,4 +860,4 @@ recipes: B: minecraft:stick result: id: default:palm_fence - count: 1 \ No newline at end of file + count: 3 \ No newline at end of file From e12273e60071014a39dcd1c98556e4fc8b9693a6 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Fri, 26 Sep 2025 21:58:21 +0800 Subject: [PATCH 195/226] =?UTF-8?q?=E9=87=8D=E6=96=B0=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BukkitCompatibilityManager.java | 6 +- .../plugin/BukkitCraftEnginePlugin.java | 2 +- .../plugin/PaperCraftEngineBootstrap.java | 2 +- .../advancement/BukkitAdvancementManager.java | 3 +- .../bukkit/api/CraftEngineBlocks.java | 10 + .../bukkit/block/BlockEventListener.java | 5 +- .../bukkit/block/BukkitBlockManager.java | 706 +-- .../bukkit/block/BukkitBlockStateWrapper.java | 11 + .../bukkit/block/BukkitCustomBlock.java | 124 - .../block/behavior/BushBlockBehavior.java | 2 +- .../block/behavior/ChimeBlockBehavior.java | 2 - .../DirectionalAttachedBlockBehavior.java | 2 +- .../block/behavior/FallingBlockBehavior.java | 27 +- .../item/behavior/BlockItemBehavior.java | 4 +- .../behavior/DoubleHighBlockItemBehavior.java | 4 +- .../LiquidCollisionBlockItemBehavior.java | 4 +- .../item/behavior/WallBlockItemBehavior.java | 4 +- .../bukkit/loot/BukkitVanillaLootManager.java | 7 +- .../bukkit/plugin/BukkitCraftEngine.java | 10 +- .../DebugAppearanceStateUsageCommand.java | 94 +- .../feature/DebugRealStateUsageCommand.java | 76 +- .../plugin/injector/BlockGenerator.java | 26 + .../plugin/network/BukkitNetworkManager.java | 35 +- .../protocol/ClientBlockStateSizePacket.java | 1 - .../reflection/minecraft/CoreReflections.java | 9 +- .../plugin/user/BukkitServerPlayer.java | 2 +- .../bukkit/util/BlockStateUtils.java | 55 +- .../util/NoteBlockChainUpdateUtils.java | 3 +- .../bukkit/world/BukkitExistingBlock.java | 2 +- .../main/resources/additional-real-blocks.yml | 91 - common-files/src/main/resources/config.yml | 2 +- common-files/src/main/resources/mappings.yml | 4452 ----------------- .../configuration/blocks/amethyst_torch.yml | 8 - .../configuration/blocks/chessboard_block.yml | 5 - .../configuration/blocks/chinese_lantern.yml | 2 - .../configuration/blocks/copper_coil.yml | 3 - .../blocks/ender_pearl_flower.yml | 4 - .../configuration/blocks/fairy_flower.yml | 2 - .../configuration/blocks/flame_cane.yml | 7 - .../configuration/blocks/gunpowder_block.yml | 4 - .../configuration/blocks/hami_melon.yml | 16 - .../configuration/blocks/netherite_anvil.yml | 10 +- .../configuration/blocks/palm_tree.yml | 68 - .../default/configuration/blocks/pebble.yml | 4 - .../default/configuration/blocks/reed.yml | 2 - .../configuration/blocks/safe_block.yml | 9 - .../default/configuration/blocks/sofa.yml | 18 +- .../configuration/blocks/table_lamp.yml | 10 - .../configuration/blocks/topaz_ore.yml | 5 - .../default/configuration/furniture/bench.yml | 1 - .../configuration/furniture/flower_basket.yml | 4 - .../configuration/furniture/wooden_chair.yml | 1 - .../default/configuration/items/cap.yml | 1 - .../configuration/items/flame_elytra.yml | 1 - .../default/configuration/items/gui_head.yml | 4 - .../configuration/items/topaz_armor.yml | 1 - .../configuration/items/topaz_tool_weapon.yml | 10 - .../default/configuration/templates.yml | 341 +- .../resources/internal/configuration/gui.yml | 13 - .../internal/configuration/mappings.yml | 4451 ++++++++++++++++ .../src/main/resources/translations/de.yml | 1 - .../src/main/resources/translations/en.yml | 4 +- .../src/main/resources/translations/es.yml | 1 - .../src/main/resources/translations/ru_ru.yml | 1 - .../src/main/resources/translations/tr.yml | 1 - .../src/main/resources/translations/zh_cn.yml | 2 +- .../core/block/AbstractBlockManager.java | 262 +- .../core/block/AbstractCustomBlock.java | 18 +- .../craftengine/core/block/BlockManager.java | 13 +- .../core/block/BlockRegistryMirror.java | 8 +- .../craftengine/core/block/BlockSounds.java | 21 +- .../core/block/BlockStateAppearance.java | 7 +- .../core/block/BlockStateVariant.java | 10 +- .../core/block/BlockStateWrapper.java | 4 + .../core/block/DelegatingBlock.java | 2 + .../craftengine/core/block/EmptyBlock.java | 4 - .../core/block/InactiveCustomBlock.java | 4 - .../craftengine/core/block/PushReaction.java | 4 +- .../core/block/properties/Property.java | 1 - .../furniture/AbstractFurnitureManager.java | 6 +- .../core/font/AbstractFontManager.java | 5 +- .../core/item/AbstractItemManager.java | 5 +- .../item/recipe/AbstractRecipeManager.java | 3 +- .../core/pack/AbstractPackManager.java | 84 +- .../core/pack/LoadingSequence.java | 1 + .../craftengine/core/plugin/CraftEngine.java | 7 +- .../core/plugin/config/Config.java | 42 +- .../core/plugin/config/ConfigParser.java | 17 - .../plugin/config/IdObjectConfigParser.java | 13 + .../plugin/config/IdSectionConfigParser.java | 14 + .../plugin/config/SectionConfigParser.java | 13 + .../config/template/TemplateManagerImpl.java | 8 +- .../plugin/context/GlobalVariableManager.java | 8 +- .../gui/category/ItemBrowserManagerImpl.java | 3 +- .../plugin/locale/TranslationManager.java | 2 - .../plugin/locale/TranslationManagerImpl.java | 18 +- .../core/plugin/logger/Debugger.java | 1 + .../core/plugin/network/NetWorkUser.java | 1 + .../core/sound/AbstractSoundManager.java | 5 +- .../craftengine/core/sound/SoundData.java | 1 + .../craftengine/core/util/Instrument.java | 2 + 101 files changed, 5278 insertions(+), 6137 deletions(-) delete mode 100644 common-files/src/main/resources/additional-real-blocks.yml delete mode 100644 common-files/src/main/resources/mappings.yml create mode 100644 common-files/src/main/resources/resources/internal/configuration/mappings.yml create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index c4ca0328e..f35396548 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -20,12 +20,14 @@ import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockR import net.momirealms.craftengine.bukkit.font.BukkitFontManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.core.block.BlockManager; import net.momirealms.craftengine.core.entity.furniture.ExternalModel; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.loot.LootConditions; import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager; import net.momirealms.craftengine.core.plugin.compatibility.LevelerProvider; import net.momirealms.craftengine.core.plugin.compatibility.ModelProvider; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition; import net.momirealms.craftengine.core.plugin.context.event.EventConditions; import net.momirealms.craftengine.core.util.Key; @@ -246,8 +248,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager { private void initWorldEditHook() { WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(BukkitBlockManager.instance(), false); try { - for (Key newBlockId : BukkitBlockManager.instance().blockRegisterOrder()) { - weBlockRegister.register(newBlockId); + for (int i = 0; i < Config.serverSideBlocks(); i++) { + weBlockRegister.register(BlockManager.createCustomBlockKey(i)); } } catch (Exception e) { this.plugin.logger().warn("Failed to initialize world edit hook", e); diff --git a/bukkit/loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEnginePlugin.java b/bukkit/loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEnginePlugin.java index 5f9871d85..cbd3aacbe 100644 --- a/bukkit/loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEnginePlugin.java +++ b/bukkit/loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEnginePlugin.java @@ -8,7 +8,7 @@ public class BukkitCraftEnginePlugin extends JavaPlugin { public BukkitCraftEnginePlugin() { this.plugin = new BukkitCraftEngine(this); this.plugin.applyDependencies(); - this.plugin.setUpConfig(); + this.plugin.setUpConfigAndLocale(); } @Override diff --git a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java index 05364cd61..45eb0a35d 100644 --- a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java +++ b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java @@ -55,7 +55,7 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap { ); } this.plugin.applyDependencies(); - this.plugin.setUpConfig(); + this.plugin.setUpConfigAndLocale(); if (isDatapackDiscoveryAvailable()) { new ModernEventHandler(context, this.plugin).register(); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java index c037f46c3..1cbff1a44 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; @@ -105,7 +106,7 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager { } } - public class AdvancementParser implements ConfigParser { + public class AdvancementParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"advancements", "advancement"}; @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java index 3fb08edd3..d769ab88f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java @@ -286,4 +286,14 @@ public final class CraftEngineBlocks { public static BlockData getBukkitBlockData(@NotNull ImmutableBlockState blockState) { return BlockStateUtils.fromBlockData(blockState.customBlockState().literalObject()); } + + /** + * Checks if the block state is a vanilla block state + * + * @param id state id + * @return is vanilla block or not + */ + public static boolean isVanillaBlockState(int id) { + return BukkitBlockManager.instance().isVanillaBlockState(id); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 1daab5e9d..7350e97b4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -44,13 +44,11 @@ import java.util.Optional; public final class BlockEventListener implements Listener { private final BukkitCraftEngine plugin; - private final boolean enableNoteBlockCheck; private final BukkitBlockManager manager; - public BlockEventListener(BukkitCraftEngine plugin, BukkitBlockManager manager, boolean enableNoteBlockCheck) { + public BlockEventListener(BukkitCraftEngine plugin, BukkitBlockManager manager) { this.plugin = plugin; this.manager = manager; - this.enableNoteBlockCheck = enableNoteBlockCheck; } @EventHandler(ignoreCancelled = true) @@ -276,7 +274,6 @@ public final class BlockEventListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onBlockPhysics(BlockPhysicsEvent event) { - if (!this.enableNoteBlockCheck) return; // for vanilla blocks if (event.getChangedType() == Material.NOTE_BLOCK) { Block block = event.getBlock(); 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 7dfe46fa0..5fc635efe 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 @@ -1,16 +1,10 @@ package net.momirealms.craftengine.bukkit.block; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator; -import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; @@ -24,100 +18,69 @@ import net.momirealms.craftengine.bukkit.util.TagUtils; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; import net.momirealms.craftengine.core.block.parser.BlockStateParser; -import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.sound.SoundData; -import net.momirealms.craftengine.core.sound.Sounds; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.block.data.BlockData; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.Yaml; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; public final class BukkitBlockManager extends AbstractBlockManager { + public static final Set CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(2048, 0.6f); + private static final Object ALWAYS_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false); + private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true); private static BukkitBlockManager instance; private final BukkitCraftEngine plugin; - - // The total amount of blocks registered - private int customBlockCount; - private ImmutableBlockState[] stateId2ImmutableBlockStates; - // Minecraft objects - // Cached new blocks $ holders - private Map stateId2BlockHolder; - // This map is used to change the block states that are not necessarily needed into a certain block state - private Map blockAppearanceMapper; - // Record the amount of real blocks by block type - private Map registeredRealBlockSlots; - // A set of blocks that sounds have been removed - private Set affectedSoundBlocks; - private Map> affectedOpenableBlockSounds; - private Map soundMapper; - // A list to record the order of registration - private List blockRegisterOrder = new ObjectArrayList<>(); - // Event listeners + // 事件监听器 private BlockEventListener blockEventListener; - // cached tag packet + // 可燃烧的方块 + private Map igniteOdds; + private Map burnOdds; + // 自定义客户端侧原版方块标签 + private Map> clientBoundTags = Map.of(); + private Map> previousClientBoundTags = Map.of(); + // 缓存的原版方块tag包 private Object cachedUpdateTagsPacket; - - private final List> blocksToDeceive = new ArrayList<>(); + // 被移除声音的原版方块 + private final Set replacedBlockSounds = new HashSet<>(); + // 用于缓存string形式的方块状态到原版方块状态 + private final Map blockStateCache = new HashMap<>(1024); public BukkitBlockManager(BukkitCraftEngine plugin) { - super(plugin); - instance = this; + super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks()); this.plugin = plugin; - this.initVanillaRegistry(); - this.loadMappingsAndAdditionalBlocks(); - this.registerBlocks(); + this.registerServerSideCustomBlocks(Config.serverSideBlocks()); this.registerEmptyBlock(); + instance = this; } @Override public void init() { this.initMirrorRegistry(); - this.deceiveBukkit(); - boolean enableNoteBlocks = this.blockAppearanceArranger.containsKey(BlockKeys.NOTE_BLOCK); - this.blockEventListener = new BlockEventListener(plugin, this, enableNoteBlocks); - if (enableNoteBlocks) { - this.recordVanillaNoteBlocks(); - } - this.stateId2ImmutableBlockStates = new ImmutableBlockState[this.customBlockCount]; - Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - this.resetPacketListeners(); - } - - @Override - public String stateRegistryIdToStateSNBT(int id) { - return BlockStateUtils.idToBlockState(id).toString(); + this.initFireBlock(); + this.initVanillaBlockSettings(); + this.deceiveBukkitRegistry(); + this.markVanillaNoteBlocks(); + this.blockEventListener = new BlockEventListener(plugin, this); + Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限 } public static BukkitBlockManager instance() { return instance; } - public List blockRegisterOrder() { - return Collections.unmodifiableList(this.blockRegisterOrder); - } - @Override public void delayedInit() { Bukkit.getPluginManager().registerEvents(this.blockEventListener, this.plugin.javaPlugin()); @@ -126,9 +89,12 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void unload() { super.unload(); + Arrays.fill(this.blockStateMappings, -1); + this.previousClientBoundTags = this.clientBoundTags; + this.clientBoundTags = new HashMap<>(); if (EmptyBlock.STATE != null) - Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.STATE); - for (DelegatingBlock block : this.registeredBlocks.values()) { + Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); + for (DelegatingBlock block : this.customBlocks) { block.behaviorDelegate().bindValue(EmptyBlockBehavior.INSTANCE); block.shapeDelegate().bindValue(BukkitBlockShape.STONE); DelegatingBlockState state = (DelegatingBlockState) FastNMS.INSTANCE.method$Block$defaultState(block); @@ -142,14 +108,9 @@ public final class BukkitBlockManager extends AbstractBlockManager { HandlerList.unregisterAll(this.blockEventListener); } - @Override - public Map soundMapper() { - return this.soundMapper; - } - @Override public void delayedLoad() { - this.resetPacketListeners(); + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表 super.delayedLoad(); } @@ -180,47 +141,39 @@ public final class BukkitBlockManager extends AbstractBlockManager { if (state != null) { return state.customBlockState(); } + return createVanillaBlockState(blockState); + } + + @Override + public BlockStateWrapper createVanillaBlockState(String blockState) { + Object state = parseBlockState(blockState); + if (state == null) return null; + return BlockStateUtils.toBlockStateWrapper(state); + } + + @Nullable + private Object parseBlockState(String state) { + if (this.blockStateCache.containsKey(state)) { + return this.blockStateCache.get(state); + } try { - BlockData blockData = Bukkit.createBlockData(blockState); - return BlockStateUtils.toBlockStateWrapper(blockData); - } catch (IllegalArgumentException e) { + Object registryOrLookUp = MBuiltInRegistries.BLOCK; + if (CoreReflections.method$Registry$asLookup != null) { + registryOrLookUp = CoreReflections.method$Registry$asLookup.invoke(registryOrLookUp); + } + Object result = CoreReflections.method$BlockStateParser$parseForBlock.invoke(null, registryOrLookUp, state, false); + Object resultState = CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result); + this.blockStateCache.put(state, resultState); + return resultState; + } catch (Exception e) { + Debugger.BLOCK.warn(() -> "Failed to create block state: " + state, e); return null; } } @Nullable public Object getMinecraftBlockHolder(int stateId) { - return this.stateId2BlockHolder.get(stateId); - } - - @NotNull - @Override - public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) { - return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()]; - } - - @Nullable - @Override - public ImmutableBlockState getImmutableBlockState(int stateId) { - if (!BlockStateUtils.isVanillaBlock(stateId)) { - return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()]; - } - return null; - } - - @Override - public void addBlockInternal(Key id, CustomBlock customBlock) { - // bind appearance and real state - for (ImmutableBlockState state : customBlock.variantProvider().states()) { - ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()]; - if (previous != null && !previous.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.block.state.bind_failed", state.toString(), previous.toString(), BlockStateUtils.getBlockOwnerIdFromState(previous.customBlockState().literalObject()).toString()); - } - this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state; - this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId()); - this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new IntArrayList()).add(state.customBlockState().registryId()); - } - super.addBlockInternal(id, customBlock); + return this.customBlockHolders[stateId - BlockStateUtils.vanillaBlockStateCount()]; } @Override @@ -233,19 +186,67 @@ public final class BukkitBlockManager extends AbstractBlockManager { return BlockStateUtils.getBlockOwnerIdFromState(BlockStateUtils.idToBlockState(id)); } - @Override - public int availableAppearances(Key blockType) { - return Optional.ofNullable(this.registeredRealBlockSlots.get(blockType)).orElse(0); + @SuppressWarnings("unchecked") + private void initFireBlock() { + try { + this.igniteOdds = (Map) CoreReflections.field$FireBlock$igniteOdds.get(MBlocks.FIRE); + this.burnOdds = (Map) CoreReflections.field$FireBlock$burnOdds.get(MBlocks.FIRE); + } catch (IllegalAccessException e) { + this.plugin.logger().warn("Failed to get ignite odds", e); + } } - @NotNull - public Map> blockAppearanceArranger() { - return this.blockAppearanceArranger; + private void initVanillaBlockSettings() { + try { + for (int i = 0; i < this.vanillaBlockStateCount; i++) { + Object blockState = BlockStateUtils.idToBlockState(i); + // 确保缓存已被激活 + CoreReflections.method$BlockStateBase$initCache.invoke(blockState); + BlockSettings settings = BlockSettings.of() + .pushReaction(PushReaction.VALUES[((Enum) CoreReflections.field$BlockStateBase$pushReaction.get(blockState)).ordinal()]) + .mapColor(MapColor.get(CoreReflections.field$MapColor$id.getInt(CoreReflections.field$BlockStateBase$mapColor.get(blockState)))) + .canOcclude(FastNMS.INSTANCE.method$BlockStateBase$canOcclude(blockState) ? Tristate.TRUE : Tristate.FALSE) + .isRandomlyTicking(CoreReflections.field$BlockStateBase$isRandomlyTicking.getBoolean(blockState)) + .hardness(CoreReflections.field$BlockStateBase$hardness.getFloat(blockState)) + .replaceable(CoreReflections.field$BlockStateBase$replaceable.getBoolean(blockState)) + .burnable(CoreReflections.field$BlockStateBase$burnable.getBoolean(blockState)) + .luminance(CoreReflections.field$BlockStateBase$lightEmission.getInt(blockState)) + .instrument(Instrument.VALUES[((Enum) CoreReflections.field$BlockStateBase$instrument.get(blockState)).ordinal()]) + .pushReaction(PushReaction.VALUES[((Enum) CoreReflections.field$BlockStateBase$pushReaction.get(blockState)).ordinal()]); + Object block = BlockStateUtils.getBlockOwner(blockState); + settings.resistance(CoreReflections.field$BlockBehaviour$explosionResistance.getFloat(block)) + .friction(CoreReflections.field$BlockBehaviour$friction.getFloat(block)) + .speedFactor(CoreReflections.field$BlockBehaviour$speedFactor.getFloat(block)) + .jumpFactor(CoreReflections.field$BlockBehaviour$jumpFactor.getFloat(block)) + .sounds(toBlockSounds(CoreReflections.field$BlockBehaviour$soundType.get(block))); + if (VersionHelper.isOrAbove1_21_2()) { + settings.blockLight(CoreReflections.field$BlockStateBase$lightBlock.getInt(blockState)); + settings.propagatesSkylightDown(CoreReflections.field$BlockStateBase$propagatesSkylightDown.getBoolean(blockState) ? Tristate.TRUE : Tristate.FALSE); + } else { + Object cache = CoreReflections.field$BlockStateBase$cache.get(blockState); + settings.blockLight(CoreReflections.field$BlockStateBase$Cache$lightBlock.getInt(cache)); + settings.propagatesSkylightDown(CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.getBoolean(cache) ? Tristate.TRUE : Tristate.FALSE); + } + this.vanillaBlockSettings[i] = settings; + } + } catch (Exception e) { + this.plugin.logger().warn("Failed to initialize vanilla block settings", e); + } } - @NotNull - public Map> realBlockArranger() { - return this.realBlockArranger; + private BlockSounds toBlockSounds(Object soundType) throws ReflectiveOperationException { + return new BlockSounds( + toSoundData(CoreReflections.field$SoundType$breakSound.get(soundType), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8), + toSoundData(CoreReflections.field$SoundType$stepSound.get(soundType), SoundData.SoundValue.FIXED_0_15, SoundData.SoundValue.FIXED_1), + toSoundData(CoreReflections.field$SoundType$placeSound.get(soundType), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8), + toSoundData(CoreReflections.field$SoundType$hitSound.get(soundType), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_5), + toSoundData(CoreReflections.field$SoundType$fallSound.get(soundType), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_75) + ); + } + + private SoundData toSoundData(Object soundEvent, SoundData.SoundValue volume, SoundData.SoundValue pitch) { + Key soundId = KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.field$SoundEvent$location(soundEvent)); + return new SoundData(soundId, volume, pitch); } private void initMirrorRegistry() { @@ -263,419 +264,126 @@ public final class BukkitBlockManager extends AbstractBlockManager { holder.bindValue(emptyBlock); } - private void resetPacketListeners() { - Map finalMapping = new HashMap<>(this.blockAppearanceMapper); - int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); - for (int custom : this.internalId2StateId.values()) { - finalMapping.put(custom, stoneId); - } - finalMapping.putAll(this.tempBlockAppearanceConvertor); - BukkitNetworkManager.instance().registerBlockStatePacketListeners(finalMapping, RegistryUtils.currentBlockRegistrySize()); - } - - private void initVanillaRegistry() { - int vanillaStateCount = RegistryUtils.currentBlockRegistrySize(); - this.plugin.logger().info("Vanilla block count: " + vanillaStateCount); - BlockStateUtils.init(vanillaStateCount); - } - @Override protected CustomBlock.Builder platformBuilder(Key id) { return BukkitCustomBlock.builder(id); } - @SuppressWarnings("unchecked") - private void registerBlocks() { - this.plugin.logger().info("Registering blocks. Please wait..."); - try { - ImmutableMap.Builder builder1 = ImmutableMap.builder(); - ImmutableMap.Builder builder2 = ImmutableMap.builder(); - ImmutableMap.Builder> builder3 = ImmutableMap.builder(); - ImmutableMap.Builder builder4 = ImmutableMap.builder(); - Set affectedBlockSounds = new HashSet<>(); - Map> affectedDoors = new IdentityHashMap<>(); - Set affectedBlocks = new HashSet<>(); - List order = new ArrayList<>(); - - unfreezeRegistry(); - - int counter = 0; - for (Map.Entry baseBlockAndItsCount : this.registeredRealBlockSlots.entrySet()) { - counter = registerBlockVariants(baseBlockAndItsCount, counter, builder1, builder2, builder3, builder4, affectedBlockSounds, order); - } - - freezeRegistry(); - this.plugin.logger().info("Registered block count: " + counter); - this.customBlockCount = counter; - this.internalId2StateId = builder1.build(); - this.stateId2BlockHolder = builder2.build(); - this.realBlockArranger = builder3.build(); - this.registeredBlocks = builder4.build(); - this.blockRegisterOrder = ImmutableList.copyOf(order); - if (MCUtils.ceilLog2(BlockStateUtils.vanillaStateSize() + counter) == MCUtils.ceilLog2(BlockStateUtils.vanillaStateSize())) { - PalettedContainer.NEED_DOWNGRADE = false; - } - for (Object block : (Iterable) MBuiltInRegistries.BLOCK) { - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(block); - if (affectedBlockSounds.contains(soundType)) { - Object state = FastNMS.INSTANCE.method$Block$defaultState(block); - if (BlockStateUtils.isVanillaBlock(state)) { - affectedBlocks.add(block); - } - } - } - - affectedBlocks.remove(MBlocks.FIRE); - affectedBlocks.remove(MBlocks.SOUL_FIRE); - - this.affectedSoundBlocks = ImmutableSet.copyOf(affectedBlocks); - - ImmutableMap.Builder soundMapperBuilder = ImmutableMap.builder(); - for (Object soundType : affectedBlockSounds) { - for (Field field : List.of(CoreReflections.field$SoundType$placeSound, CoreReflections.field$SoundType$fallSound, CoreReflections.field$SoundType$hitSound, CoreReflections.field$SoundType$stepSound, CoreReflections.field$SoundType$breakSound)) { - Object soundEvent = field.get(soundType); - Key previousId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(soundEvent).toString()); - soundMapperBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value())); - } - } - - Predicate predicate = it -> this.realBlockArranger.containsKey(it); - Consumer soundCallback = s -> soundMapperBuilder.put(s, Key.of("replaced." + s.value())); - BiConsumer> affectedBlockCallback = affectedDoors::put; - Function soundMapper = (k) -> SoundData.of(k, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f)); - collectDoorSounds(predicate, Sounds.WOODEN_TRAPDOOR_OPEN, Sounds.WOODEN_TRAPDOOR_CLOSE, Sounds.WOODEN_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.NETHER_WOOD_TRAPDOOR_OPEN, Sounds.NETHER_WOOD_TRAPDOOR_CLOSE, Sounds.NETHER_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.BAMBOO_WOOD_TRAPDOOR_OPEN, Sounds.BAMBOO_WOOD_TRAPDOOR_CLOSE, Sounds.BAMBOO_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.CHERRY_WOOD_TRAPDOOR_OPEN, Sounds.CHERRY_WOOD_TRAPDOOR_CLOSE, Sounds.CHERRY_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.COPPER_TRAPDOOR_OPEN, Sounds.COPPER_TRAPDOOR_CLOSE, Sounds.COPPER_TRAPDOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.WOODEN_DOOR_OPEN, Sounds.WOODEN_DOOR_CLOSE, Sounds.WOODEN_DOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.NETHER_WOOD_DOOR_OPEN, Sounds.NETHER_WOOD_DOOR_CLOSE, Sounds.NETHER_DOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.BAMBOO_WOOD_DOOR_OPEN, Sounds.BAMBOO_WOOD_DOOR_CLOSE, Sounds.BAMBOO_DOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.CHERRY_WOOD_DOOR_OPEN, Sounds.CHERRY_WOOD_DOOR_CLOSE, Sounds.CHERRY_DOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.COPPER_DOOR_OPEN, Sounds.COPPER_DOOR_CLOSE, Sounds.COPPER_DOORS, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.WOODEN_FENCE_GATE_OPEN, Sounds.WOODEN_FENCE_GATE_CLOSE, Sounds.WOODEN_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.NETHER_WOOD_FENCE_GATE_OPEN, Sounds.NETHER_WOOD_FENCE_GATE_CLOSE, Sounds.NETHER_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.BAMBOO_WOOD_FENCE_GATE_OPEN, Sounds.BAMBOO_WOOD_FENCE_GATE_CLOSE, Sounds.BAMBOO_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback); - collectDoorSounds(predicate, Sounds.CHERRY_WOOD_FENCE_GATE_OPEN, Sounds.CHERRY_WOOD_FENCE_GATE_CLOSE, Sounds.CHERRY_FENCE_GATES, soundMapper, soundCallback, affectedBlockCallback); - this.affectedOpenableBlockSounds = ImmutableMap.copyOf(affectedDoors); - this.soundMapper = soundMapperBuilder.buildKeepingLast(); - } catch (Throwable e) { - plugin.logger().warn("Failed to inject blocks.", e); + // 注册服务端侧的真实方块 + private void registerServerSideCustomBlocks(int count) { + // 这个会影响全局调色盘 + if (MCUtils.ceilLog2(this.vanillaBlockStateCount + count) == MCUtils.ceilLog2(this.vanillaBlockStateCount)) { + PalettedContainer.NEED_DOWNGRADE = false; } - } - - private void collectDoorSounds(Predicate isUsedForCustomBlock, - Key openSound, - Key closeSound, - List doors, - Function soundMapper, - Consumer soundCallback, - BiConsumer> affectedBlockCallback) { - for (Key d : doors) { - if (isUsedForCustomBlock.test(d)) { - soundCallback.accept(openSound); - soundCallback.accept(closeSound); - for (Key door : doors) { - Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(door)); - if (block != null) { - affectedBlockCallback.accept(block, Pair.of(soundMapper.apply(Key.of("replaced." + openSound.value())), soundMapper.apply(Key.of("replaced." + closeSound.value())))); - } + try { + unfreezeRegistry(); + for (int i = 0; i < count; i++) { + Key customBlockId = BlockManager.createCustomBlockKey(i); + DelegatingBlock customBlock; + try { + customBlock = BlockGenerator.generateBlock(customBlockId); + } catch (Throwable t) { + CraftEngine.instance().logger().warn("Failed to generate custom block " + customBlockId, t); + break; + } + this.customBlocks[i] = customBlock; + try { + Object resourceLocation = KeyUtils.toResourceLocation(customBlockId); + Object blockHolder = CoreReflections.method$Registry$registerForHolder.invoke(null, MBuiltInRegistries.BLOCK, resourceLocation, customBlock); + this.customBlockHolders[i] = blockHolder; + CoreReflections.method$Holder$Reference$bindValue.invoke(blockHolder, customBlock); + CoreReflections.field$Holder$Reference$tags.set(blockHolder, Set.of()); + DelegatingBlockState newBlockState = (DelegatingBlockState) FastNMS.INSTANCE.method$Block$defaultState(customBlock); + this.customBlockStates[i] = newBlockState; + CoreReflections.method$IdMapper$add.invoke(CoreReflections.instance$Block$BLOCK_STATE_REGISTRY, newBlockState); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to register custom block " + customBlockId, e); } - break; } + } finally { + freezeRegistry(); } } public Object cachedUpdateTagsPacket() { - return cachedUpdateTagsPacket; + return this.cachedUpdateTagsPacket; } - private void loadMappingsAndAdditionalBlocks() { - this.plugin.logger().info("Loading mappings.yml."); - Path mappingsFile = this.plugin.dataFolderPath().resolve("mappings.yml"); - if (!Files.exists(mappingsFile)) { - this.plugin.saveResource("mappings.yml"); - } - Path additionalFile = this.plugin.dataFolderPath().resolve("additional-real-blocks.yml"); - if (!Files.exists(additionalFile)) { - this.plugin.saveResource("additional-real-blocks.yml"); - } - Yaml yaml = new Yaml(new StringKeyConstructor(mappingsFile, new LoaderOptions())); - Map blockTypeCounter = new LinkedHashMap<>(); - try (InputStream is = Files.newInputStream(mappingsFile)) { - Map blockStateMappings = loadBlockStateMappings(yaml.load(is)); - this.validateBlockStateMappings(mappingsFile, blockStateMappings); - Map stateMap = new Int2ObjectOpenHashMap<>(); - Map appearanceMapper = new Int2IntOpenHashMap(); - Map> appearanceArranger = new HashMap<>(); - for (Map.Entry entry : blockStateMappings.entrySet()) { - this.processBlockStateMapping(mappingsFile, entry, stateMap, blockTypeCounter, appearanceMapper, appearanceArranger); - } - this.blockAppearanceMapper = ImmutableMap.copyOf(appearanceMapper); - this.blockAppearanceArranger = ImmutableMap.copyOf(appearanceArranger); - this.plugin.logger().info("Freed " + this.blockAppearanceMapper.size() + " block state appearances."); - } catch (IOException e) { - throw new RuntimeException("Failed to init mappings.yml", e); - } - try (InputStream is = Files.newInputStream(additionalFile)) { - this.registeredRealBlockSlots = this.buildRegisteredRealBlockSlots(blockTypeCounter, yaml.load(is)); - } catch (IOException e) { - throw new RuntimeException("Failed to init additional-real-blocks.yml", e); - } - } - - private void recordVanillaNoteBlocks() { + private void markVanillaNoteBlocks() { try { - Object resourceLocation = KeyUtils.toResourceLocation(BlockKeys.NOTE_BLOCK); - Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, resourceLocation); + Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(BlockKeys.NOTE_BLOCK)); Object stateDefinition = CoreReflections.field$Block$StateDefinition.get(block); @SuppressWarnings("unchecked") ImmutableList states = (ImmutableList) CoreReflections.field$StateDefinition$states.get(stateDefinition); - for (Object state : states) { - BlockStateUtils.CLIENT_SIDE_NOTE_BLOCKS.put(state, new Object()); - } + CLIENT_SIDE_NOTE_BLOCKS.addAll(states); } catch (ReflectiveOperationException e) { - plugin.logger().warn("Failed to init vanilla note block", e); - } - } - - @Nullable - public Key replaceSoundIfExist(Key id) { - return this.soundMapper.get(id); - } - - public boolean isBlockSoundRemoved(Object block) { - return this.affectedSoundBlocks.contains(block); - } - - public boolean isOpenableBlockSoundRemoved(Object block) { - return this.affectedOpenableBlockSounds.containsKey(block); - } - - public SoundData getRemovedOpenableBlockSound(Object block, boolean open) { - return open ? this.affectedOpenableBlockSounds.get(block).left() : this.affectedOpenableBlockSounds.get(block).right(); - } - - private Map loadBlockStateMappings(Map mappings) { - Map blockStateMappings = new LinkedHashMap<>(); - for (Map.Entry entry : mappings.entrySet()) { - if (entry.getValue() instanceof String afterValue) { - blockStateMappings.put(entry.getKey(), afterValue); - } - } - return blockStateMappings; - } - - private void validateBlockStateMappings(Path mappingFile, Map blockStateMappings) { - Map temp = new HashMap<>(blockStateMappings); - for (Map.Entry entry : temp.entrySet()) { - String state = entry.getValue(); - if (blockStateMappings.containsKey(state)) { - String after = blockStateMappings.remove(state); - plugin.logger().warn(mappingFile, "'" + state + ": " + after + "' is invalid because '" + state + "' has already been used as a base block."); - } - } - } - - private void processBlockStateMapping(Path mappingFile, - Map.Entry entry, - Map stateMap, - Map counter, - Map mapper, - Map> arranger) { - Object before = createBlockState(mappingFile, entry.getKey()); - Object after = createBlockState(mappingFile, entry.getValue()); - if (before == null || after == null) return; - - int beforeId = BlockStateUtils.blockStateToId(before); - int afterId = BlockStateUtils.blockStateToId(after); - - Integer previous = mapper.put(beforeId, afterId); - if (previous == null) { - Key key = blockOwnerFromString(entry.getKey()); - counter.compute(key, (k, count) -> count == null ? 1 : count + 1); - stateMap.put(beforeId, entry.getKey()); - stateMap.put(afterId, entry.getValue()); - arranger.computeIfAbsent(key, (k) -> new IntArrayList()).add(beforeId); - } else { - String previousState = stateMap.get(previous); - plugin.logger().warn(mappingFile, "Duplicate entry: '" + previousState + "' equals '" + entry.getKey() + "'"); - } - } - - private Key blockOwnerFromString(String stateString) { - int index = stateString.indexOf('['); - if (index == -1) { - return Key.of(stateString); - } else { - return Key.of(stateString.substring(0, index)); - } - } - - private Object createBlockState(Path mappingFile, String state) { - try { - Object registryOrLookUp = MBuiltInRegistries.BLOCK; - if (CoreReflections.method$Registry$asLookup != null) { - registryOrLookUp = CoreReflections.method$Registry$asLookup.invoke(registryOrLookUp); - } - Object result = CoreReflections.method$BlockStateParser$parseForBlock.invoke(null, registryOrLookUp, state, false); - return CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result); - } catch (Exception e) { - this.plugin.logger().warn(mappingFile, "'" + state + "' is not a valid block state."); - return null; - } - } - - private LinkedHashMap buildRegisteredRealBlockSlots(Map counter, Map additionalYaml) { - LinkedHashMap map = new LinkedHashMap<>(counter); - for (Map.Entry entry : additionalYaml.entrySet()) { - Key blockType = Key.of(entry.getKey()); - if (entry.getValue() instanceof Integer i) { - int previous = map.getOrDefault(blockType, 0); - if (previous == 0) { - map.put(blockType, i); - this.plugin.logger().info("Loaded " + blockType + " with " + i + " real block states"); - } else { - map.put(blockType, i + previous); - this.plugin.logger().info("Loaded " + blockType + " with " + previous + " appearances and " + (i + previous) + " real block states"); - } - } - } - return map; - } - - private void unfreezeRegistry() throws IllegalAccessException { - CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, false); - CoreReflections.field$MappedRegistry$unregisteredIntrusiveHolders.set(MBuiltInRegistries.BLOCK, new IdentityHashMap<>()); - } - - private void freezeRegistry() throws IllegalAccessException { - CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, true); - } - - private int registerBlockVariants(Map.Entry blockWithCount, - int counter, - ImmutableMap.Builder builder1, - ImmutableMap.Builder builder2, - ImmutableMap.Builder> builder3, - ImmutableMap.Builder builder4, - Set affectSoundTypes, - List order) throws Exception { - Key clientSideBlockType = blockWithCount.getKey(); - boolean isNoteBlock = clientSideBlockType.equals(BlockKeys.NOTE_BLOCK); - Object clientSideBlock = getBlockFromRegistry(createResourceLocation(clientSideBlockType)); - int amount = blockWithCount.getValue(); - - List stateIds = new IntArrayList(); - - for (int i = 0; i < amount; i++) { - Key realBlockKey = createRealBlockKey(clientSideBlockType, i); - Object blockProperties = createBlockProperties(realBlockKey); - - Object newRealBlock; - Object newBlockState; - Object blockHolder; - Object resourceLocation = createResourceLocation(realBlockKey); - - try { - newRealBlock = BlockGenerator.generateBlock(clientSideBlockType, clientSideBlock, blockProperties); - } catch (Throwable throwable) { - this.plugin.logger().warn("Failed to generate dynamic block class", throwable); - continue; - } - - blockHolder = CoreReflections.method$Registry$registerForHolder.invoke(null, MBuiltInRegistries.BLOCK, resourceLocation, newRealBlock); - CoreReflections.method$Holder$Reference$bindValue.invoke(blockHolder, newRealBlock); - CoreReflections.field$Holder$Reference$tags.set(blockHolder, Set.of()); - - newBlockState = FastNMS.INSTANCE.method$Block$defaultState(newRealBlock); - CoreReflections.method$IdMapper$add.invoke(CoreReflections.instance$Block$BLOCK_STATE_REGISTRY, newBlockState); - - if (isNoteBlock) { - BlockStateUtils.CLIENT_SIDE_NOTE_BLOCKS.put(newBlockState, new Object()); - } - - int stateId = BlockStateUtils.vanillaStateSize() + counter; - - builder1.put(realBlockKey, stateId); - builder2.put(stateId, blockHolder); - builder4.put(realBlockKey, (DelegatingBlock) newRealBlock); - stateIds.add(stateId); - - this.blocksToDeceive.add(Tuple.of(newRealBlock, clientSideBlockType, isNoteBlock)); - order.add(realBlockKey); - counter++; - } - - builder3.put(clientSideBlockType, stateIds); - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(clientSideBlock); - affectSoundTypes.add(soundType); - return counter; - } - - private Object createResourceLocation(Key key) { - return FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath(key.namespace(), key.value()); - } - - private Object getBlockFromRegistry(Object resourceLocation) { - return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, resourceLocation); - } - - private Key createRealBlockKey(Key replacedBlock, int index) { - return Key.of(Key.DEFAULT_NAMESPACE, replacedBlock.value() + "_" + index); - } - - private Object createBlockProperties(Key realBlockKey) throws Exception { - Object blockProperties = CoreReflections.method$BlockBehaviour$Properties$of.invoke(null); - Object realBlockResourceLocation = createResourceLocation(realBlockKey); - Object realBlockResourceKey = CoreReflections.method$ResourceKey$create.invoke(null, MRegistries.BLOCK, realBlockResourceLocation); - if (CoreReflections.field$BlockBehaviour$Properties$id != null) { - CoreReflections.field$BlockBehaviour$Properties$id.set(blockProperties, realBlockResourceKey); - } - return blockProperties; - } - - @SuppressWarnings("unchecked") - private void deceiveBukkit() { - try { - Map magicMap = (Map) CraftBukkitReflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null); - Map factories = (Map) CraftBukkitReflections.field$CraftBlockStates$FACTORIES.get(null); - for (Tuple tuple : this.blocksToDeceive) { - deceiveBukkit(tuple.left(), tuple.mid(), tuple.right(), magicMap, factories); - } - this.blocksToDeceive.clear(); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to deceive bukkit", e); - } - } - - private void deceiveBukkit(Object newBlock, Key replacedBlock, boolean isNoteBlock, Map magicMap, Map factories) { - if (isNoteBlock) { - magicMap.put(newBlock, Material.STONE); - } else { - Material material = org.bukkit.Registry.MATERIAL.get(new NamespacedKey(replacedBlock.namespace(), replacedBlock.value())); - if (CraftBukkitReflections.clazz$CraftBlockStates$BlockEntityStateFactory.isInstance(factories.get(material))) { - magicMap.put(newBlock, Material.STONE); - } else { - magicMap.put(newBlock, material); - } + this.plugin.logger().warn("Failed to init vanilla note block", e); } } @Override - protected int getBlockRegistryId(Key id) { + protected void setVanillaBlockTags(Key id, List tags) { Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)); - return FastNMS.INSTANCE.method$IdMap$getId(MBuiltInRegistries.BLOCK, block).orElseThrow(() -> new IllegalStateException("Block " + id + " not found")); + this.clientBoundTags.put(FastNMS.INSTANCE.method$IdMap$getId(MBuiltInRegistries.BLOCK, block).orElseThrow(() -> new IllegalStateException("Block " + id + " not found")), tags); + } + + public boolean isBlockSoundRemoved(Object block) { + return this.replacedBlockSounds.contains(block); + } + + private void unfreezeRegistry() { + try { + CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, false); + CoreReflections.field$MappedRegistry$unregisteredIntrusiveHolders.set(MBuiltInRegistries.BLOCK, new IdentityHashMap<>()); + } catch (IllegalAccessException e) { + this.plugin.logger().warn("Failed to unfreeze block registry", e); + } + } + + private void freezeRegistry() { + try { + CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, true); + } catch (IllegalAccessException e) { + this.plugin.logger().warn("Failed to freeze block registry", e); + } + } + + @SuppressWarnings("unchecked") + private void deceiveBukkitRegistry() { + try { + Map magicMap = (Map) CraftBukkitReflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null); + for (DelegatingBlock customBlock : this.customBlocks) { + magicMap.put(customBlock, Material.STONE); + } + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Failed to deceive bukkit magic blocks", e); + } } @Override protected boolean isVanillaBlock(Key id) { - if (!id.namespace().equals("minecraft")) { + if (!id.namespace().equals("minecraft")) return false; - } - if (id.value().equals("air")) { + if (id.value().equals("air")) return true; - } return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)) != MBlocks.AIR; } + + public boolean isBurnable(Object blockState) { + Object blockOwner = BlockStateUtils.getBlockOwner(blockState); + return this.igniteOdds.getOrDefault(blockOwner, 0) > 0; + } + + @Override + public int vanillaBlockStateCount() { + return this.vanillaBlockStateCount; + } + + public boolean isOpenableBlockSoundRemoved(Object blockOwner) { + return false; + } + + public SoundData getRemovedOpenableBlockSound(Object blockOwner, boolean b) { + return null; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java index cafb79317..8d0190d76 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.block; import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.util.Key; public class BukkitBlockStateWrapper implements BlockStateWrapper { private final Object blockState; @@ -20,4 +21,14 @@ public class BukkitBlockStateWrapper implements BlockStateWrapper { public int registryId() { return this.registryId; } + + @Override + public String toString() { + return this.blockState.toString(); + } + + @Override + public Key ownerId() { + return null; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java index ae91184a2..b831a0cde 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java @@ -32,8 +32,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; public final class BukkitCustomBlock extends AbstractCustomBlock { - private static final Object ALWAYS_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false); - private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true); private BukkitCustomBlock( @NotNull Key id, @@ -71,128 +69,6 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { return (LootTable) super.lootTable(); } - @Override - protected void applyPlatformSettings() { - try { - for (ImmutableBlockState immutableBlockState : variantProvider().states()) { - if (immutableBlockState.vanillaBlockState() == null) { - CraftEngine.instance().logger().warn("Could not find vanilla visual block state for " + immutableBlockState + ". This might cause errors!"); - continue; - } else if (immutableBlockState.customBlockState() == null) { - CraftEngine.instance().logger().warn("Could not find real block state for " + immutableBlockState + ". This might cause errors!"); - continue; - } - DelegatingBlockState nmsState = (DelegatingBlockState) immutableBlockState.customBlockState().literalObject(); - nmsState.setBlockState(immutableBlockState); - BlockSettings settings = immutableBlockState.settings(); - - // set block properties - CoreReflections.field$BlockStateBase$lightEmission.set(nmsState, settings.luminance()); - CoreReflections.field$BlockStateBase$burnable.set(nmsState, settings.burnable()); - CoreReflections.field$BlockStateBase$hardness.set(nmsState, settings.hardness()); - CoreReflections.field$BlockStateBase$replaceable.set(nmsState, settings.replaceable()); - Object mcMapColor = CoreReflections.method$MapColor$byId.invoke(null, settings.mapColor().id); - CoreReflections.field$BlockStateBase$mapColor.set(nmsState, mcMapColor); - Object mcInstrument = ((Object[]) CoreReflections.method$NoteBlockInstrument$values.invoke(null))[settings.instrument().ordinal()]; - CoreReflections.field$BlockStateBase$instrument.set(nmsState, mcInstrument); - Object pushReaction = ((Object[]) CoreReflections.method$PushReaction$values.invoke(null))[settings.pushReaction().ordinal()]; - CoreReflections.field$BlockStateBase$pushReaction.set(nmsState, pushReaction); - - boolean canOcclude = settings.canOcclude() == Tristate.UNDEFINED ? BlockStateUtils.isOcclude(immutableBlockState.vanillaBlockState().literalObject()) : settings.canOcclude().asBoolean(); - CoreReflections.field$BlockStateBase$canOcclude.set(nmsState, canOcclude); - - boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(immutableBlockState.vanillaBlockState().literalObject()) : settings.useShapeForLightOcclusion().asBoolean(); - CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion); - - CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); - CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); - CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, settings.isViewBlocking() == Tristate.UNDEFINED ? settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE : (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE)); - - // set parent block properties - DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState); - ObjectHolder shapeHolder = nmsBlock.shapeDelegate(); - shapeHolder.bindValue(new BukkitBlockShape(immutableBlockState.vanillaBlockState().literalObject(), Optional.ofNullable(immutableBlockState.settings().supportShapeBlockState()).map(it -> { - try { - Object blockState = BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(it)); - if (!BlockStateUtils.isVanillaBlock(blockState)) { - throw new IllegalArgumentException("BlockState is not a Vanilla block"); - } - return blockState; - } catch (IllegalArgumentException e) { - CraftEngine.instance().logger().warn("Illegal shape block state: " + it, e); - return null; - } - }).orElse(null))); - // bind behavior - ObjectHolder behaviorHolder = nmsBlock.behaviorDelegate(); - behaviorHolder.bindValue(super.behavior); - // set block side properties - CoreReflections.field$BlockBehaviour$explosionResistance.set(nmsBlock, settings.resistance()); - CoreReflections.field$BlockBehaviour$friction.set(nmsBlock, settings.friction()); - CoreReflections.field$BlockBehaviour$speedFactor.set(nmsBlock, settings.speedFactor()); - CoreReflections.field$BlockBehaviour$jumpFactor.set(nmsBlock, settings.jumpFactor()); - CoreReflections.field$BlockBehaviour$soundType.set(nmsBlock, SoundUtils.toSoundType(settings.sounds())); - // init cache - CoreReflections.method$BlockStateBase$initCache.invoke(nmsState); - boolean isConditionallyFullOpaque = canOcclude & useShapeForLightOcclusion; - if (!VersionHelper.isOrAbove1_21_2()) { - CoreReflections.field$BlockStateBase$isConditionallyFullOpaque.set(nmsState, isConditionallyFullOpaque); - } - // modify cache - if (VersionHelper.isOrAbove1_21_2()) { - int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$lightBlock.getInt(immutableBlockState.vanillaBlockState().literalObject()); - // set block light - CoreReflections.field$BlockStateBase$lightBlock.set(nmsState, blockLight); - // set propagates skylight - if (settings.propagatesSkylightDown() == Tristate.TRUE) { - CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, true); - } else if (settings.propagatesSkylightDown() == Tristate.FALSE) { - CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, false); - } else { - CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, CoreReflections.field$BlockStateBase$propagatesSkylightDown.getBoolean(immutableBlockState.vanillaBlockState().literalObject())); - } - } else { - Object cache = CoreReflections.field$BlockStateBase$cache.get(nmsState); - int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$Cache$lightBlock.getInt(CoreReflections.field$BlockStateBase$cache.get(immutableBlockState.vanillaBlockState().literalObject())); - // set block light - CoreReflections.field$BlockStateBase$Cache$lightBlock.set(cache, blockLight); - // set propagates skylight - if (settings.propagatesSkylightDown() == Tristate.TRUE) { - CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, true); - } else if (settings.propagatesSkylightDown() == Tristate.FALSE) { - CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, false); - } else { - CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.getBoolean(CoreReflections.field$BlockStateBase$cache.get(immutableBlockState.vanillaBlockState().literalObject()))); - } - if (!isConditionallyFullOpaque) { - CoreReflections.field$BlockStateBase$opacityIfCached.set(nmsState, blockLight); - } - } - // set fluid later - if (settings.fluidState()) { - CoreReflections.field$BlockStateBase$fluidState.set(nmsState, CoreReflections.method$FlowingFluid$getSource.invoke(MFluids.WATER, false)); - } else { - CoreReflections.field$BlockStateBase$fluidState.set(nmsState, MFluids.EMPTY$defaultState); - } - // set random tick later - CoreReflections.field$BlockStateBase$isRandomlyTicking.set(nmsState, settings.isRandomlyTicking()); - // bind tags - Object holder = BukkitCraftEngine.instance().blockManager().getMinecraftBlockHolder(immutableBlockState.customBlockState().registryId()); - Set tags = new HashSet<>(); - for (Key tag : settings.tags()) { - tags.add(CoreReflections.method$TagKey$create.invoke(null, MRegistries.BLOCK, KeyUtils.toResourceLocation(tag))); - } - CoreReflections.field$Holder$Reference$tags.set(holder, tags); - // set burning properties - if (settings.burnable()) { - CoreReflections.method$FireBlock$setFlammable.invoke(MBlocks.FIRE, nmsBlock, settings.burnChance(), settings.fireSpreadChance()); - } - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to init block settings", e); - } - } - public static Builder builder(Key id) { return new BuilderImpl(id); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java index bace95fe2..8a807c7e0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java @@ -65,7 +65,7 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { if (material != null) { if (index == -1) { // vanilla - mcBlocks.addAll(BlockStateUtils.getAllVanillaBlockStates(blockType)); + mcBlocks.addAll(BlockStateUtils.getPossibleBlockStates(blockType)); } else { mcBlocks.add(BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(blockStateStr))); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index 56e68b810..4b286d52e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -10,10 +10,8 @@ import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; -import java.util.stream.Stream; public class ChimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index f2e4d9987..958a1e37a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -163,7 +163,7 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { if (material != null) { if (index == -1) { // vanilla - mcBlocks.addAll(BlockStateUtils.getAllVanillaBlockStates(blockType)); + mcBlocks.addAll(BlockStateUtils.getPossibleBlockStates(blockType)); } else { mcBlocks.add(BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(blockStateStr))); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index 48a99aa1f..f71bbf15b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldPosition; @@ -23,11 +24,15 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final float hurtAmount; private final int maxHurt; + private final SoundData landSound; + private final SoundData destroySound; - public FallingBlockBehavior(CustomBlock block, float hurtAmount, int maxHurt) { + public FallingBlockBehavior(CustomBlock block, float hurtAmount, int maxHurt, SoundData landSound, SoundData destroySound) { super(block); this.hurtAmount = hurtAmount; this.maxHurt = maxHurt; + this.landSound = landSound; + this.destroySound = destroySound; } @Override @@ -80,10 +85,11 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { Object blockState = CoreReflections.field$FallingBlockEntity$blockState.get(fallingBlockEntity); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isEmpty()) return; - ImmutableBlockState customState = optionalCustomState.get(); net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); WorldPosition position = new WorldPosition(world, CoreReflections.field$Entity$xo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$yo.getDouble(fallingBlockEntity), CoreReflections.field$Entity$zo.getDouble(fallingBlockEntity)); - world.playBlockSound(position, customState.settings().sounds().destroySound()); + if (this.destroySound != null) { + world.playBlockSound(position, this.destroySound); + } } } @@ -99,16 +105,27 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { if (immutableBlockState == null || immutableBlockState.isEmpty()) return; if (!BaseEntityData.Silent.get(entityData)) { net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), immutableBlockState.settings().sounds().landSound()); + if (this.landSound != null) { + world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), this.landSound); + } } } public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { float hurtAmount = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("hurt-amount", -1f), "hurt-amount"); int hurtMax = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-hurt", -1), "max-hurt"); - return new FallingBlockBehavior(block, hurtAmount, hurtMax); + Map sounds = (Map) arguments.get("sounds"); + SoundData fallSound = null; + SoundData destroySound = null; + if (sounds != null) { + fallSound = Optional.ofNullable(sounds.get("fall")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + destroySound = Optional.ofNullable(sounds.get("destroy")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + } + return new FallingBlockBehavior(block, hurtAmount, hurtMax, fallSound, destroySound); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index faf8bbc09..45d9c1ef7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -242,9 +242,9 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); } return new BlockItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java index 0acf2bfb3..122bb18b4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java @@ -48,9 +48,9 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); } return new DoubleHighBlockItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java index 36a62b18d..9e058c549 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java @@ -79,9 +79,9 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); } return new LiquidCollisionBlockItemBehavior(key, offset); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java index 53767eeaf..bbc48120b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java @@ -43,9 +43,9 @@ public class WallBlockItemBehavior extends BlockItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); } return new WallBlockItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java index 68205240c..31ae7e70d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.loot.VanillaLoot; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -90,8 +91,8 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme return this.vanillaLootParser; } - public class VanillaLootParser implements ConfigParser { - public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot", "vanilla_loots", "vanilla_loot"}; + public class VanillaLootParser implements IdSectionConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"}; @Override public int loadingSequence() { @@ -126,7 +127,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme VanillaLoot vanillaLoot = blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK)); vanillaLoot.addLootTable(lootTable); } else { - for (Object blockState : BlockStateUtils.getAllVanillaBlockStates(Key.of(target))) { + for (Object blockState : BlockStateUtils.getPossibleBlockStates(Key.of(target))) { if (blockState == MBlocks.AIR$defaultState) { throw new LocalizedResourceConfigException("warning.config.vanilla_loot.block.invalid_target", target); } 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 e00b9b64b..9acb350a2 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 @@ -102,9 +102,15 @@ public class BukkitCraftEngine extends CraftEngine { this.javaPlugin = javaPlugin; } - protected void setUpConfig() { - this.translationManager = new TranslationManagerImpl(this); + protected void setUpConfigAndLocale() { this.config = new Config(this); + this.config.updateConfigCache(); + // 先读取语言后,再重载语言文件系统 + this.config.loadForcedLocale(); + this.translationManager = new TranslationManagerImpl(this); + this.translationManager.reload(); + // 最后才加载完整的config配置 + this.config.loadFullSettings(); } public void injectRegistries() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java index f47eb842e..ce6029653 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java @@ -31,54 +31,54 @@ public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder - .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().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); - } - })) +// .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().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); +// } +// })) .handler(context -> { - String data = context.get("id"); - BukkitBlockManager blockManager = plugin().blockManager(); - Key baseBlockId = Key.of(data); - List appearances = blockManager.blockAppearanceArranger().get(baseBlockId); - if (appearances == null) return; - int i = 0; - Component block = Component.text(baseBlockId + ": "); - plugin().senderFactory().wrap(context.sender()).sendMessage(block); - - List batch = new ArrayList<>(); - for (int appearance : appearances) { - Component text = Component.text("|"); - List reals = blockManager.appearanceToRealStates(appearance); - if (reals == null || reals.isEmpty()) { - Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN); - hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN)); - text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)); - } else { - Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.RED); - List hoverChildren = new ArrayList<>(); - hoverChildren.add(Component.newline()); - hoverChildren.add(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.RED)); - for (int real : reals) { - hoverChildren.add(Component.newline()); - hoverChildren.add(Component.text(blockManager.getImmutableBlockStateUnsafe(real).toString()).color(NamedTextColor.GRAY)); - } - text = text.color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover.children(hoverChildren))); - } - batch.add(text); - i++; - if (batch.size() == 100) { - plugin().senderFactory().wrap(context.sender()) - .sendMessage(Component.text("").children(batch)); - batch.clear(); - } - } - if (!batch.isEmpty()) { - plugin().senderFactory().wrap(context.sender()) - .sendMessage(Component.text("").children(batch)); - batch.clear(); - } +// String data = context.get("id"); +// BukkitBlockManager blockManager = plugin().blockManager(); +// Key baseBlockId = Key.of(data); +// List appearances = blockManager.blockAppearanceArranger().get(baseBlockId); +// if (appearances == null) return; +// int i = 0; +// Component block = Component.text(baseBlockId + ": "); +// plugin().senderFactory().wrap(context.sender()).sendMessage(block); +// +// List batch = new ArrayList<>(); +// for (int appearance : appearances) { +// Component text = Component.text("|"); +// List reals = blockManager.appearanceToRealStates(appearance); +// if (reals == null || reals.isEmpty()) { +// Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN); +// hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN)); +// text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)); +// } else { +// Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.RED); +// List hoverChildren = new ArrayList<>(); +// hoverChildren.add(Component.newline()); +// hoverChildren.add(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.RED)); +// for (int real : reals) { +// hoverChildren.add(Component.newline()); +// hoverChildren.add(Component.text(blockManager.getImmutableBlockStateUnsafe(real).toString()).color(NamedTextColor.GRAY)); +// } +// text = text.color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover.children(hoverChildren))); +// } +// batch.add(text); +// i++; +// if (batch.size() == 100) { +// plugin().senderFactory().wrap(context.sender()) +// .sendMessage(Component.text("").children(batch)); +// batch.clear(); +// } +// } +// if (!batch.isEmpty()) { +// plugin().senderFactory().wrap(context.sender()) +// .sendMessage(Component.text("").children(batch)); +// batch.clear(); +// } }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java index 01531251d..04b7d0ce2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java @@ -31,45 +31,45 @@ public class DebugRealStateUsageCommand extends BukkitCommandFeature assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder - .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().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); - } - })) +// .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().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); +// } +// })) .handler(context -> { - String data = context.get("id"); - BukkitBlockManager blockManager = plugin().blockManager(); - Key baseBlockId = Key.of(data); - List reals = blockManager.realBlockArranger().get(baseBlockId); - if (reals == null) return; - int i = 0; - Component block = Component.text(baseBlockId + ": "); - plugin().senderFactory().wrap(context.sender()).sendMessage(block); - - List batch = new ArrayList<>(100); - for (int real : reals) { - ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(real); - if (state.isEmpty()) { - Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.GREEN); - batch.add(Component.text("|").color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover))); - } else { - Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.RED); - hover = hover.append(Component.newline()).append(Component.text(state.toString()).color(NamedTextColor.GRAY)); - batch.add(Component.text("|").color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover))); - } - i++; - if (batch.size() == 100) { - plugin().senderFactory().wrap(context.sender()) - .sendMessage(Component.text("").children(batch)); - batch.clear(); - } - } - if (!batch.isEmpty()) { - plugin().senderFactory().wrap(context.sender()) - .sendMessage(Component.text("").children(batch)); - batch.clear(); - } +// String data = context.get("id"); +// BukkitBlockManager blockManager = plugin().blockManager(); +// Key baseBlockId = Key.of(data); +// List reals = blockManager.realBlockArranger().get(baseBlockId); +// if (reals == null) return; +// int i = 0; +// Component block = Component.text(baseBlockId + ": "); +// plugin().senderFactory().wrap(context.sender()).sendMessage(block); +// +// List batch = new ArrayList<>(100); +// for (int real : reals) { +// ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(real); +// if (state.isEmpty()) { +// Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.GREEN); +// batch.add(Component.text("|").color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover))); +// } else { +// Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.RED); +// hover = hover.append(Component.newline()).append(Component.text(state.toString()).color(NamedTextColor.GRAY)); +// batch.add(Component.text("|").color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover))); +// } +// i++; +// if (batch.size() == 100) { +// plugin().senderFactory().wrap(context.sender()) +// .sendMessage(Component.text("").children(batch)); +// batch.clear(); +// } +// } +// if (!batch.isEmpty()) { +// plugin().senderFactory().wrap(context.sender()) +// .sendMessage(Component.text("").children(batch)); +// batch.clear(); +// } }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index 2b3792085..70f1823bb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java @@ -17,6 +17,8 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockShape; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.BlockKeys; @@ -199,6 +201,30 @@ public final class BlockGenerator { field$CraftEngineBlock$isTripwire = clazz$CraftEngineBlock.getField("isClientSideTripwire"); } + public static DelegatingBlock generateBlock(Key blockId) throws Throwable { + ObjectHolder behaviorHolder = new ObjectHolder<>(EmptyBlockBehavior.INSTANCE); + ObjectHolder shapeHolder = new ObjectHolder<>(STONE_SHAPE); + Object newBlockInstance = constructor$CraftEngineBlock.invoke(createEmptyBlockProperties(blockId)); + field$CraftEngineBlock$behavior.set(newBlockInstance, behaviorHolder); + field$CraftEngineBlock$shape.set(newBlockInstance, shapeHolder); + Object stateDefinitionBuilder = CoreReflections.constructor$StateDefinition$Builder.newInstance(newBlockInstance); + Object stateDefinition = CoreReflections.method$StateDefinition$Builder$create.invoke(stateDefinitionBuilder, + (Function) FastNMS.INSTANCE::method$Block$defaultState, BlockStateGenerator.instance$StateDefinition$Factory); + CoreReflections.field$Block$StateDefinition.set(newBlockInstance, stateDefinition); + CoreReflections.field$Block$defaultBlockState.set(newBlockInstance, ((ImmutableList) CoreReflections.field$StateDefinition$states.get(stateDefinition)).getFirst()); + return (DelegatingBlock) newBlockInstance; + } + + private static Object createEmptyBlockProperties(Key id) throws ReflectiveOperationException { + Object blockProperties = CoreReflections.method$BlockBehaviour$Properties$of.invoke(null); + Object resourceLocation = KeyUtils.toResourceLocation(id); + Object resourceKey = FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.BLOCK, resourceLocation); + if (CoreReflections.field$BlockBehaviour$Properties$id != null) { + CoreReflections.field$BlockBehaviour$Properties$id.set(blockProperties, resourceKey); + } + return blockProperties; + } + public static Object generateBlock(Key replacedBlock, Object ownerBlock, Object properties) throws Throwable { Object ownerProperties = CoreReflections.field$BlockBehaviour$properties.get(ownerBlock); CoreReflections.field$BlockBehaviour$Properties$hasCollision.set(properties, CoreReflections.field$BlockBehaviour$Properties$hasCollision.get(ownerProperties)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 7ced7ec1b..48c15f39c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -261,26 +261,33 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - public void registerBlockStatePacketListeners(Map map, int registrySize) { - int[] newMappings = new int[registrySize]; - for (int i = 0; i < registrySize; i++) { - newMappings[i] = i; - } - int[] newMappingsMOD = Arrays.copyOf(newMappings, registrySize); - for (Map.Entry entry : map.entrySet()) { - newMappings[entry.getKey()] = entry.getValue(); - if (BlockStateUtils.isVanillaBlock((int) entry.getKey())) { - newMappingsMOD[entry.getKey()] = entry.getValue(); + public void registerBlockStatePacketListeners(int[] blockStateMappings) { + int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); + int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount(); + int[] newMappings = new int[blockStateMappings.length]; + int[] newMappingsMOD = new int[blockStateMappings.length]; + for (int i = 0; i < vanillaBlocks; i++) { + int mappedId = blockStateMappings[i]; + if (mappedId != -1) { + newMappings[i] = mappedId; + newMappingsMOD[i] = mappedId; + } else { + newMappings[i] = i; + newMappingsMOD[i] = i; } } - for (int i = 0; i < newMappingsMOD.length; i++) { - if (BlockStateUtils.isVanillaBlock(i)) { - newMappingsMOD[i] = newMappings[i]; + for (int i = vanillaBlocks; i < blockStateMappings.length; i++) { + int mappedId = blockStateMappings[i]; + if (mappedId != -1) { + newMappings[i] = mappedId; + } else { + newMappings[i] = stoneId; } + newMappingsMOD[i] = i; } this.blockStateRemapper = newMappings; this.modBlockStateRemapper = newMappingsMOD; - registerS2CGamePacketListener(new LevelChunkWithLightListener(newMappings, newMappingsMOD, registrySize, RegistryUtils.currentBiomeRegistrySize()), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); + registerS2CGamePacketListener(new LevelChunkWithLightListener(newMappings, newMappingsMOD, newMappings.length, RegistryUtils.currentBiomeRegistrySize()), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); registerS2CGamePacketListener( diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java index e2840c816..41adce8c7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/ClientBlockStateSizePacket.java @@ -36,5 +36,4 @@ public record ClientBlockStateSizePacket(int blockStateSize) implements ModPacke public void handle(NetWorkUser user) { user.setClientBlockList(new IntIdentityList(this.blockStateSize)); } - } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index bbc030da9..0bbb93176 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -1220,6 +1220,10 @@ public final class CoreReflections { ReflectionUtils.getStaticMethod(clazz$MapColor, clazz$MapColor, int.class) ); + public static final Field field$MapColor$id = requireNonNull( + ReflectionUtils.getDeclaredField(clazz$MapColor, int.class, 1) + ); + public static final Class clazz$PushReaction = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( "world.level.material.EnumPistonReaction", @@ -1292,7 +1296,6 @@ public final class CoreReflections { ReflectionUtils.getDeclaredField(clazz$BlockStateBase, clazz$MapColor, 0) ); - public static final Field field$BlockStateBase$instrument = requireNonNull( ReflectionUtils.getDeclaredField(clazz$BlockStateBase, clazz$NoteBlockInstrument, 0) ); @@ -3661,6 +3664,10 @@ public final class CoreReflections { ReflectionUtils.getDeclaredField(clazz$FireBlock, Object2IntMap.class, 0) ); + public static final Field field$FireBlock$burnOdds = requireNonNull( + ReflectionUtils.getDeclaredField(clazz$FireBlock, Object2IntMap.class, 1) + ); + public static final Class clazz$EnchantmentMenu = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( "world.inventory.ContainerEnchantTable", diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index c08d6af2d..11557ee15 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -104,7 +104,7 @@ public class BukkitServerPlayer extends Player { private int resentSwingTick; // has fabric client mod or not private boolean hasClientMod = false; - private IntIdentityList blockList = new IntIdentityList(BlockStateUtils.vanillaStateSize()); + private IntIdentityList blockList = new IntIdentityList(BlockStateUtils.vanillaBlockStateCount()); // cache if player can break blocks private boolean clientSideCanBreak = true; // prevent AFK players from consuming too much CPU resource on predicting diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java index a75f5808e..8c1f4c3d3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java @@ -1,15 +1,10 @@ package net.momirealms.craftengine.bukkit.util; -import net.momirealms.craftengine.bukkit.block.BukkitBlockStateWrapper; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.reflection.ReflectionInitException; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; -import net.momirealms.craftengine.core.block.BlockSettings; -import net.momirealms.craftengine.core.block.BlockStateWrapper; -import net.momirealms.craftengine.core.block.DelegatingBlockState; -import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.util.Key; import org.bukkit.block.Block; @@ -18,30 +13,11 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.IdentityHashMap; import java.util.List; -import java.util.Map; import java.util.Optional; public final class BlockStateUtils { - public static final IdentityHashMap CLIENT_SIDE_NOTE_BLOCKS = new IdentityHashMap<>(); - private static int vanillaStateSize; - private static boolean hasInit; - public static Map IGNITE_ODDS; - - @SuppressWarnings("unchecked") - public static void init(int size) { - if (hasInit) { - throw new IllegalStateException("BlockStateUtils has already been initialized"); - } - vanillaStateSize = size; - try { - IGNITE_ODDS = (Map) CoreReflections.field$FireBlock$igniteOdds.get(MBlocks.FIRE); - } catch (ReflectiveOperationException e) { - throw new ReflectionInitException("Failed to initialize instance$FireBlock$igniteOdds", e); - } - hasInit = true; - } + private BlockStateUtils() {} public static BlockStateWrapper toBlockStateWrapper(BlockData blockData) { Object state = blockDataToBlockState(blockData); @@ -50,7 +26,7 @@ public final class BlockStateUtils { public static BlockStateWrapper toBlockStateWrapper(Object blockState) { int id = blockStateToId(blockState); - return new BukkitBlockStateWrapper(blockState, id); + return BlockRegistryMirror.byId(id); } public static boolean isCorrectTool(@NotNull ImmutableBlockState state, @Nullable Item itemInHand) { @@ -64,13 +40,13 @@ public final class BlockStateUtils { } @SuppressWarnings("unchecked") - public static List getAllVanillaBlockStates(Key block) { + public static List getPossibleBlockStates(Key block) { try { Object blockIns = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(block)); Object definition = CoreReflections.field$Block$StateDefinition.get(blockIns); return (List) CoreReflections.field$StateDefinition$states.get(definition); } catch (Exception e) { - throw new RuntimeException("Failed to get all block states for " + block, e); + throw new RuntimeException("Failed to get possible block states for " + block, e); } } @@ -116,10 +92,6 @@ public final class BlockStateUtils { return FastNMS.INSTANCE.method$BlockStateBase$isReplaceable(state); } - public static boolean isClientSideNoteBlock(Object state) { - return CLIENT_SIDE_NOTE_BLOCKS.containsKey(state); - } - public static boolean isVanillaBlock(Object state) { return !(state instanceof DelegatingBlockState); } @@ -129,11 +101,11 @@ public final class BlockStateUtils { } public static boolean isVanillaBlock(int id) { - return id >= 0 && id < vanillaStateSize; + return BukkitBlockManager.instance().isVanillaBlockState(id); } - public static int vanillaStateSize() { - return vanillaStateSize; + public static int vanillaBlockStateCount() { + return BukkitBlockManager.instance().vanillaBlockStateCount(); } public static Optional getOptionalCustomBlockState(Object state) { @@ -144,12 +116,11 @@ public final class BlockStateUtils { } } - public static boolean isBurnable(Object state) { - Object blockOwner = getBlockOwner(state); - return IGNITE_ODDS.getOrDefault(blockOwner, 0) > 0; - } - public static Object getBlockState(Block block) { return FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld()), LocationUtils.toBlockPos(block.getX(), block.getY(), block.getZ())); } + + public static boolean isBurnable(Object blockState) { + return BukkitBlockManager.instance().isBurnable(blockState); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/NoteBlockChainUpdateUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/NoteBlockChainUpdateUtils.java index b77fea1ba..1f00cd1dc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/NoteBlockChainUpdateUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/NoteBlockChainUpdateUtils.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; public final class NoteBlockChainUpdateUtils { @@ -11,7 +12,7 @@ public final class NoteBlockChainUpdateUtils { if (times-- < 0) return; Object relativePos = FastNMS.INSTANCE.method$BlockPos$relative(blockPos, direction); Object state = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, relativePos); - if (BlockStateUtils.isClientSideNoteBlock(state)) { + if (BukkitBlockManager.CLIENT_SIDE_NOTE_BLOCKS.contains(state)) { FastNMS.INSTANCE.method$ServerChunkCache$blockChanged(chunkSource, relativePos); noteBlockChainUpdate(level, chunkSource, direction, relativePos, times); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java index 38dd8db86..0c8d61650 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java @@ -64,7 +64,7 @@ public class BukkitExistingBlock implements ExistingBlock { @Override public @NotNull BlockStateWrapper blockState() { Object blockState = BlockStateUtils.getBlockState(this.block); - return BlockRegistryMirror.stateByRegistryId(BlockStateUtils.blockStateToId(blockState)); + return BlockRegistryMirror.byId(BlockStateUtils.blockStateToId(blockState)); } @Override diff --git a/common-files/src/main/resources/additional-real-blocks.yml b/common-files/src/main/resources/additional-real-blocks.yml deleted file mode 100644 index 6ca380ee7..000000000 --- a/common-files/src/main/resources/additional-real-blocks.yml +++ /dev/null @@ -1,91 +0,0 @@ -# This file will register an additional number of block states to the server, based on the mappings defined in mappings.yml. -# If you're unsure what this means, you can read the following explanation below. - -# Suppose you create a new type of leaf, but its appearance has only two states (waterlogged and normal). -# However, because of the defined properties such as distance, persistent, and waterlogged, it requires at least 2x2x7 = 28 different block states. -# By default, the plugin only registers the same number of block states as those defined in the mappings.yml file. -# Therefore, during actual configuration, you will notice that the internal IDs are insufficient -# (without configuring additional-real-block, one type of leaf can only provide 26 states, whereas creating a new leaf requires 28 states). -# The purpose of this file is to register additional block states with the server when starting it, ensuring the correct mapping between real blocks and the visual appearance of fake blocks on the server. - -# Some common questions: -# Q: Do I need to restart the server for the changes to take effect? -# A: Yes! Modifying the block registry while the server is running is extremely risky. -# Q: When do I need to configure this file? -# A: When the number of real block IDs is insufficient, but there are still available appearances. - -minecraft:oak_leaves: 112 -minecraft:oak_sapling: 1 -minecraft:birch_sapling: 1 -minecraft:spruce_sapling: 1 -minecraft:jungle_sapling: 1 -minecraft:dark_oak_sapling: 1 -minecraft:pale_oak_sapling: 1 -minecraft:acacia_sapling: 1 -minecraft:cherry_sapling: 1 -minecraft:anvil: 2 -minecraft:chipped_anvil: 2 -minecraft:damaged_anvil: 2 -minecraft:sugar_cane: 14 -minecraft:iron_trapdoor: 32 -minecraft:acacia_trapdoor: 32 -minecraft:oak_trapdoor: 32 -minecraft:spruce_trapdoor: 32 -minecraft:birch_trapdoor: 32 -minecraft:jungle_trapdoor: 32 -minecraft:dark_oak_trapdoor: 32 -minecraft:pale_oak_trapdoor: 32 -minecraft:mangrove_trapdoor: 32 -minecraft:cherry_trapdoor: 32 -minecraft:bamboo_trapdoor: 32 -minecraft:crimson_trapdoor: 32 -minecraft:warped_trapdoor: 32 -minecraft:copper_trapdoor: 32 -minecraft:exposed_copper_trapdoor: 32 -minecraft:weathered_copper_trapdoor: 32 -minecraft:oxidized_copper_trapdoor: 32 -minecraft:waxed_copper_trapdoor: 32 -minecraft:waxed_exposed_copper_trapdoor: 32 -minecraft:waxed_weathered_copper_trapdoor: 32 -minecraft:waxed_oxidized_copper_trapdoor: 32 -minecraft:iron_door: 32 -minecraft:acacia_door: 32 -minecraft:oak_door: 32 -minecraft:spruce_door: 32 -minecraft:birch_door: 32 -minecraft:jungle_door: 32 -minecraft:dark_oak_door: 32 -minecraft:pale_oak_door: 32 -minecraft:mangrove_door: 32 -minecraft:cherry_door: 32 -minecraft:bamboo_door: 32 -minecraft:crimson_door: 32 -minecraft:warped_door: 32 -minecraft:copper_door: 32 -minecraft:exposed_copper_door: 32 -minecraft:weathered_copper_door: 32 -minecraft:oxidized_copper_door: 32 -minecraft:waxed_copper_door: 32 -minecraft:waxed_exposed_copper_door: 32 -minecraft:waxed_weathered_copper_door: 32 -minecraft:waxed_oxidized_copper_door: 32 -minecraft:oak_fence_gate: 16 -minecraft:acacia_fence_gate: 16 -minecraft:spruce_fence_gate: 16 -minecraft:birch_fence_gate: 16 -minecraft:jungle_fence_gate: 16 -minecraft:dark_oak_fence_gate: 16 -minecraft:pale_oak_fence_gate: 16 -minecraft:mangrove_fence_gate: 16 -minecraft:cherry_fence_gate: 16 -minecraft:bamboo_fence_gate: 16 -minecraft:crimson_fence_gate: 16 -minecraft:warped_fence_gate: 16 -minecraft:barrier: 128 -minecraft:white_bed: 1 -minecraft:redstone_torch: 1 -minecraft:redstone_wall_torch: 4 -minecraft:pumpkin_stem: 8 -minecraft:attached_pumpkin_stem: 4 -minecraft:birch_button: 24 -minecraft:oak_fence: 32 \ No newline at end of file diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 22cbd0dad..6a773846c 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -139,7 +139,7 @@ resource-pack: item: # Make custom-model-data and item-model clientside by default - client-bound-model: false + client-bound-model: true # Add a tag on custom name and lore non-italic-tag: false # Determines when to trigger the item updater diff --git a/common-files/src/main/resources/mappings.yml b/common-files/src/main/resources/mappings.yml deleted file mode 100644 index d3f9f58f2..000000000 --- a/common-files/src/main/resources/mappings.yml +++ /dev/null @@ -1,4452 +0,0 @@ -# This is one of the plugin's core settings - it basically controls how many block states you can use in your config files. -# Heads up: if you edit this, you'll need to restart the server for changes to take effect. -# Below is the default setup - feel free to add or remove stuff as needed. - -#### Anvil #### -# An anvil has four possible orientations, but the east-west and north-south orientations look exactly the same. -minecraft:anvil[facing=north]: minecraft:anvil[facing=south] -minecraft:anvil[facing=east]: minecraft:anvil[facing=west] -minecraft:chipped_anvil[facing=north]: minecraft:chipped_anvil[facing=south] -minecraft:chipped_anvil[facing=east]: minecraft:chipped_anvil[facing=west] -minecraft:damaged_anvil[facing=north]: minecraft:damaged_anvil[facing=south] -minecraft:damaged_anvil[facing=east]: minecraft:damaged_anvil[facing=west] - -#### Sapling #### -# Every sapling has two stages, 0 and 1, but they look exactly the same. -minecraft:oak_sapling[stage=1]: minecraft:oak_sapling[stage=0] -minecraft:birch_sapling[stage=1]: minecraft:birch_sapling[stage=0] -minecraft:spruce_sapling[stage=1]: minecraft:spruce_sapling[stage=0] -minecraft:jungle_sapling[stage=1]: minecraft:jungle_sapling[stage=0] -minecraft:dark_oak_sapling[stage=1]: minecraft:dark_oak_sapling[stage=0] -minecraft:acacia_sapling[stage=1]: minecraft:acacia_sapling[stage=0] -minecraft:cherry_sapling[stage=1]: minecraft:cherry_sapling[stage=0] -$$>=1.21.4#sapling: - minecraft:pale_oak_sapling[stage=1]: minecraft:pale_oak_sapling[stage=0] - -#### Sculk Sensor #### -# The Sculk Sensor's hitbox is exactly half a block. Plus, its appearance only changes based on the sculk_sensor_phase, -# not the power level. That means we can repurpose the extra states to make bottom-half slabs -minecraft:sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:sculk_sensor[power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:sculk_sensor[power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:sculk_sensor[power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:sculk_sensor[power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:sculk_sensor[power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] - -#### Calibrated Sculk Sensor #### -# Just like the regular Sculk Sensor, but the Calibrated Sculk Sensor has directional facing - which gives us way more usable states to play with -minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] -minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] -minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] - -#### Mushroom #### -# Most people probably don't mind that mushroom blocks look the same on all six sides. So that means each type can free up like, 63 different states we could use for other stuff -minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] - -#### Kelp #### -# 'kelp' here means specifically the top block of the kelp plant. Great for making aquatic crops. -minecraft:kelp[age=1]: minecraft:kelp[age=0] -minecraft:kelp[age=2]: minecraft:kelp[age=0] -minecraft:kelp[age=3]: minecraft:kelp[age=0] -minecraft:kelp[age=4]: minecraft:kelp[age=0] -minecraft:kelp[age=5]: minecraft:kelp[age=0] -minecraft:kelp[age=6]: minecraft:kelp[age=0] -minecraft:kelp[age=7]: minecraft:kelp[age=0] -minecraft:kelp[age=8]: minecraft:kelp[age=0] -minecraft:kelp[age=9]: minecraft:kelp[age=0] -minecraft:kelp[age=10]: minecraft:kelp[age=0] -minecraft:kelp[age=11]: minecraft:kelp[age=0] -minecraft:kelp[age=12]: minecraft:kelp[age=0] -minecraft:kelp[age=13]: minecraft:kelp[age=0] -minecraft:kelp[age=14]: minecraft:kelp[age=0] -minecraft:kelp[age=15]: minecraft:kelp[age=0] -minecraft:kelp[age=16]: minecraft:kelp[age=0] -minecraft:kelp[age=17]: minecraft:kelp[age=0] -minecraft:kelp[age=18]: minecraft:kelp[age=0] -minecraft:kelp[age=19]: minecraft:kelp[age=0] -minecraft:kelp[age=20]: minecraft:kelp[age=0] -minecraft:kelp[age=21]: minecraft:kelp[age=0] -minecraft:kelp[age=22]: minecraft:kelp[age=0] -minecraft:kelp[age=23]: minecraft:kelp[age=0] -minecraft:kelp[age=24]: minecraft:kelp[age=0] -minecraft:kelp[age=25]: minecraft:kelp[age=0] - -#### Vines #### -# Unless you tweak the vine's block tag, the client will always think it's climbable. -# Since vines look identical at different growth stages, we can repurpose those extra states to make custom blocks like ropes. -minecraft:weeping_vines[age=1]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=2]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=3]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=4]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=5]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=6]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=7]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=8]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=9]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=10]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=11]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=12]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=13]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=14]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=15]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=16]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=17]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=18]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=19]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=20]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=21]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=22]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=23]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=24]: minecraft:weeping_vines[age=0] -minecraft:weeping_vines[age=25]: minecraft:weeping_vines[age=0] -minecraft:twisting_vines[age=1]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=2]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=3]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=4]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=5]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=6]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=7]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=8]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=9]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=10]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=11]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=12]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=13]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=14]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=15]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=16]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=17]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=18]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=19]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=20]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=21]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=22]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=23]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=24]: minecraft:twisting_vines[age=0] -minecraft:twisting_vines[age=25]: minecraft:twisting_vines[age=0] -minecraft:cave_vines[age=1,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=2,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=3,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=4,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=5,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=6,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=7,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=8,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=9,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=10,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=11,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=12,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=13,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=14,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=15,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=16,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=17,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=18,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=19,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=20,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=21,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=22,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=23,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=24,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=25,berries=false]: minecraft:cave_vines[age=0,berries=false] -minecraft:cave_vines[age=1,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=2,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=3,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=4,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=5,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=6,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=7,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=8,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=9,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=10,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=11,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=12,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=13,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=14,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=15,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=16,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=17,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=18,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=19,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=20,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=21,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=22,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=23,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=24,berries=true]: minecraft:cave_vines[age=0,berries=true] -minecraft:cave_vines[age=25,berries=true]: minecraft:cave_vines[age=0,berries=true] - -#### SugarCane #### -# Sugar cane looks exactly the same no matter its growth stage. Plus, it's got this perfect hitbox that makes it awesome for taller plants -minecraft:sugar_cane[age=1]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=2]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=3]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=4]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=5]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=6]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=7]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=8]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=9]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=10]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=11]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=12]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=13]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=14]: minecraft:sugar_cane[age=0] -minecraft:sugar_cane[age=15]: minecraft:sugar_cane[age=0] - -#### Cactus #### -# Cactus looks the same at all growth stages.Its hitbox is 14x14x15, making it perfect for creating blocks that are just slightly smaller than full-size -minecraft:cactus[age=1]: minecraft:cactus[age=0] -minecraft:cactus[age=2]: minecraft:cactus[age=0] -minecraft:cactus[age=3]: minecraft:cactus[age=0] -minecraft:cactus[age=4]: minecraft:cactus[age=0] -minecraft:cactus[age=5]: minecraft:cactus[age=0] -minecraft:cactus[age=6]: minecraft:cactus[age=0] -minecraft:cactus[age=7]: minecraft:cactus[age=0] -minecraft:cactus[age=8]: minecraft:cactus[age=0] -minecraft:cactus[age=9]: minecraft:cactus[age=0] -minecraft:cactus[age=10]: minecraft:cactus[age=0] -minecraft:cactus[age=11]: minecraft:cactus[age=0] -minecraft:cactus[age=12]: minecraft:cactus[age=0] -minecraft:cactus[age=13]: minecraft:cactus[age=0] -minecraft:cactus[age=14]: minecraft:cactus[age=0] -minecraft:cactus[age=15]: minecraft:cactus[age=0] - -#### Leaves #### -# The 'distance' and 'persistent' properties are used under the hood to optimize how leaves decay, but visually? They look exactly the same. -# These are some of the few block types that actually support transparent textures. -minecraft:oak_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:oak_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:oak_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:acacia_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:acacia_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:jungle_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:jungle_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:birch_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:birch_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:mangrove_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:mangrove_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:cherry_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:cherry_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:dark_oak_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:dark_oak_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:azalea_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:azalea_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:flowering_azalea_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:flowering_azalea_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] -minecraft:spruce_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] -minecraft:spruce_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] - -$$>=1.21.4#leaves: - minecraft:pale_oak_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - minecraft:pale_oak_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - minecraft:pale_oak_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - -#### Tripwire #### -# Tripwires actually have 128 different states, but we're keeping just two of them to match vanilla's visual styles. -# Honestly, as long as the tripwire works properly, most players won't even mind what it looks like. -# Tripwire hitboxes aren't all the same - the triggered ones are way shorter. -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] - -#### Note Block #### -# This block has the most unused states in Minecraft, but the client always thinks it's interactive. -# Plus, there's some visual glitches when the client try predicting instrument changes. -# We've kept a full set of note settings by default - that way it plays nice with resource packs that show notes -minecraft:note_block[instrument=hat,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=hat,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=hat,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=hat,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=hat,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=hat,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=hat,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=hat,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=hat,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=hat,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=hat,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=hat,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=hat,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=hat,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=hat,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=hat,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=hat,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=hat,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=hat,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=hat,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=hat,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=hat,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=hat,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=hat,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=hat,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=hat,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=hat,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=hat,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=hat,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=hat,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=hat,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=hat,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=hat,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=hat,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=hat,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=hat,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=hat,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=hat,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=hat,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=hat,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=hat,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=hat,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=hat,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=hat,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=hat,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=hat,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=hat,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=hat,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=hat,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=hat,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=basedrum,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=basedrum,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=basedrum,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=basedrum,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=basedrum,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=basedrum,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=basedrum,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=basedrum,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=basedrum,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=basedrum,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=basedrum,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=basedrum,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=basedrum,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=basedrum,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=basedrum,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=basedrum,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=basedrum,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=basedrum,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=basedrum,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=basedrum,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=basedrum,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=basedrum,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=basedrum,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=basedrum,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=basedrum,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=basedrum,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=basedrum,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=basedrum,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=basedrum,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=basedrum,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=basedrum,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=basedrum,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=basedrum,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=basedrum,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=basedrum,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=basedrum,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=basedrum,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=basedrum,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=basedrum,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=basedrum,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=basedrum,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=basedrum,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=basedrum,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=basedrum,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=basedrum,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=basedrum,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=basedrum,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=basedrum,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=basedrum,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=basedrum,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=snare,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=snare,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=snare,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=snare,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=snare,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=snare,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=snare,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=snare,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=snare,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=snare,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=snare,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=snare,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=snare,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=snare,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=snare,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=snare,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=snare,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=snare,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=snare,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=snare,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=snare,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=snare,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=snare,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=snare,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=snare,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=snare,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=snare,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=snare,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=snare,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=snare,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=snare,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=snare,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=snare,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=snare,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=snare,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=snare,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=snare,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=snare,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=snare,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=snare,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=snare,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=snare,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=snare,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=snare,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=snare,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=snare,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=snare,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=snare,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=snare,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=snare,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=bass,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=bass,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=bass,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=bass,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=bass,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=bass,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=bass,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=bass,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=bass,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=bass,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=bass,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=bass,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=bass,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=bass,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=bass,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=bass,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=bass,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=bass,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=bass,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=bass,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=bass,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=bass,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=bass,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=bass,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=bass,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=bass,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=bass,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=bass,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=bass,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=bass,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=bass,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=bass,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=bass,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=bass,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=bass,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=bass,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=bass,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=bass,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=bass,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=bass,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=bass,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=bass,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=bass,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=bass,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=bass,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=bass,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=bass,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=bass,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=bass,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=bass,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=flute,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=flute,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=flute,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=flute,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=flute,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=flute,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=flute,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=flute,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=flute,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=flute,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=flute,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=flute,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=flute,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=flute,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=flute,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=flute,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=flute,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=flute,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=flute,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=flute,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=flute,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=flute,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=flute,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=flute,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=flute,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=flute,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=flute,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=flute,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=flute,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=flute,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=flute,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=flute,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=flute,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=flute,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=flute,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=flute,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=flute,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=flute,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=flute,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=flute,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=flute,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=flute,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=flute,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=flute,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=flute,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=flute,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=flute,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=flute,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=flute,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=flute,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=bell,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=bell,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=bell,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=bell,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=bell,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=bell,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=bell,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=bell,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=bell,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=bell,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=bell,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=bell,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=bell,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=bell,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=bell,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=bell,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=bell,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=bell,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=bell,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=bell,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=bell,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=bell,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=bell,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=bell,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=bell,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=bell,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=bell,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=bell,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=bell,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=bell,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=bell,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=bell,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=bell,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=bell,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=bell,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=bell,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=bell,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=bell,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=bell,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=bell,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=bell,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=bell,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=bell,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=bell,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=bell,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=bell,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=bell,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=bell,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=bell,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=bell,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=guitar,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=guitar,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=guitar,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=guitar,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=guitar,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=guitar,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=guitar,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=guitar,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=guitar,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=guitar,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=guitar,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=guitar,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=guitar,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=guitar,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=guitar,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=guitar,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=guitar,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=guitar,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=guitar,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=guitar,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=guitar,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=guitar,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=guitar,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=guitar,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=guitar,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=guitar,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=guitar,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=guitar,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=guitar,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=guitar,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=guitar,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=guitar,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=guitar,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=guitar,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=guitar,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=guitar,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=guitar,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=guitar,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=guitar,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=guitar,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=guitar,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=guitar,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=guitar,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=guitar,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=guitar,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=guitar,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=guitar,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=guitar,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=guitar,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=guitar,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=chime,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=chime,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=chime,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=chime,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=chime,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=chime,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=chime,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=chime,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=chime,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=chime,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=chime,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=chime,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=chime,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=chime,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=chime,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=chime,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=chime,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=chime,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=chime,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=chime,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=chime,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=chime,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=chime,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=chime,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=chime,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=chime,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=chime,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=chime,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=chime,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=chime,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=chime,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=chime,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=chime,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=chime,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=chime,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=chime,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=chime,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=chime,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=chime,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=chime,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=chime,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=chime,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=chime,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=chime,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=chime,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=chime,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=chime,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=chime,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=chime,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=chime,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=xylophone,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=xylophone,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=xylophone,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=xylophone,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=xylophone,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=xylophone,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=xylophone,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=xylophone,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=xylophone,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=xylophone,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=xylophone,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=xylophone,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=xylophone,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=xylophone,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=xylophone,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=xylophone,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=xylophone,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=xylophone,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=xylophone,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=xylophone,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=xylophone,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=xylophone,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=xylophone,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=xylophone,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=xylophone,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=xylophone,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=xylophone,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=xylophone,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=xylophone,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=xylophone,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=xylophone,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=xylophone,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=xylophone,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=xylophone,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=xylophone,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=xylophone,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=xylophone,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=xylophone,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=xylophone,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=xylophone,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=xylophone,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=xylophone,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=xylophone,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=xylophone,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=xylophone,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=xylophone,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=xylophone,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=xylophone,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=xylophone,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=xylophone,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=iron_xylophone,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=iron_xylophone,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=cow_bell,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=cow_bell,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=cow_bell,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=cow_bell,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=cow_bell,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=cow_bell,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=cow_bell,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=cow_bell,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=cow_bell,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=cow_bell,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=cow_bell,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=cow_bell,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=cow_bell,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=cow_bell,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=cow_bell,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=cow_bell,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=cow_bell,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=cow_bell,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=cow_bell,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=cow_bell,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=cow_bell,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=cow_bell,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=cow_bell,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=cow_bell,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=cow_bell,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=cow_bell,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=cow_bell,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=cow_bell,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=cow_bell,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=cow_bell,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=cow_bell,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=cow_bell,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=cow_bell,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=cow_bell,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=cow_bell,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=cow_bell,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=cow_bell,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=cow_bell,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=cow_bell,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=cow_bell,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=cow_bell,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=cow_bell,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=cow_bell,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=cow_bell,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=cow_bell,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=cow_bell,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=cow_bell,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=cow_bell,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=cow_bell,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=cow_bell,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=didgeridoo,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=didgeridoo,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=didgeridoo,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=didgeridoo,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=didgeridoo,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=didgeridoo,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=didgeridoo,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=didgeridoo,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=didgeridoo,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=didgeridoo,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=didgeridoo,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=didgeridoo,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=didgeridoo,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=didgeridoo,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=didgeridoo,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=didgeridoo,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=didgeridoo,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=didgeridoo,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=didgeridoo,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=didgeridoo,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=didgeridoo,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=didgeridoo,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=didgeridoo,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=didgeridoo,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=didgeridoo,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=didgeridoo,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=didgeridoo,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=didgeridoo,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=didgeridoo,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=didgeridoo,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=didgeridoo,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=didgeridoo,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=didgeridoo,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=didgeridoo,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=didgeridoo,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=didgeridoo,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=didgeridoo,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=didgeridoo,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=didgeridoo,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=didgeridoo,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=didgeridoo,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=didgeridoo,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=didgeridoo,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=didgeridoo,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=didgeridoo,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=didgeridoo,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=didgeridoo,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=didgeridoo,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=didgeridoo,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=didgeridoo,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=bit,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=bit,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=bit,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=bit,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=bit,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=bit,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=bit,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=bit,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=bit,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=bit,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=bit,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=bit,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=bit,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=bit,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=bit,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=bit,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=bit,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=bit,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=bit,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=bit,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=bit,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=bit,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=bit,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=bit,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=bit,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=bit,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=bit,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=bit,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=bit,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=bit,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=bit,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=bit,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=bit,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=bit,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=bit,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=bit,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=bit,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=bit,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=bit,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=bit,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=bit,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=bit,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=bit,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=bit,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=bit,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=bit,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=bit,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=bit,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=bit,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=bit,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=banjo,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=banjo,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=banjo,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=banjo,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=banjo,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=banjo,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=banjo,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=banjo,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=banjo,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=banjo,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=banjo,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=banjo,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=banjo,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=banjo,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=banjo,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=banjo,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=banjo,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=banjo,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=banjo,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=banjo,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=banjo,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=banjo,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=banjo,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=banjo,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=banjo,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=banjo,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=banjo,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=banjo,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=banjo,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=banjo,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=banjo,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=banjo,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=banjo,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=banjo,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=banjo,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=banjo,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=banjo,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=banjo,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=banjo,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=banjo,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=banjo,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=banjo,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=banjo,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=banjo,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=banjo,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=banjo,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=banjo,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=banjo,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=banjo,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=banjo,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=pling,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=pling,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=pling,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=pling,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=pling,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=pling,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=pling,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=pling,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=pling,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=pling,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=pling,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=pling,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=pling,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=pling,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=pling,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=pling,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=pling,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=pling,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=pling,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=pling,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=pling,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=pling,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=pling,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=pling,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=pling,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=pling,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=pling,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=pling,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=pling,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=pling,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=pling,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=pling,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=pling,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=pling,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=pling,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=pling,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=pling,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=pling,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=pling,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=pling,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=pling,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=pling,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=pling,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=pling,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=pling,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=pling,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=pling,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=pling,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=pling,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=pling,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=zombie,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=zombie,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=zombie,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=zombie,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=zombie,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=zombie,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=zombie,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=zombie,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=zombie,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=zombie,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=zombie,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=zombie,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=zombie,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=zombie,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=zombie,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=zombie,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=zombie,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=zombie,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=zombie,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=zombie,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=zombie,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=zombie,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=zombie,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=zombie,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=zombie,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=zombie,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=zombie,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=zombie,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=zombie,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=zombie,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=zombie,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=zombie,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=zombie,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=zombie,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=zombie,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=zombie,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=zombie,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=zombie,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=zombie,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=zombie,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=zombie,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=zombie,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=zombie,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=zombie,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=zombie,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=zombie,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=zombie,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=zombie,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=zombie,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=zombie,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=skeleton,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=skeleton,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=skeleton,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=skeleton,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=skeleton,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=skeleton,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=skeleton,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=skeleton,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=skeleton,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=skeleton,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=skeleton,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=skeleton,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=skeleton,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=skeleton,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=skeleton,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=skeleton,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=skeleton,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=skeleton,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=skeleton,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=skeleton,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=skeleton,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=skeleton,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=skeleton,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=skeleton,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=skeleton,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=skeleton,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=skeleton,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=skeleton,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=skeleton,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=skeleton,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=skeleton,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=skeleton,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=skeleton,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=skeleton,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=skeleton,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=skeleton,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=skeleton,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=skeleton,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=skeleton,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=skeleton,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=skeleton,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=skeleton,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=skeleton,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=skeleton,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=skeleton,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=skeleton,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=skeleton,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=skeleton,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=skeleton,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=skeleton,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=creeper,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=creeper,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=creeper,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=creeper,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=creeper,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=creeper,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=creeper,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=creeper,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=creeper,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=creeper,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=creeper,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=creeper,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=creeper,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=creeper,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=creeper,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=creeper,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=creeper,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=creeper,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=creeper,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=creeper,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=creeper,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=creeper,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=creeper,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=creeper,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=creeper,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=creeper,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=creeper,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=creeper,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=creeper,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=creeper,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=creeper,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=creeper,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=creeper,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=creeper,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=creeper,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=creeper,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=creeper,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=creeper,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=creeper,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=creeper,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=creeper,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=creeper,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=creeper,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=creeper,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=creeper,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=creeper,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=creeper,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=creeper,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=creeper,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=creeper,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=dragon,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=dragon,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=dragon,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=dragon,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=dragon,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=dragon,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=dragon,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=dragon,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=dragon,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=dragon,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=dragon,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=dragon,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=dragon,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=dragon,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=dragon,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=dragon,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=dragon,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=dragon,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=dragon,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=dragon,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=dragon,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=dragon,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=dragon,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=dragon,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=dragon,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=dragon,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=dragon,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=dragon,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=dragon,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=dragon,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=dragon,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=dragon,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=dragon,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=dragon,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=dragon,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=dragon,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=dragon,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=dragon,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=dragon,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=dragon,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=dragon,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=dragon,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=dragon,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=dragon,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=dragon,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=dragon,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=dragon,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=dragon,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=dragon,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=dragon,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=wither_skeleton,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=wither_skeleton,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=piglin,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=piglin,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=piglin,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=piglin,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=piglin,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=piglin,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=piglin,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=piglin,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=piglin,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=piglin,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=piglin,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=piglin,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=piglin,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=piglin,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=piglin,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=piglin,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=piglin,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=piglin,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=piglin,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=piglin,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=piglin,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=piglin,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=piglin,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=piglin,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=piglin,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=piglin,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=piglin,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=piglin,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=piglin,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=piglin,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=piglin,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=piglin,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=piglin,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=piglin,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=piglin,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=piglin,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=piglin,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=piglin,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=piglin,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=piglin,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=piglin,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=piglin,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=piglin,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=piglin,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=piglin,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=piglin,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=piglin,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=piglin,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=piglin,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=piglin,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] -minecraft:note_block[instrument=custom_head,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] -minecraft:note_block[instrument=custom_head,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] -minecraft:note_block[instrument=custom_head,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] -minecraft:note_block[instrument=custom_head,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] -minecraft:note_block[instrument=custom_head,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] -minecraft:note_block[instrument=custom_head,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] -minecraft:note_block[instrument=custom_head,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] -minecraft:note_block[instrument=custom_head,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] -minecraft:note_block[instrument=custom_head,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] -minecraft:note_block[instrument=custom_head,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] -minecraft:note_block[instrument=custom_head,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] -minecraft:note_block[instrument=custom_head,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] -minecraft:note_block[instrument=custom_head,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] -minecraft:note_block[instrument=custom_head,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] -minecraft:note_block[instrument=custom_head,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] -minecraft:note_block[instrument=custom_head,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] -minecraft:note_block[instrument=custom_head,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] -minecraft:note_block[instrument=custom_head,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] -minecraft:note_block[instrument=custom_head,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] -minecraft:note_block[instrument=custom_head,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] -minecraft:note_block[instrument=custom_head,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] -minecraft:note_block[instrument=custom_head,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] -minecraft:note_block[instrument=custom_head,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] -minecraft:note_block[instrument=custom_head,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] -minecraft:note_block[instrument=custom_head,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] -minecraft:note_block[instrument=custom_head,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] -minecraft:note_block[instrument=custom_head,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] -minecraft:note_block[instrument=custom_head,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] -minecraft:note_block[instrument=custom_head,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] -minecraft:note_block[instrument=custom_head,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] -minecraft:note_block[instrument=custom_head,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] -minecraft:note_block[instrument=custom_head,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] -minecraft:note_block[instrument=custom_head,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] -minecraft:note_block[instrument=custom_head,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] -minecraft:note_block[instrument=custom_head,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] -minecraft:note_block[instrument=custom_head,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] -minecraft:note_block[instrument=custom_head,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] -minecraft:note_block[instrument=custom_head,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] -minecraft:note_block[instrument=custom_head,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] -minecraft:note_block[instrument=custom_head,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] -minecraft:note_block[instrument=custom_head,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] -minecraft:note_block[instrument=custom_head,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] -minecraft:note_block[instrument=custom_head,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] -minecraft:note_block[instrument=custom_head,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] -minecraft:note_block[instrument=custom_head,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] -minecraft:note_block[instrument=custom_head,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] -minecraft:note_block[instrument=custom_head,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] -minecraft:note_block[instrument=custom_head,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] -minecraft:note_block[instrument=custom_head,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] -minecraft:note_block[instrument=custom_head,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] - -#### Trapdoor #### -# Trapdoors look identical whether they're powered or not - which means we can double our trapdoors by using both states -minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] -minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] -minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - -$$>=1.20.3#trapdoor: - minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - # Fun fact: copper blocks look the same whether waxed or not. - # We're playing it safe with the default setup - keeping vanilla's waxed states recognizable. - # But you can always change it to convert waxed blocks back to regular ones. - minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - -$$>=1.21.4#trapdoor: - minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] - minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] - minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] - -#### Door #### -# A door look exactly the same whether it's powered on or off, just like how a trapdoor works -minecraft:oak_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:oak_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:oak_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:oak_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:oak_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:spruce_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:spruce_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:spruce_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:spruce_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:birch_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:birch_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:birch_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:birch_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:jungle_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:jungle_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:jungle_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:jungle_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:acacia_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:acacia_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:acacia_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:acacia_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:cherry_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:cherry_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:cherry_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:cherry_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:crimson_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:crimson_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:crimson_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:crimson_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:warped_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:warped_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:warped_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:warped_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=left,open=false,powered=false] -minecraft:iron_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=right,open=false,powered=false] -minecraft:iron_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=right,open=true,powered=false] -minecraft:iron_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=left,open=true,powered=false] -minecraft:iron_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=right,open=true,powered=false] - -$$>=1.20.3#door: - minecraft:copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] - -$$>=1.21.4#door: - minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=false,powered=false] - minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=false,powered=false] - minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=true,powered=false] - minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=true,powered=false] - minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=true,powered=false] - -#### Fence Gate #### -minecraft:oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:oak_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:oak_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:oak_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:oak_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:oak_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:oak_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:oak_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:oak_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:spruce_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:spruce_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:spruce_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:spruce_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:spruce_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:spruce_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:spruce_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:spruce_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:spruce_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:spruce_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:spruce_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:spruce_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:spruce_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:spruce_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:spruce_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:spruce_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:birch_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:birch_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:birch_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:birch_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:birch_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:birch_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:birch_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:birch_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:birch_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:birch_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:birch_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:birch_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:birch_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:birch_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:birch_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:birch_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:jungle_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:jungle_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:jungle_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:jungle_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:jungle_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:jungle_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:jungle_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:jungle_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:jungle_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:jungle_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:jungle_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:jungle_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:jungle_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:jungle_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:jungle_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:jungle_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:acacia_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:acacia_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:acacia_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:acacia_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:acacia_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:acacia_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:acacia_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:acacia_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:acacia_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:acacia_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:acacia_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:acacia_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:acacia_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:acacia_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:acacia_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:acacia_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:cherry_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:cherry_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:cherry_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:cherry_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:cherry_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:cherry_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:cherry_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:cherry_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:cherry_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:cherry_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:cherry_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:cherry_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:cherry_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:cherry_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:cherry_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:cherry_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:crimson_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:crimson_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:crimson_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:crimson_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:crimson_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:crimson_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:crimson_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:crimson_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:crimson_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:crimson_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:crimson_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:crimson_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:crimson_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:crimson_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:crimson_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:crimson_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=true,open=true,powered=false] -minecraft:warped_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=false,open=false,powered=false] -minecraft:warped_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=false,open=true,powered=false] -minecraft:warped_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=true,open=false,powered=false] -minecraft:warped_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=true,open=true,powered=false] -minecraft:warped_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=false,open=false,powered=false] -minecraft:warped_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=false,open=true,powered=false] -minecraft:warped_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=true,open=false,powered=false] -minecraft:warped_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=true,open=true,powered=false] -minecraft:warped_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=false,open=false,powered=false] -minecraft:warped_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=false,open=true,powered=false] -minecraft:warped_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=true,open=false,powered=false] -minecraft:warped_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=true,open=true,powered=false] -minecraft:warped_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=false,open=false,powered=false] -minecraft:warped_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=false,open=true,powered=false] -minecraft:warped_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=true,open=false,powered=false] -minecraft:warped_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=true,open=true,powered=false] - -$$>=1.21.4#fence_gate: - minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=true,powered=false] - minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=false,powered=false] - minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=true,powered=false] - -#### Slab #### -minecraft:petrified_oak_slab[type=bottom,waterlogged=false]: minecraft:oak_slab[type=bottom,waterlogged=false] -minecraft:petrified_oak_slab[type=top,waterlogged=false]: minecraft:oak_slab[type=top,waterlogged=false] -minecraft:petrified_oak_slab[type=double,waterlogged=false]: minecraft:oak_slab[type=double,waterlogged=false] -minecraft:petrified_oak_slab[type=bottom,waterlogged=true]: minecraft:oak_slab[type=bottom,waterlogged=true] -minecraft:petrified_oak_slab[type=top,waterlogged=true]: minecraft:oak_slab[type=top,waterlogged=true] -minecraft:petrified_oak_slab[type=double,waterlogged=true]: minecraft:oak_slab[type=double,waterlogged=true] -minecraft:cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_cut_copper_slab[type=bottom,waterlogged=false] -minecraft:cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_cut_copper_slab[type=top,waterlogged=false] -minecraft:cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_cut_copper_slab[type=double,waterlogged=false] -minecraft:cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_cut_copper_slab[type=bottom,waterlogged=true] -minecraft:cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_cut_copper_slab[type=top,waterlogged=true] -minecraft:cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_cut_copper_slab[type=double,waterlogged=true] -minecraft:exposed_cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_exposed_cut_copper_slab[type=bottom,waterlogged=false] -minecraft:exposed_cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_exposed_cut_copper_slab[type=top,waterlogged=false] -minecraft:exposed_cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_exposed_cut_copper_slab[type=double,waterlogged=false] -minecraft:exposed_cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_exposed_cut_copper_slab[type=bottom,waterlogged=true] -minecraft:exposed_cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_exposed_cut_copper_slab[type=top,waterlogged=true] -minecraft:exposed_cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_exposed_cut_copper_slab[type=double,waterlogged=true] -minecraft:weathered_cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_weathered_cut_copper_slab[type=bottom,waterlogged=false] -minecraft:weathered_cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_weathered_cut_copper_slab[type=top,waterlogged=false] -minecraft:weathered_cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_weathered_cut_copper_slab[type=double,waterlogged=false] -minecraft:weathered_cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_weathered_cut_copper_slab[type=bottom,waterlogged=true] -minecraft:weathered_cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_weathered_cut_copper_slab[type=top,waterlogged=true] -minecraft:weathered_cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_weathered_cut_copper_slab[type=double,waterlogged=true] -minecraft:oxidized_cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_slab[type=bottom,waterlogged=false] -minecraft:oxidized_cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_slab[type=top,waterlogged=false] -minecraft:oxidized_cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_slab[type=double,waterlogged=false] -minecraft:oxidized_cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_slab[type=bottom,waterlogged=true] -minecraft:oxidized_cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_slab[type=top,waterlogged=true] -minecraft:oxidized_cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_slab[type=double,waterlogged=true] - -#### Stairs #### -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] -minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] -minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] -minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] -minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] -minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] - -#### Grate #### -# Suitable for making glass because it is completely transparent -$$>=1.20.3#grate: - minecraft:copper_grate[waterlogged=false]: minecraft:waxed_copper_grate[waterlogged=false] - minecraft:copper_grate[waterlogged=true]: minecraft:waxed_copper_grate[waterlogged=true] - minecraft:weathered_copper_grate[waterlogged=false]: minecraft:waxed_weathered_copper_grate[waterlogged=false] - minecraft:weathered_copper_grate[waterlogged=true]: minecraft:waxed_weathered_copper_grate[waterlogged=true] - minecraft:exposed_copper_grate[waterlogged=false]: minecraft:waxed_exposed_copper_grate[waterlogged=false] - minecraft:exposed_copper_grate[waterlogged=true]: minecraft:waxed_exposed_copper_grate[waterlogged=true] - minecraft:oxidized_copper_grate[waterlogged=false]: minecraft:waxed_oxidized_copper_grate[waterlogged=false] - minecraft:oxidized_copper_grate[waterlogged=true]: minecraft:waxed_oxidized_copper_grate[waterlogged=true] - -#### Pressure Plate #### -# Triggered pressure plates appear identical, even though they output different signal strengths. -minecraft:light_weighted_pressure_plate[power=2]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=3]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=4]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=5]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=6]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=7]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=8]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=9]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=10]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=11]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=12]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=13]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=14]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:light_weighted_pressure_plate[power=15]: minecraft:light_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=2]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=3]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=4]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=5]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=6]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=7]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=8]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=9]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=10]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=11]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=12]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=13]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=14]: minecraft:heavy_weighted_pressure_plate[power=1] -minecraft:heavy_weighted_pressure_plate[power=15]: minecraft:heavy_weighted_pressure_plate[power=1] - -#### Corals #### -# Coral blocks are ideal for creating water blocks or wall-mounted blocks. But you have to sacrifice its dry appearance. -# minecraft:dead_brain_coral[waterlogged=false]: minecraft:brain_coral[waterlogged=false] -# minecraft:dead_brain_coral[waterlogged=true]: minecraft:brain_coral[waterlogged=true] -# minecraft:dead_brain_coral_fan[waterlogged=false]: minecraft:brain_coral_fan[waterlogged=false] -# minecraft:dead_brain_coral_fan[waterlogged=true]: minecraft:brain_coral_fan[waterlogged=true] -# minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=east]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=north]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=south]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=west]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=west] -# minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=east]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=east] -# minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=north]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=north] -# minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=south]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=south] -# minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=west]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=west] -# minecraft:dead_bubble_coral[waterlogged=false]: minecraft:bubble_coral[waterlogged=false] -# minecraft:dead_bubble_coral[waterlogged=true]: minecraft:bubble_coral[waterlogged=true] -# minecraft:dead_bubble_coral_fan[waterlogged=false]: minecraft:bubble_coral_fan[waterlogged=false] -# minecraft:dead_bubble_coral_fan[waterlogged=true]: minecraft:bubble_coral_fan[waterlogged=true] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=west] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=east] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=north] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=south] -# minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=west] -# minecraft:dead_fire_coral[waterlogged=false]: minecraft:fire_coral[waterlogged=false] -# minecraft:dead_fire_coral[waterlogged=true]: minecraft:fire_coral[waterlogged=true] -# minecraft:dead_fire_coral_fan[waterlogged=false]: minecraft:fire_coral_fan[waterlogged=false] -# minecraft:dead_fire_coral_fan[waterlogged=true]: minecraft:fire_coral_fan[waterlogged=true] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=west] -# minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=east] -# minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=north] -# minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=south] -# minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=west] -# minecraft:dead_horn_coral[waterlogged=false]: minecraft:horn_coral[waterlogged=false] -# minecraft:dead_horn_coral[waterlogged=true]: minecraft:horn_coral[waterlogged=true] -# minecraft:dead_horn_coral_fan[waterlogged=false]: minecraft:horn_coral_fan[waterlogged=false] -# minecraft:dead_horn_coral_fan[waterlogged=true]: minecraft:horn_coral_fan[waterlogged=true] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=west] -# minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=east] -# minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=north] -# minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=south] -# minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=west] -# minecraft:dead_tube_coral[waterlogged=false]: minecraft:tube_coral[waterlogged=false] -# minecraft:dead_tube_coral[waterlogged=true]: minecraft:tube_coral[waterlogged=true] -# minecraft:dead_tube_coral_fan[waterlogged=false]: minecraft:tube_coral_fan[waterlogged=false] -# minecraft:dead_tube_coral_fan[waterlogged=true]: minecraft:tube_coral_fan[waterlogged=true] -# minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=east]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=east] -# minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=north]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=north] -# minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=south]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=south] -# minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=west]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=west] -# minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=east]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=east] -# minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=north]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=north] -# minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=south]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=south] -# minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=west]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=west] - -#### Chorus Plant #### -# Chorus Plant does support transparent textures, but man... its hitbox is super weird. You're probably better off using leaves. -# minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -# minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml index 84b5e29e0..773ea9c4e 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/amethyst_torch.yml @@ -1,7 +1,6 @@ items: default:amethyst_torch: material: nether_brick - custom-model-data: 3020 data: item-name: model: @@ -20,7 +19,6 @@ items: block: default:amethyst_wall_torch default:amethyst_standing_torch: material: nether_brick - custom-model-data: 3021 data: item-name: model: @@ -32,7 +30,6 @@ items: torch: minecraft:block/custom/amethyst_torch default:amethyst_wall_torch: material: nether_brick - custom-model-data: 3022 data: item-name: model: @@ -59,7 +56,6 @@ blocks: luminance: 15 item: default:amethyst_torch state: - id: 0 state: redstone_torch[lit=false] entity-renderer: item: default:amethyst_standing_torch @@ -146,16 +142,12 @@ blocks: variants: facing=north: appearance: north - id: 0 facing=east: appearance: east - id: 1 facing=west: appearance: west - id: 2 facing=south: appearance: south - id: 3 recipes: default:amethyst_torch: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml index 2ba57608c..07e6a3f1e 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml @@ -1,7 +1,6 @@ items: default:chessboard_block: material: nether_brick - custom-model-data: 3000 data: item-name: model: @@ -60,16 +59,12 @@ items: variants: facing=east: appearance: east - id: 18 facing=north: appearance: north - id: 19 facing=south: appearance: south - id: 20 facing=west: appearance: west - id: 21 recipes: default:chessboard_block: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml index 9a22722b8..4776b2d4d 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml @@ -1,7 +1,6 @@ items: default:chinese_lantern: material: nether_brick - custom-model-data: 3001 data: item-name: model: @@ -26,7 +25,6 @@ items: luminance: 15 map-color: 36 state: - id: 15 state: note_block:15 model: path: minecraft:block/custom/chinese_lantern diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml index 7edbe4e33..94185e5aa 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml @@ -1,7 +1,6 @@ items: default:copper_coil: material: nether_brick - custom-model-data: 3002 data: item-name: model: @@ -61,10 +60,8 @@ items: variants: lit=false: appearance: 'off' - id: 0 lit=true: appearance: 'on' - id: 1 settings: luminance: 8 recipes: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml index 6ccd4a673..ccb1fad33 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml @@ -1,7 +1,6 @@ items: default:ender_pearl_flower_seeds: material: nether_brick - custom-model-data: 3003 data: item-name: model: @@ -117,13 +116,10 @@ blocks: variants: age=0: appearance: stage_0 - id: 0 age=1: appearance: stage_1 - id: 1 age=2: appearance: stage_2 - id: 8 vanilla-loots: default:ender_pearl_flower_seeds_from_endermite: type: entity diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml index 4777b653c..5e2b00ba4 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml @@ -1,7 +1,6 @@ items: default:fairy_flower: material: nether_brick - custom-model-data: 3004 data: item-name: model: @@ -27,7 +26,6 @@ items: loot: template: default:loot_table/self state: - id: 0 state: sugar_cane:0 models: - path: minecraft:block/custom/fairy_flower_1 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml index 6a43fc1cf..f24859eef 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml @@ -1,7 +1,6 @@ items: default:flame_cane: material: nether_brick - custom-model-data: 3005 data: item-name: model: @@ -71,22 +70,16 @@ items: variants: age=0: appearance: default - id: 2 age=1: appearance: default - id: 3 age=2: appearance: default - id: 4 age=3: appearance: default - id: 5 age=4: appearance: default - id: 6 age=5: appearance: default - id: 7 recipes: default:magma_cream: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml index 1bab9bb39..17ccff09d 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml @@ -1,7 +1,6 @@ items: default:gunpowder_block: material: nether_brick - custom-model-data: 3006 data: item-name: model: @@ -28,7 +27,6 @@ items: instrument: snare map-color: 45 state: - id: 16 state: note_block:16 model: path: minecraft:block/custom/gunpowder_block @@ -38,7 +36,6 @@ items: all: minecraft:block/custom/gunpowder_block default:solid_gunpowder_block: material: nether_brick - custom-model-data: 3007 data: item-name: model: @@ -62,7 +59,6 @@ items: instrument: basedrum map-color: 45 state: - id: 17 state: note_block:17 model: path: minecraft:block/custom/solid_gunpowder_block diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml index f9407d1f2..46e1b51e7 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml @@ -1,7 +1,6 @@ items: default:hami_melon_slice: material: melon_slice - custom-model-data: 1000 data: item-name: $$>=1.20.5: @@ -20,7 +19,6 @@ items: path: minecraft:item/custom/hami_melon_slice default:hami_melon: material: nether_brick - custom-model-data: 3023 data: item-name: model: @@ -32,7 +30,6 @@ items: block: default:hami_melon default:hami_melon_seeds: material: nether_brick - custom-model-data: 3024 data: item-name: model: @@ -82,7 +79,6 @@ blocks: - minecraft:mineable/axe - minecraft:sword_efficient state: - id: 30 state: note_block:30 model: template: default:model/cube @@ -144,28 +140,20 @@ blocks: variants: age=0: appearance: age=0 - id: 0 age=1: appearance: age=1 - id: 1 age=2: appearance: age=2 - id: 2 age=3: appearance: age=3 - id: 3 age=4: appearance: age=4 - id: 4 age=5: appearance: age=5 - id: 5 age=6: appearance: age=6 - id: 6 age=7: appearance: age=7 - id: 7 default:attached_hami_melon_stem: settings: template: @@ -205,16 +193,12 @@ blocks: variants: facing=east: appearance: facing=east - id: 0 facing=south: appearance: facing=south - id: 1 facing=west: appearance: facing=west - id: 2 facing=north: appearance: facing=north - id: 3 recipes: default:hami_melon: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml index add02ff3b..467ff359b 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/netherite_anvil.yml @@ -1,7 +1,6 @@ items: default:netherite_anvil: material: nether_brick - custom-model-data: 3008 data: item-name: model: @@ -18,6 +17,9 @@ items: type: falling_block hurt-amount: 4 max-hurt: 80 + sounds: + land: minecraft:block.anvil.land + destroy: minecraft:block.anvil.destroy events: - on: right_click functions: @@ -39,8 +41,6 @@ items: place: minecraft:block.anvil.place hit: minecraft:block.anvil.hit fall: minecraft:block.anvil.fall - land: minecraft:block.anvil.land - destroy: minecraft:block.anvil.destroy map-color: 29 hardness: 10.0 resistance: 1200 @@ -69,16 +69,12 @@ items: variants: facing_clockwise=east: appearance: axisX - id: 0 facing_clockwise=west: appearance: axisX - id: 1 facing_clockwise=north: appearance: axisZ - id: 2 facing_clockwise=south: appearance: axisZ - id: 3 recipes: default:netherite_anvil: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 7e5fcef3a..65268063a 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -1,7 +1,6 @@ items: default:palm_log: material: nether_brick - custom-model-data: 1000 settings: fuel-time: 300 tags: @@ -33,17 +32,8 @@ items: texture_side_path: minecraft:block/custom/palm_log model_vertical_path: minecraft:block/custom/palm_log model_horizontal_path: minecraft:block/custom/palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 0 - to: 2 - internal_id: - type: self_increase_int - from: 0 - to: 2 default:stripped_palm_log: material: nether_brick - custom-model-data: 1001 settings: fuel-time: 300 tags: @@ -72,17 +62,8 @@ items: texture_side_path: minecraft:block/custom/stripped_palm_log model_vertical_path: minecraft:block/custom/stripped_palm_log model_horizontal_path: minecraft:block/custom/stripped_palm_log_horizontal - vanilla_id: - type: self_increase_int - from: 3 - to: 5 - internal_id: - type: self_increase_int - from: 3 - to: 5 default:palm_wood: material: nether_brick - custom-model-data: 1002 settings: fuel-time: 300 tags: @@ -114,17 +95,8 @@ items: texture_side_path: minecraft:block/custom/palm_log model_vertical_path: minecraft:block/custom/palm_wood model_horizontal_path: minecraft:block/custom/palm_wood_horizontal - vanilla_id: - type: self_increase_int - from: 6 - to: 8 - internal_id: - type: self_increase_int - from: 6 - to: 8 default:stripped_palm_wood: material: nether_brick - custom-model-data: 1003 settings: fuel-time: 300 tags: @@ -153,17 +125,8 @@ items: texture_side_path: minecraft:block/custom/stripped_palm_log model_vertical_path: minecraft:block/custom/stripped_palm_wood model_horizontal_path: minecraft:block/custom/stripped_palm_wood_horizontal - vanilla_id: - type: self_increase_int - from: 9 - to: 11 - internal_id: - type: self_increase_int - from: 9 - to: 11 default:palm_planks: material: nether_brick - custom-model-data: 1004 settings: fuel-time: 300 tags: @@ -188,11 +151,9 @@ items: template: default:model/simplified_cube_all arguments: path: minecraft:block/custom/palm_planks - id: 12 state: note_block:12 default:palm_sapling: material: nether_brick - custom-model-data: 1005 settings: fuel-time: 100 data: @@ -236,13 +197,10 @@ items: variants: stage=0: appearance: default - id: 0 stage=1: appearance: default - id: 1 default:palm_leaves: material: oak_leaves - custom-model-data: 1000 data: item-name: components: @@ -277,13 +235,8 @@ items: waterlogged_state: oak_leaves[distance=1,persistent=false,waterlogged=true] model_path: minecraft:block/custom/palm_leaves texture_path: minecraft:block/custom/palm_leaves - internal_id: - type: self_increase_int - from: 0 - to: 27 default:palm_trapdoor: material: nether_brick - custom-model-data: 1006 data: item-name: settings: @@ -338,7 +291,6 @@ items: texture: minecraft:block/custom/palm_trapdoor default:palm_door: material: nether_brick - custom-model-data: 1007 data: item-name: settings: @@ -413,7 +365,6 @@ items: textures: *textures default:palm_fence_gate: material: nether_brick - custom-model-data: 1008 data: item-name: settings: @@ -470,7 +421,6 @@ items: textures: *textures default:palm_slab: material: nether_brick - custom-model-data: 1009 data: item-name: settings: @@ -517,7 +467,6 @@ items: model_double_path: minecraft:block/custom/palm_planks default:palm_stairs: material: nether_brick - custom-model-data: 1013 model: type: minecraft:model path: minecraft:item/custom/palm_stairs @@ -567,7 +516,6 @@ items: textures: *textures default:palm_pressure_plate: material: nether_brick - custom-model-data: 1014 model: type: minecraft:model path: minecraft:item/custom/palm_pressure_plate @@ -608,8 +556,6 @@ items: arguments: normal_state: light_weighted_pressure_plate:0 powered_state: light_weighted_pressure_plate:1 - normal_id: 0 - powered_id: 1 model_normal_path: minecraft:block/custom/palm_pressure_plate model_normal_generation: parent: minecraft:block/pressure_plate_up @@ -624,7 +570,6 @@ items: items#pfence: default:palm_fence: material: nether_brick - custom-model-data: 1018 data: item-name: model: @@ -639,7 +584,6 @@ items#pfence: block: default:palm_fence default:palm_fence_post: material: nether_brick - custom-model-data: 1019 model: type: minecraft:model path: minecraft:block/custom/palm_fence_post @@ -649,7 +593,6 @@ items#pfence: texture: minecraft:block/custom/palm_planks default:palm_fence_side: material: nether_brick - custom-model-data: 1020 model: type: minecraft:model path: minecraft:block/custom/palm_fence_side @@ -689,15 +632,10 @@ blocks#fence: base_block: oak_fence fence_post_item: default:palm_fence_post fence_side_item: default:palm_fence_side - internal_id: - type: self_increase_int - from: 0 - to: 31 items#button: default:palm_button: material: nether_brick - custom-model-data: 1015 model: type: minecraft:model path: minecraft:item/custom/palm_button @@ -714,7 +652,6 @@ items#button: block: default:palm_button default:palm_button_pressed: material: nether_brick - custom-model-data: 1016 model: type: minecraft:model path: minecraft:block/custom/palm_button_pressed @@ -724,7 +661,6 @@ items#button: texture: minecraft:block/custom/palm_planks default:palm_button_not_pressed: material: nether_brick - custom-model-data: 1017 model: type: minecraft:model path: minecraft:block/custom/palm_button_not_pressed @@ -763,10 +699,6 @@ blocks#button: base_block: birch_button pressed_item: default:palm_button_pressed not_pressed_item: default:palm_button_not_pressed - internal_id: - type: self_increase_int - from: 0 - to: 23 recipes: default:palm_planks: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml index 9d5b8a502..5a164c44e 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml @@ -1,7 +1,6 @@ items: default:pebble: material: nether_brick - custom-model-data: 3009 data: item-name: model: @@ -103,13 +102,10 @@ items: variants: pebble=1: appearance: 'one' - id: 2 pebble=2: appearance: 'two' - id: 3 pebble=3: appearance: 'three' - id: 4 recipes: default:pebble: type: shapeless diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml index f444576b6..c43425c81 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml @@ -1,7 +1,6 @@ items: default:reed: material: nether_brick - custom-model-data: 3010 data: item-name: model: @@ -26,7 +25,6 @@ items: loot: template: default:loot_table/self state: - id: 1 state: sugar_cane:1 model: path: minecraft:block/custom/reed diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index a1349bcbd..710804ef9 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -1,7 +1,6 @@ items: default:safe_block: material: nether_brick - custom-model-data: 3011 data: item-name: model: @@ -101,28 +100,20 @@ items: variants: facing=east,open=false: appearance: east - id: 22 facing=east,open=true: appearance: east_open - id: 23 facing=north,open=false: appearance: north - id: 24 facing=north,open=true: appearance: north_open - id: 25 facing=south,open=false: appearance: south - id: 26 facing=south,open=true: appearance: south_open - id: 27 facing=west,open=false: appearance: west - id: 28 facing=west,open=true: appearance: west_open - id: 29 recipes: default:safe_block: type: shaped diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index 131392513..73ffa6029 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -1,7 +1,6 @@ items: default:sleeper_sofa: material: nether_brick - custom-model-data: 3012 data: item-name: model: @@ -37,19 +36,16 @@ items: bounce-height: 0.66 sync-player-position: false state: - id: 0 state: white_bed[facing=west,occupied=false,part=foot] entity-renderer: item: default:sleeper_sofa default:sofa_inner: material: nether_brick - custom-model-data: 3013 model: type: minecraft:model path: minecraft:item/custom/sofa_inner default:sofa: material: nether_brick - custom-model-data: 3014 data: item-name: model: @@ -150,37 +146,25 @@ items: variants: facing=east,shape=inner_left: appearance: facing=east,shape=inner_left - id: 0 facing=east,shape=inner_right: appearance: facing=east,shape=inner_right - id: 1 facing=east,shape=straight: appearance: facing=east,shape=straight - id: 2 facing=north,shape=inner_left: appearance: facing=north,shape=inner_left - id: 3 facing=north,shape=inner_right: appearance: facing=north,shape=inner_right - id: 4 facing=north,shape=straight: appearance: facing=north,shape=straight - id: 5 facing=south,shape=inner_left: appearance: facing=south,shape=inner_left - id: 6 facing=south,shape=inner_right: appearance: facing=south,shape=inner_right - id: 7 facing=south,shape=straight: appearance: facing=south,shape=straight - id: 8 facing=west,shape=inner_left: appearance: facing=west,shape=inner_left - id: 9 facing=west,shape=inner_right: appearance: facing=west,shape=inner_right - id: 10 facing=west,shape=straight: - appearance: facing=west,shape=straight - id: 11 \ No newline at end of file + appearance: facing=west,shape=straight \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml index 7539c0882..7f7165c56 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/table_lamp.yml @@ -1,7 +1,6 @@ items: default:table_lamp: material: nether_brick - custom-model-data: 3015 data: item-name: model: @@ -80,39 +79,30 @@ items: variants: facing=east,lit=false: appearance: east_off - id: 12 facing=north,lit=false: appearance: north_off - id: 13 facing=south,lit=false: appearance: south_off - id: 14 facing=west,lit=false: appearance: west_off - id: 15 facing=east,lit=true: appearance: east_on - id: 16 settings: luminance: 15 facing=north,lit=true: appearance: north_on - id: 17 settings: luminance: 15 facing=south,lit=true: appearance: south_on - id: 18 settings: luminance: 15 facing=west,lit=true: appearance: west_on - id: 19 settings: luminance: 15 default:table_lamp_on: material: nether_brick - custom-model-data: 3016 model: type: minecraft:model path: minecraft:item/custom/table_lamp_on diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index 067cc0a09..0eee914e4 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -1,7 +1,6 @@ items: default:topaz_ore: material: nether_brick - custom-model-data: 3017 data: item-name: model: @@ -14,7 +13,6 @@ items: block: default:topaz_ore default:deepslate_topaz_ore: material: nether_brick - custom-model-data: 3018 data: item-name: model: @@ -27,7 +25,6 @@ items: block: default:deepslate_topaz_ore default:topaz: material: nether_brick - custom-model-data: 3019 settings: anvil-repair-item: - target: @@ -53,7 +50,6 @@ blocks: arguments: break_power: 2 state: - id: 13 state: note_block:13 model: template: default:model/simplified_cube_all @@ -72,7 +68,6 @@ blocks: arguments: break_power: 2 state: - id: 14 state: note_block:14 model: template: default:model/simplified_cube_all diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml b/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml index e5eb93aa2..16bab5968 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml @@ -1,7 +1,6 @@ items: default:bench: material: nether_brick - custom-model-data: 2000 data: item-name: model: diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml b/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml index 2a008d707..fff66d0d4 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml @@ -1,7 +1,6 @@ items: default:flower_basket: material: nether_brick - custom-model-data: 2001 data: item-name: model: @@ -13,19 +12,16 @@ items: furniture: default:flower_basket default:flower_basket_ground: material: nether_brick - custom-model-data: 2002 model: type: minecraft:model path: minecraft:item/custom/flower_basket_ground default:flower_basket_wall: material: nether_brick - custom-model-data: 2003 model: type: minecraft:model path: minecraft:item/custom/flower_basket_wall default:flower_basket_ceiling: material: nether_brick - custom-model-data: 2004 model: type: minecraft:model path: minecraft:item/custom/flower_basket_ceiling diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml b/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml index f835379c3..ef848f6fc 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml @@ -1,7 +1,6 @@ items: default:wooden_chair: material: nether_brick - custom-model-data: 2005 data: item-name: model: diff --git a/common-files/src/main/resources/resources/default/configuration/items/cap.yml b/common-files/src/main/resources/resources/default/configuration/items/cap.yml index ab797c99a..f682b7cc5 100644 --- a/common-files/src/main/resources/resources/default/configuration/items/cap.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/cap.yml @@ -2,7 +2,6 @@ items: default:cap: material: leather_helmet client-bound-material: leather_horse_armor - custom-model-data: 1000 data: item-name: unbreakable: true diff --git a/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml b/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml index 6ec20e315..43425ae92 100644 --- a/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/flame_elytra.yml @@ -2,7 +2,6 @@ items: $$>=1.21.2#flame_elytra: default:flame_elytra: material: elytra - custom-model-data: 1000 settings: equippable: slot: chest diff --git a/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml b/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml index 9a6b67a8b..1c13ffa1e 100644 --- a/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/gui_head.yml @@ -1,8 +1,6 @@ items: default:gui_head_size_1: material: player_head - custom-model-data: 1000 - item-model: default:gui_head_size_1 model: type: minecraft:special path: minecraft:item/custom/gui_head_size_1 @@ -17,8 +15,6 @@ items: type: minecraft:player_head default:gui_head_size_4: material: player_head - custom-model-data: 1001 - item-model: default:gui_head_size_4 model: type: minecraft:special path: minecraft:item/custom/gui_head_size_4 diff --git a/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml b/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml index bde573c46..fcbaf8c86 100644 --- a/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/topaz_armor.yml @@ -1,7 +1,6 @@ templates: default:armor/topaz: material: chainmail_${part} - custom-model-data: 1000 data: item-name: <#FF8C00> tooltip-style: minecraft:topaz diff --git a/common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml b/common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml index 5102ab3f6..3e2114151 100644 --- a/common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml +++ b/common-files/src/main/resources/resources/default/configuration/items/topaz_tool_weapon.yml @@ -1,7 +1,6 @@ items: default:topaz_rod: material: fishing_rod - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -15,7 +14,6 @@ items: cast_path: minecraft:item/custom/topaz_rod_cast default:topaz_bow: material: bow - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -31,7 +29,6 @@ items: pulling_2_path: minecraft:item/custom/topaz_bow_pulling_2 default:topaz_crossbow: material: crossbow - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -49,7 +46,6 @@ items: firework_path: minecraft:item/custom/topaz_crossbow_firework default:topaz_pickaxe: material: golden_pickaxe - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -64,7 +60,6 @@ items: path: minecraft:item/custom/topaz_pickaxe default:topaz_axe: material: golden_axe - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -79,7 +74,6 @@ items: path: minecraft:item/custom/topaz_axe default:topaz_hoe: material: golden_hoe - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -94,7 +88,6 @@ items: path: minecraft:item/custom/topaz_hoe default:topaz_shovel: material: golden_shovel - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -109,7 +102,6 @@ items: path: minecraft:item/custom/topaz_shovel default:topaz_sword: material: golden_sword - custom-model-data: 1000 settings: tags: - default:topaz_tools @@ -125,7 +117,6 @@ items: $$>=1.21.4#topaz_trident: default:topaz_trident: material: trident - custom-model-data: 1000 settings: projectile: item: default:topaz_trident @@ -176,7 +167,6 @@ items: client-bound-material: $$1.20.1~1.21.1: bow $$1.21.2~1.21.3: honey_bottle - custom-model-data: 1001 data: item-name: <#FF8C00> components: diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index fa284b068..4403a7a15 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -862,7 +862,7 @@ templates#block_states: default: y appearances: axisY: - state: ${base_block}:${vanilla_id} + state: ${base_block} model: path: ${model_vertical_path} generation: @@ -871,7 +871,7 @@ templates#block_states: end: ${texture_top_path} side: ${texture_side_path} axisX: - state: ${base_block}:${vanilla_id} + state: ${base_block} model: x: 90 y: 90 @@ -882,7 +882,7 @@ templates#block_states: end: ${texture_top_path} side: ${texture_side_path} axisZ: - state: ${base_block}:${vanilla_id} + state: ${base_block} model: x: 90 path: ${model_horizontal_path} @@ -894,13 +894,10 @@ templates#block_states: variants: axis=x: appearance: axisX - id: ${internal_id} axis=y: appearance: axisY - id: ${internal_id} axis=z: appearance: axisZ - id: ${internal_id} # leaves block default:block_state/leaves: properties: @@ -930,93 +927,72 @@ templates#block_states: variants: distance=1,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} distance=2,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} distance=3,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} distance=4,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} distance=5,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} distance=6,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} distance=7,persistent=false,waterlogged=false: appearance: default - id: ${internal_id} settings: is-randomly-ticking: true distance=1,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=2,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=3,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=4,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=5,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=6,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=7,persistent=true,waterlogged=false: appearance: default - id: ${internal_id} distance=1,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=2,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=3,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=4,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=5,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=6,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=7,persistent=false,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false @@ -1024,49 +1000,42 @@ templates#block_states: fluid-state: water distance=1,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=2,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=3,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=4,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=5,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=6,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water distance=7,persistent=true,waterlogged=true: appearance: waterlogged - id: ${internal_id} settings: resistance: 1200.0 burnable: false @@ -1256,318 +1225,254 @@ templates#block_states: variants: facing=east,half=bottom,open=false,powered=false,waterlogged=false: appearance: facing=east,half=bottom,open=false,waterlogged=false - id: 0 facing=east,half=bottom,open=false,powered=true,waterlogged=false: appearance: facing=east,half=bottom,open=false,waterlogged=false - id: 1 facing=east,half=bottom,open=true,powered=false,waterlogged=false: appearance: facing=east,half=bottom,open=true,waterlogged=false - id: 2 facing=east,half=bottom,open=true,powered=true,waterlogged=false: appearance: facing=east,half=bottom,open=true,waterlogged=false - id: 3 facing=east,half=top,open=false,powered=false,waterlogged=false: appearance: facing=east,half=top,open=false,waterlogged=false - id: 4 facing=east,half=top,open=false,powered=true,waterlogged=false: appearance: facing=east,half=top,open=false,waterlogged=false - id: 5 facing=east,half=top,open=true,powered=false,waterlogged=false: appearance: facing=east,half=top,open=true,waterlogged=false - id: 6 facing=east,half=top,open=true,powered=true,waterlogged=false: appearance: facing=east,half=top,open=true,waterlogged=false - id: 7 facing=north,half=bottom,open=false,powered=false,waterlogged=false: appearance: facing=north,half=bottom,open=false,waterlogged=false - id: 8 facing=north,half=bottom,open=false,powered=true,waterlogged=false: appearance: facing=north,half=bottom,open=false,waterlogged=false - id: 9 facing=north,half=bottom,open=true,powered=false,waterlogged=false: appearance: facing=north,half=bottom,open=true,waterlogged=false - id: 10 facing=north,half=bottom,open=true,powered=true,waterlogged=false: appearance: facing=north,half=bottom,open=true,waterlogged=false - id: 11 facing=north,half=top,open=false,powered=false,waterlogged=false: appearance: facing=north,half=top,open=false,waterlogged=false - id: 12 facing=north,half=top,open=false,powered=true,waterlogged=false: appearance: facing=north,half=top,open=false,waterlogged=false - id: 13 facing=north,half=top,open=true,powered=false,waterlogged=false: appearance: facing=north,half=top,open=true,waterlogged=false - id: 14 facing=north,half=top,open=true,powered=true,waterlogged=false: appearance: facing=north,half=top,open=true,waterlogged=false - id: 15 facing=south,half=bottom,open=false,powered=false,waterlogged=false: appearance: facing=south,half=bottom,open=false,waterlogged=false - id: 16 facing=south,half=bottom,open=false,powered=true,waterlogged=false: appearance: facing=south,half=bottom,open=false,waterlogged=false - id: 17 facing=south,half=bottom,open=true,powered=false,waterlogged=false: appearance: facing=south,half=bottom,open=true,waterlogged=false - id: 18 facing=south,half=bottom,open=true,powered=true,waterlogged=false: appearance: facing=south,half=bottom,open=true,waterlogged=false - id: 19 facing=south,half=top,open=false,powered=false,waterlogged=false: appearance: facing=south,half=top,open=false,waterlogged=false - id: 20 facing=south,half=top,open=false,powered=true,waterlogged=false: appearance: facing=south,half=top,open=false,waterlogged=false - id: 21 facing=south,half=top,open=true,powered=false,waterlogged=false: appearance: facing=south,half=top,open=true,waterlogged=false - id: 22 facing=south,half=top,open=true,powered=true,waterlogged=false: appearance: facing=south,half=top,open=true,waterlogged=false - id: 23 facing=west,half=bottom,open=false,powered=false,waterlogged=false: appearance: facing=west,half=bottom,open=false,waterlogged=false - id: 24 facing=west,half=bottom,open=false,powered=true,waterlogged=false: appearance: facing=west,half=bottom,open=false,waterlogged=false - id: 25 facing=west,half=bottom,open=true,powered=false,waterlogged=false: appearance: facing=west,half=bottom,open=true,waterlogged=false - id: 26 facing=west,half=bottom,open=true,powered=true,waterlogged=false: appearance: facing=west,half=bottom,open=true,waterlogged=false - id: 27 facing=west,half=top,open=false,powered=false,waterlogged=false: appearance: facing=west,half=top,open=false,waterlogged=false - id: 28 facing=west,half=top,open=false,powered=true,waterlogged=false: appearance: facing=west,half=top,open=false,waterlogged=false - id: 29 facing=west,half=top,open=true,powered=false,waterlogged=false: appearance: facing=west,half=top,open=true,waterlogged=false - id: 30 facing=west,half=top,open=true,powered=true,waterlogged=false: appearance: facing=west,half=top,open=true,waterlogged=false - id: 31 facing=east,half=bottom,open=false,powered=false,waterlogged=true: appearance: facing=east,half=bottom,open=false,waterlogged=true - id: 32 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,open=false,powered=true,waterlogged=true: appearance: facing=east,half=bottom,open=false,waterlogged=true - id: 33 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,open=true,powered=false,waterlogged=true: appearance: facing=east,half=bottom,open=true,waterlogged=true - id: 34 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,open=true,powered=true,waterlogged=true: appearance: facing=east,half=bottom,open=true,waterlogged=true - id: 35 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,open=false,powered=false,waterlogged=true: appearance: facing=east,half=top,open=false,waterlogged=true - id: 36 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,open=false,powered=true,waterlogged=true: appearance: facing=east,half=top,open=false,waterlogged=true - id: 37 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,open=true,powered=false,waterlogged=true: appearance: facing=east,half=top,open=true,waterlogged=true - id: 38 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,open=true,powered=true,waterlogged=true: appearance: facing=east,half=top,open=true,waterlogged=true - id: 39 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,open=false,powered=false,waterlogged=true: appearance: facing=north,half=bottom,open=false,waterlogged=true - id: 40 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,open=false,powered=true,waterlogged=true: appearance: facing=north,half=bottom,open=false,waterlogged=true - id: 41 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,open=true,powered=false,waterlogged=true: appearance: facing=north,half=bottom,open=true,waterlogged=true - id: 42 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,open=true,powered=true,waterlogged=true: appearance: facing=north,half=bottom,open=true,waterlogged=true - id: 43 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,open=false,powered=false,waterlogged=true: appearance: facing=north,half=top,open=false,waterlogged=true - id: 44 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,open=false,powered=true,waterlogged=true: appearance: facing=north,half=top,open=false,waterlogged=true - id: 45 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,open=true,powered=false,waterlogged=true: appearance: facing=north,half=top,open=true,waterlogged=true - id: 46 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,open=true,powered=true,waterlogged=true: appearance: facing=north,half=top,open=true,waterlogged=true - id: 47 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,open=false,powered=false,waterlogged=true: appearance: facing=south,half=bottom,open=false,waterlogged=true - id: 48 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,open=false,powered=true,waterlogged=true: appearance: facing=south,half=bottom,open=false,waterlogged=true - id: 49 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,open=true,powered=false,waterlogged=true: appearance: facing=south,half=bottom,open=true,waterlogged=true - id: 50 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,open=true,powered=true,waterlogged=true: appearance: facing=south,half=bottom,open=true,waterlogged=true - id: 51 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,open=false,powered=false,waterlogged=true: appearance: facing=south,half=top,open=false,waterlogged=true - id: 52 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,open=false,powered=true,waterlogged=true: appearance: facing=south,half=top,open=false,waterlogged=true - id: 53 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,open=true,powered=false,waterlogged=true: appearance: facing=south,half=top,open=true,waterlogged=true - id: 54 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,open=true,powered=true,waterlogged=true: appearance: facing=south,half=top,open=true,waterlogged=true - id: 55 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,open=false,powered=false,waterlogged=true: appearance: facing=west,half=bottom,open=false,waterlogged=true - id: 56 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,open=false,powered=true,waterlogged=true: appearance: facing=west,half=bottom,open=false,waterlogged=true - id: 57 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,open=true,powered=false,waterlogged=true: appearance: facing=west,half=bottom,open=true,waterlogged=true - id: 58 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,open=true,powered=true,waterlogged=true: appearance: facing=west,half=bottom,open=true,waterlogged=true - id: 59 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,open=false,powered=false,waterlogged=true: appearance: facing=west,half=top,open=false,waterlogged=true - id: 60 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,open=false,powered=true,waterlogged=true: appearance: facing=west,half=top,open=false,waterlogged=true - id: 61 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,open=true,powered=false,waterlogged=true: appearance: facing=west,half=top,open=true,waterlogged=true - id: 62 settings: fluid-state: water facing=west,half=top,open=true,powered=true,waterlogged=true: appearance: facing=west,half=top,open=true,waterlogged=true - id: 63 settings: resistance: 1200.0 burnable: false @@ -1751,196 +1656,132 @@ templates#block_states: variants: facing=east,half=lower,hinge=left,open=false,powered=true: appearance: facing=east,half=lower,hinge=left,open=false - id: 0 facing=east,half=lower,hinge=left,open=false,powered=false: appearance: facing=east,half=lower,hinge=left,open=false - id: 1 facing=east,half=lower,hinge=right,open=false,powered=true: appearance: facing=east,half=lower,hinge=right,open=false - id: 2 facing=east,half=lower,hinge=right,open=false,powered=false: appearance: facing=east,half=lower,hinge=right,open=false - id: 3 facing=east,half=upper,hinge=left,open=false,powered=true: appearance: facing=east,half=upper,hinge=left,open=false - id: 4 facing=east,half=upper,hinge=left,open=false,powered=false: appearance: facing=east,half=upper,hinge=left,open=false - id: 5 facing=east,half=upper,hinge=right,open=false,powered=true: appearance: facing=east,half=upper,hinge=right,open=false - id: 6 facing=east,half=upper,hinge=right,open=false,powered=false: appearance: facing=east,half=upper,hinge=right,open=false - id: 7 facing=north,half=lower,hinge=left,open=false,powered=true: appearance: facing=north,half=lower,hinge=left,open=false - id: 8 facing=north,half=lower,hinge=left,open=false,powered=false: appearance: facing=north,half=lower,hinge=left,open=false - id: 9 facing=north,half=lower,hinge=right,open=false,powered=true: appearance: facing=north,half=lower,hinge=right,open=false - id: 10 facing=north,half=lower,hinge=right,open=false,powered=false: appearance: facing=north,half=lower,hinge=right,open=false - id: 11 facing=north,half=upper,hinge=left,open=false,powered=true: appearance: facing=north,half=upper,hinge=left,open=false - id: 12 facing=north,half=upper,hinge=left,open=false,powered=false: appearance: facing=north,half=upper,hinge=left,open=false - id: 13 facing=north,half=upper,hinge=right,open=false,powered=true: appearance: facing=north,half=upper,hinge=right,open=false - id: 14 facing=north,half=upper,hinge=right,open=false,powered=false: appearance: facing=north,half=upper,hinge=right,open=false - id: 15 facing=south,half=lower,hinge=left,open=false,powered=true: appearance: facing=south,half=lower,hinge=left,open=false - id: 16 facing=south,half=lower,hinge=left,open=false,powered=false: appearance: facing=south,half=lower,hinge=left,open=false - id: 17 facing=south,half=lower,hinge=right,open=false,powered=true: appearance: facing=south,half=lower,hinge=right,open=false - id: 18 facing=south,half=lower,hinge=right,open=false,powered=false: appearance: facing=south,half=lower,hinge=right,open=false - id: 19 facing=south,half=upper,hinge=left,open=false,powered=true: appearance: facing=south,half=upper,hinge=left,open=false - id: 20 facing=south,half=upper,hinge=left,open=false,powered=false: appearance: facing=south,half=upper,hinge=left,open=false - id: 21 facing=south,half=upper,hinge=right,open=false,powered=true: appearance: facing=south,half=upper,hinge=right,open=false - id: 22 facing=south,half=upper,hinge=right,open=false,powered=false: appearance: facing=south,half=upper,hinge=right,open=false - id: 23 facing=west,half=lower,hinge=left,open=false,powered=true: appearance: facing=west,half=lower,hinge=left,open=false - id: 24 facing=west,half=lower,hinge=left,open=false,powered=false: appearance: facing=west,half=lower,hinge=left,open=false - id: 25 facing=west,half=lower,hinge=right,open=false,powered=true: appearance: facing=west,half=lower,hinge=right,open=false - id: 26 facing=west,half=lower,hinge=right,open=false,powered=false: appearance: facing=west,half=lower,hinge=right,open=false - id: 27 facing=west,half=upper,hinge=left,open=false,powered=true: appearance: facing=west,half=upper,hinge=left,open=false - id: 28 facing=west,half=upper,hinge=left,open=false,powered=false: appearance: facing=west,half=upper,hinge=left,open=false - id: 29 facing=west,half=upper,hinge=right,open=false,powered=true: appearance: facing=west,half=upper,hinge=right,open=false - id: 30 facing=west,half=upper,hinge=right,open=false,powered=false: appearance: facing=west,half=upper,hinge=right,open=false - id: 31 facing=east,half=lower,hinge=left,open=true,powered=true: appearance: facing=east,half=lower,hinge=left,open=true - id: 32 facing=east,half=lower,hinge=left,open=true,powered=false: appearance: facing=east,half=lower,hinge=left,open=true - id: 33 facing=east,half=lower,hinge=right,open=true,powered=true: appearance: facing=east,half=lower,hinge=right,open=true - id: 34 facing=east,half=lower,hinge=right,open=true,powered=false: appearance: facing=east,half=lower,hinge=right,open=true - id: 35 facing=east,half=upper,hinge=left,open=true,powered=true: appearance: facing=east,half=upper,hinge=left,open=true - id: 36 facing=east,half=upper,hinge=left,open=true,powered=false: appearance: facing=east,half=upper,hinge=left,open=true - id: 37 facing=east,half=upper,hinge=right,open=true,powered=true: appearance: facing=east,half=upper,hinge=right,open=true - id: 38 facing=east,half=upper,hinge=right,open=true,powered=false: appearance: facing=east,half=upper,hinge=right,open=true - id: 39 facing=north,half=lower,hinge=left,open=true,powered=true: appearance: facing=north,half=lower,hinge=left,open=true - id: 40 facing=north,half=lower,hinge=left,open=true,powered=false: appearance: facing=north,half=lower,hinge=left,open=true - id: 41 facing=north,half=lower,hinge=right,open=true,powered=true: appearance: facing=north,half=lower,hinge=right,open=true - id: 42 facing=north,half=lower,hinge=right,open=true,powered=false: appearance: facing=north,half=lower,hinge=right,open=true - id: 43 facing=north,half=upper,hinge=left,open=true,powered=true: appearance: facing=north,half=upper,hinge=left,open=true - id: 44 facing=north,half=upper,hinge=left,open=true,powered=false: appearance: facing=north,half=upper,hinge=left,open=true - id: 45 facing=north,half=upper,hinge=right,open=true,powered=true: appearance: facing=north,half=upper,hinge=right,open=true - id: 46 facing=north,half=upper,hinge=right,open=true,powered=false: appearance: facing=north,half=upper,hinge=right,open=true - id: 47 facing=south,half=lower,hinge=left,open=true,powered=true: appearance: facing=south,half=lower,hinge=left,open=true - id: 48 facing=south,half=lower,hinge=left,open=true,powered=false: appearance: facing=south,half=lower,hinge=left,open=true - id: 49 facing=south,half=lower,hinge=right,open=true,powered=true: appearance: facing=south,half=lower,hinge=right,open=true - id: 50 facing=south,half=lower,hinge=right,open=true,powered=false: appearance: facing=south,half=lower,hinge=right,open=true - id: 51 facing=south,half=upper,hinge=left,open=true,powered=true: appearance: facing=south,half=upper,hinge=left,open=true - id: 52 facing=south,half=upper,hinge=left,open=true,powered=false: appearance: facing=south,half=upper,hinge=left,open=true - id: 53 facing=south,half=upper,hinge=right,open=true,powered=true: appearance: facing=south,half=upper,hinge=right,open=true - id: 54 facing=south,half=upper,hinge=right,open=true,powered=false: appearance: facing=south,half=upper,hinge=right,open=true - id: 55 facing=west,half=lower,hinge=left,open=true,powered=true: appearance: facing=west,half=lower,hinge=left,open=true - id: 56 facing=west,half=lower,hinge=left,open=true,powered=false: appearance: facing=west,half=lower,hinge=left,open=true - id: 57 facing=west,half=lower,hinge=right,open=true,powered=true: appearance: facing=west,half=lower,hinge=right,open=true - id: 58 facing=west,half=lower,hinge=right,open=true,powered=false: appearance: facing=west,half=lower,hinge=right,open=true - id: 59 facing=west,half=upper,hinge=left,open=true,powered=true: appearance: facing=west,half=upper,hinge=left,open=true - id: 60 facing=west,half=upper,hinge=left,open=true,powered=false: appearance: facing=west,half=upper,hinge=left,open=true - id: 61 facing=west,half=upper,hinge=right,open=true,powered=true: appearance: facing=west,half=upper,hinge=right,open=true - id: 62 facing=west,half=upper,hinge=right,open=true,powered=false: appearance: facing=west,half=upper,hinge=right,open=true - id: 63 # fence gate block default:block_state/fence_gate: properties: @@ -2055,100 +1896,68 @@ templates#block_states: variants: facing=east,in_wall=false,open=false,powered=true: appearance: facing=east,in_wall=false,open=false - id: 0 facing=east,in_wall=false,open=false,powered=false: appearance: facing=east,in_wall=false,open=false - id: 1 facing=east,in_wall=false,open=true,powered=true: appearance: facing=east,in_wall=false,open=true - id: 2 facing=east,in_wall=false,open=true,powered=false: appearance: facing=east,in_wall=false,open=true - id: 3 facing=east,in_wall=true,open=false,powered=true: appearance: facing=east,in_wall=true,open=false - id: 4 facing=east,in_wall=true,open=false,powered=false: appearance: facing=east,in_wall=true,open=false - id: 5 facing=east,in_wall=true,open=true,powered=true: appearance: facing=east,in_wall=true,open=true - id: 6 facing=east,in_wall=true,open=true,powered=false: appearance: facing=east,in_wall=true,open=true - id: 7 facing=south,in_wall=false,open=false,powered=true: appearance: facing=south,in_wall=false,open=false - id: 8 facing=south,in_wall=false,open=false,powered=false: appearance: facing=south,in_wall=false,open=false - id: 9 facing=south,in_wall=false,open=true,powered=true: appearance: facing=south,in_wall=false,open=true - id: 10 facing=south,in_wall=false,open=true,powered=false: appearance: facing=south,in_wall=false,open=true - id: 11 facing=south,in_wall=true,open=false,powered=true: appearance: facing=south,in_wall=true,open=false - id: 12 facing=south,in_wall=true,open=false,powered=false: appearance: facing=south,in_wall=true,open=false - id: 13 facing=south,in_wall=true,open=true,powered=true: appearance: facing=south,in_wall=true,open=true - id: 14 facing=south,in_wall=true,open=true,powered=false: appearance: facing=south,in_wall=true,open=true - id: 15 facing=west,in_wall=false,open=false,powered=true: appearance: facing=west,in_wall=false,open=false - id: 16 facing=west,in_wall=false,open=false,powered=false: appearance: facing=west,in_wall=false,open=false - id: 17 facing=west,in_wall=false,open=true,powered=true: appearance: facing=west,in_wall=false,open=true - id: 18 facing=west,in_wall=false,open=true,powered=false: appearance: facing=west,in_wall=false,open=true - id: 19 facing=west,in_wall=true,open=false,powered=true: appearance: facing=west,in_wall=true,open=false - id: 20 facing=west,in_wall=true,open=false,powered=false: appearance: facing=west,in_wall=true,open=false - id: 21 facing=west,in_wall=true,open=true,powered=true: appearance: facing=west,in_wall=true,open=true - id: 22 facing=west,in_wall=true,open=true,powered=false: appearance: facing=west,in_wall=true,open=true - id: 23 facing=north,in_wall=false,open=false,powered=true: appearance: facing=north,in_wall=false,open=false - id: 24 facing=north,in_wall=false,open=false,powered=false: appearance: facing=north,in_wall=false,open=false - id: 25 facing=north,in_wall=false,open=true,powered=true: appearance: facing=north,in_wall=false,open=true - id: 26 facing=north,in_wall=false,open=true,powered=false: appearance: facing=north,in_wall=false,open=true - id: 27 facing=north,in_wall=true,open=false,powered=true: appearance: facing=north,in_wall=true,open=false - id: 28 facing=north,in_wall=true,open=false,powered=false: appearance: facing=north,in_wall=true,open=false - id: 29 facing=north,in_wall=true,open=true,powered=true: appearance: facing=north,in_wall=true,open=true - id: 30 facing=north,in_wall=true,open=true,powered=false: appearance: facing=north,in_wall=true,open=true - id: 31 # slab block default:block_state/slab: properties: @@ -2189,30 +1998,24 @@ templates#block_states: variants: type=top,waterlogged=false: appearance: type=top,waterlogged=false - id: 0 type=bottom,waterlogged=false: appearance: type=bottom,waterlogged=false - id: 1 type=double,waterlogged=false: appearance: type=double,waterlogged=false - id: 2 type=top,waterlogged=true: appearance: type=top,waterlogged=true - id: 3 settings: resistance: 1200.0 burnable: false fluid-state: water type=bottom,waterlogged=true: appearance: type=bottom,waterlogged=true - id: 4 settings: resistance: 1200.0 burnable: false fluid-state: water type=double,waterlogged=true: appearance: type=double,waterlogged=true - id: 5 settings: resistance: 1200.0 burnable: false @@ -2726,400 +2529,320 @@ templates#block_states: variants: facing=east,half=bottom,shape=inner_left,waterlogged=false: appearance: facing=east,half=bottom,shape=inner_left,waterlogged=false - id: 0 facing=east,half=bottom,shape=inner_right,waterlogged=false: appearance: facing=east,half=bottom,shape=inner_right,waterlogged=false - id: 1 facing=east,half=bottom,shape=outer_left,waterlogged=false: appearance: facing=east,half=bottom,shape=outer_left,waterlogged=false - id: 2 facing=east,half=bottom,shape=outer_right,waterlogged=false: appearance: facing=east,half=bottom,shape=outer_right,waterlogged=false - id: 3 facing=east,half=bottom,shape=straight,waterlogged=false: appearance: facing=east,half=bottom,shape=straight,waterlogged=false - id: 4 facing=east,half=top,shape=inner_left,waterlogged=false: appearance: facing=east,half=top,shape=inner_left,waterlogged=false - id: 5 facing=east,half=top,shape=inner_right,waterlogged=false: appearance: facing=east,half=top,shape=inner_right,waterlogged=false - id: 6 facing=east,half=top,shape=outer_left,waterlogged=false: appearance: facing=east,half=top,shape=outer_left,waterlogged=false - id: 7 facing=east,half=top,shape=outer_right,waterlogged=false: appearance: facing=east,half=top,shape=outer_right,waterlogged=false - id: 8 facing=east,half=top,shape=straight,waterlogged=false: appearance: facing=east,half=top,shape=straight,waterlogged=false - id: 9 facing=north,half=bottom,shape=inner_left,waterlogged=false: appearance: facing=north,half=bottom,shape=inner_left,waterlogged=false - id: 10 facing=north,half=bottom,shape=inner_right,waterlogged=false: appearance: facing=north,half=bottom,shape=inner_right,waterlogged=false - id: 11 facing=north,half=bottom,shape=outer_left,waterlogged=false: appearance: facing=north,half=bottom,shape=outer_left,waterlogged=false - id: 12 facing=north,half=bottom,shape=outer_right,waterlogged=false: appearance: facing=north,half=bottom,shape=outer_right,waterlogged=false - id: 13 facing=north,half=bottom,shape=straight,waterlogged=false: appearance: facing=north,half=bottom,shape=straight,waterlogged=false - id: 14 facing=north,half=top,shape=inner_left,waterlogged=false: appearance: facing=north,half=top,shape=inner_left,waterlogged=false - id: 15 facing=north,half=top,shape=inner_right,waterlogged=false: appearance: facing=north,half=top,shape=inner_right,waterlogged=false - id: 16 facing=north,half=top,shape=outer_left,waterlogged=false: appearance: facing=north,half=top,shape=outer_left,waterlogged=false - id: 17 facing=north,half=top,shape=outer_right,waterlogged=false: appearance: facing=north,half=top,shape=outer_right,waterlogged=false - id: 18 facing=north,half=top,shape=straight,waterlogged=false: appearance: facing=north,half=top,shape=straight,waterlogged=false - id: 19 facing=south,half=bottom,shape=inner_left,waterlogged=false: appearance: facing=south,half=bottom,shape=inner_left,waterlogged=false - id: 20 facing=south,half=bottom,shape=inner_right,waterlogged=false: appearance: facing=south,half=bottom,shape=inner_right,waterlogged=false - id: 21 facing=south,half=bottom,shape=outer_left,waterlogged=false: appearance: facing=south,half=bottom,shape=outer_left,waterlogged=false - id: 22 facing=south,half=bottom,shape=outer_right,waterlogged=false: appearance: facing=south,half=bottom,shape=outer_right,waterlogged=false - id: 23 facing=south,half=bottom,shape=straight,waterlogged=false: appearance: facing=south,half=bottom,shape=straight,waterlogged=false - id: 24 facing=south,half=top,shape=inner_left,waterlogged=false: appearance: facing=south,half=top,shape=inner_left,waterlogged=false - id: 25 facing=south,half=top,shape=inner_right,waterlogged=false: appearance: facing=south,half=top,shape=inner_right,waterlogged=false - id: 26 facing=south,half=top,shape=outer_left,waterlogged=false: appearance: facing=south,half=top,shape=outer_left,waterlogged=false - id: 27 facing=south,half=top,shape=outer_right,waterlogged=false: appearance: facing=south,half=top,shape=outer_right,waterlogged=false - id: 28 facing=south,half=top,shape=straight,waterlogged=false: appearance: facing=south,half=top,shape=straight,waterlogged=false - id: 29 facing=west,half=bottom,shape=inner_left,waterlogged=false: appearance: facing=west,half=bottom,shape=inner_left,waterlogged=false - id: 30 facing=west,half=bottom,shape=inner_right,waterlogged=false: appearance: facing=west,half=bottom,shape=inner_right,waterlogged=false - id: 31 facing=west,half=bottom,shape=outer_left,waterlogged=false: appearance: facing=west,half=bottom,shape=outer_left,waterlogged=false - id: 32 facing=west,half=bottom,shape=outer_right,waterlogged=false: appearance: facing=west,half=bottom,shape=outer_right,waterlogged=false - id: 33 facing=west,half=bottom,shape=straight,waterlogged=false: appearance: facing=west,half=bottom,shape=straight,waterlogged=false - id: 34 facing=west,half=top,shape=inner_left,waterlogged=false: appearance: facing=west,half=top,shape=inner_left,waterlogged=false - id: 35 facing=west,half=top,shape=inner_right,waterlogged=false: appearance: facing=west,half=top,shape=inner_right,waterlogged=false - id: 36 facing=west,half=top,shape=outer_left,waterlogged=false: appearance: facing=west,half=top,shape=outer_left,waterlogged=false - id: 37 facing=west,half=top,shape=outer_right,waterlogged=false: appearance: facing=west,half=top,shape=outer_right,waterlogged=false - id: 38 facing=west,half=top,shape=straight,waterlogged=false: appearance: facing=west,half=top,shape=straight,waterlogged=false - id: 39 facing=east,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=east,half=bottom,shape=inner_left,waterlogged=true - id: 40 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=east,half=bottom,shape=inner_right,waterlogged=true - id: 41 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=east,half=bottom,shape=outer_left,waterlogged=true - id: 42 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=east,half=bottom,shape=outer_right,waterlogged=true - id: 43 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=bottom,shape=straight,waterlogged=true: appearance: facing=east,half=bottom,shape=straight,waterlogged=true - id: 44 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,shape=inner_left,waterlogged=true: appearance: facing=east,half=top,shape=inner_left,waterlogged=true - id: 45 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,shape=inner_right,waterlogged=true: appearance: facing=east,half=top,shape=inner_right,waterlogged=true - id: 46 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,shape=outer_left,waterlogged=true: appearance: facing=east,half=top,shape=outer_left,waterlogged=true - id: 47 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,shape=outer_right,waterlogged=true: appearance: facing=east,half=top,shape=outer_right,waterlogged=true - id: 48 settings: resistance: 1200.0 burnable: false fluid-state: water facing=east,half=top,shape=straight,waterlogged=true: appearance: facing=east,half=top,shape=straight,waterlogged=true - id: 49 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=north,half=bottom,shape=inner_left,waterlogged=true - id: 50 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=north,half=bottom,shape=inner_right,waterlogged=true - id: 51 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=north,half=bottom,shape=outer_left,waterlogged=true - id: 52 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=north,half=bottom,shape=outer_right,waterlogged=true - id: 53 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=bottom,shape=straight,waterlogged=true: appearance: facing=north,half=bottom,shape=straight,waterlogged=true - id: 54 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,shape=inner_left,waterlogged=true: appearance: facing=north,half=top,shape=inner_left,waterlogged=true - id: 55 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,shape=inner_right,waterlogged=true: appearance: facing=north,half=top,shape=inner_right,waterlogged=true - id: 56 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,shape=outer_left,waterlogged=true: appearance: facing=north,half=top,shape=outer_left,waterlogged=true - id: 57 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,shape=outer_right,waterlogged=true: appearance: facing=north,half=top,shape=outer_right,waterlogged=true - id: 58 settings: resistance: 1200.0 burnable: false fluid-state: water facing=north,half=top,shape=straight,waterlogged=true: appearance: facing=north,half=top,shape=straight,waterlogged=true - id: 59 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=south,half=bottom,shape=inner_left,waterlogged=true - id: 60 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=south,half=bottom,shape=inner_right,waterlogged=true - id: 61 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=south,half=bottom,shape=outer_left,waterlogged=true - id: 62 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=south,half=bottom,shape=outer_right,waterlogged=true - id: 63 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=bottom,shape=straight,waterlogged=true: appearance: facing=south,half=bottom,shape=straight,waterlogged=true - id: 64 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,shape=inner_left,waterlogged=true: appearance: facing=south,half=top,shape=inner_left,waterlogged=true - id: 65 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,shape=inner_right,waterlogged=true: appearance: facing=south,half=top,shape=inner_right,waterlogged=true - id: 66 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,shape=outer_left,waterlogged=true: appearance: facing=south,half=top,shape=outer_left,waterlogged=true - id: 67 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,shape=outer_right,waterlogged=true: appearance: facing=south,half=top,shape=outer_right,waterlogged=true - id: 68 settings: resistance: 1200.0 burnable: false fluid-state: water facing=south,half=top,shape=straight,waterlogged=true: appearance: facing=south,half=top,shape=straight,waterlogged=true - id: 69 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=west,half=bottom,shape=inner_left,waterlogged=true - id: 70 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=west,half=bottom,shape=inner_right,waterlogged=true - id: 71 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=west,half=bottom,shape=outer_left,waterlogged=true - id: 72 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=west,half=bottom,shape=outer_right,waterlogged=true - id: 73 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=bottom,shape=straight,waterlogged=true: appearance: facing=west,half=bottom,shape=straight,waterlogged=true - id: 74 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,shape=inner_left,waterlogged=true: appearance: facing=west,half=top,shape=inner_left,waterlogged=true - id: 75 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,shape=inner_right,waterlogged=true: appearance: facing=west,half=top,shape=inner_right,waterlogged=true - id: 76 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,shape=outer_left,waterlogged=true: appearance: facing=west,half=top,shape=outer_left,waterlogged=true - id: 77 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,shape=outer_right,waterlogged=true: appearance: facing=west,half=top,shape=outer_right,waterlogged=true - id: 78 settings: resistance: 1200.0 burnable: false fluid-state: water facing=west,half=top,shape=straight,waterlogged=true: appearance: facing=west,half=top,shape=straight,waterlogged=true - id: 79 settings: resistance: 1200.0 burnable: false @@ -3144,10 +2867,8 @@ templates#block_states: variants: powered=false: appearance: normal - id: ${normal_id} powered=true: appearance: powered - id: ${powered_id} default:block_state/button: properties: powered: @@ -3329,76 +3050,52 @@ templates#block_states: variants: face=floor,facing=east,powered=true: appearance: face=floor,facing=east,powered=true - id: ${internal_id} face=floor,facing=west,powered=true: appearance: face=floor,facing=west,powered=true - id: ${internal_id} face=floor,facing=east,powered=false: appearance: face=floor,facing=east,powered=false - id: ${internal_id} face=floor,facing=west,powered=false: appearance: face=floor,facing=west,powered=false - id: ${internal_id} face=floor,facing=south,powered=true: appearance: face=floor,facing=south,powered=true - id: ${internal_id} face=floor,facing=north,powered=true: appearance: face=floor,facing=north,powered=true - id: ${internal_id} face=floor,facing=south,powered=false: appearance: face=floor,facing=south,powered=false - id: ${internal_id} face=floor,facing=north,powered=false: appearance: face=floor,facing=north,powered=false - id: ${internal_id} face=wall,facing=north,powered=true: appearance: face=wall,facing=north,powered=true - id: ${internal_id} face=wall,facing=south,powered=true: appearance: face=wall,facing=south,powered=true - id: ${internal_id} face=wall,facing=north,powered=false: appearance: face=wall,facing=north,powered=false - id: ${internal_id} face=wall,facing=south,powered=false: appearance: face=wall,facing=south,powered=false - id: ${internal_id} face=wall,facing=west,powered=true: appearance: face=wall,facing=west,powered=true - id: ${internal_id} face=wall,facing=east,powered=true: appearance: face=wall,facing=east,powered=true - id: ${internal_id} face=wall,facing=west,powered=false: appearance: face=wall,facing=west,powered=false - id: ${internal_id} face=wall,facing=east,powered=false: appearance: face=wall,facing=east,powered=false - id: ${internal_id} face=ceiling,facing=north,powered=true: appearance: face=ceiling,facing=north,powered=true - id: ${internal_id} face=ceiling,facing=south,powered=true: appearance: face=ceiling,facing=south,powered=true - id: ${internal_id} face=ceiling,facing=north,powered=false: appearance: face=ceiling,facing=north,powered=false - id: ${internal_id} face=ceiling,facing=south,powered=false: appearance: face=ceiling,facing=south,powered=false - id: ${internal_id} face=ceiling,facing=west,powered=true: appearance: face=ceiling,facing=west,powered=true - id: ${internal_id} face=ceiling,facing=east,powered=true: appearance: face=ceiling,facing=east,powered=true - id: ${internal_id} face=ceiling,facing=west,powered=false: appearance: face=ceiling,facing=west,powered=false - id: ${internal_id} face=ceiling,facing=east,powered=false: appearance: face=ceiling,facing=east,powered=false - id: ${internal_id} default:block_state/fence: properties: north: @@ -3772,160 +3469,128 @@ templates#block_states: variants: east=false,north=false,south=false,waterlogged=false,west=false: appearance: east=false,north=false,south=false,waterlogged=false,west=false - id: ${internal_id} east=true,north=false,south=false,waterlogged=false,west=false: appearance: east=true,north=false,south=false,waterlogged=false,west=false - id: ${internal_id} east=false,north=true,south=false,waterlogged=false,west=false: appearance: east=false,north=true,south=false,waterlogged=false,west=false - id: ${internal_id} east=false,north=false,south=true,waterlogged=false,west=false: appearance: east=false,north=false,south=true,waterlogged=false,west=false - id: ${internal_id} east=false,north=false,south=false,waterlogged=false,west=true: appearance: east=false,north=false,south=false,waterlogged=false,west=true - id: ${internal_id} east=true,north=true,south=false,waterlogged=false,west=false: appearance: east=true,north=true,south=false,waterlogged=false,west=false - id: ${internal_id} east=true,north=false,south=true,waterlogged=false,west=false: appearance: east=true,north=false,south=true,waterlogged=false,west=false - id: ${internal_id} east=true,north=false,south=false,waterlogged=false,west=true: appearance: east=true,north=false,south=false,waterlogged=false,west=true - id: ${internal_id} east=false,north=true,south=true,waterlogged=false,west=false: appearance: east=false,north=true,south=true,waterlogged=false,west=false - id: ${internal_id} east=false,north=true,south=false,waterlogged=false,west=true: appearance: east=false,north=true,south=false,waterlogged=false,west=true - id: ${internal_id} east=false,north=false,south=true,waterlogged=false,west=true: appearance: east=false,north=false,south=true,waterlogged=false,west=true - id: ${internal_id} east=true,north=true,south=true,waterlogged=false,west=false: appearance: east=true,north=true,south=true,waterlogged=false,west=false - id: ${internal_id} east=true,north=true,south=false,waterlogged=false,west=true: appearance: east=true,north=true,south=false,waterlogged=false,west=true - id: ${internal_id} east=true,north=false,south=true,waterlogged=false,west=true: appearance: east=true,north=false,south=true,waterlogged=false,west=true - id: ${internal_id} east=false,north=true,south=true,waterlogged=false,west=true: appearance: east=false,north=true,south=true,waterlogged=false,west=true - id: ${internal_id} east=true,north=true,south=true,waterlogged=false,west=true: appearance: east=true,north=true,south=true,waterlogged=false,west=true - id: ${internal_id} east=false,north=false,south=false,waterlogged=true,west=false: appearance: east=false,north=false,south=false,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=false,south=false,waterlogged=true,west=false: appearance: east=true,north=false,south=false,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=true,south=false,waterlogged=true,west=false: appearance: east=false,north=true,south=false,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=false,south=true,waterlogged=true,west=false: appearance: east=false,north=false,south=true,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=false,south=false,waterlogged=true,west=true: appearance: east=false,north=false,south=false,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=true,south=false,waterlogged=true,west=false: appearance: east=true,north=true,south=false,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=false,south=true,waterlogged=true,west=false: appearance: east=true,north=false,south=true,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=false,south=false,waterlogged=true,west=true: appearance: east=true,north=false,south=false,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=true,south=true,waterlogged=true,west=false: appearance: east=false,north=true,south=true,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=true,south=false,waterlogged=true,west=true: appearance: east=false,north=true,south=false,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=false,south=true,waterlogged=true,west=true: appearance: east=false,north=false,south=true,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=true,south=true,waterlogged=true,west=false: appearance: east=true,north=true,south=true,waterlogged=true,west=false - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=true,south=false,waterlogged=true,west=true: appearance: east=true,north=true,south=false,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=false,south=true,waterlogged=true,west=true: appearance: east=true,north=false,south=true,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=false,north=true,south=true,waterlogged=true,west=true: appearance: east=false,north=true,south=true,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false fluid-state: water east=true,north=true,south=true,waterlogged=true,west=true: appearance: east=true,north=true,south=true,waterlogged=true,west=true - id: ${internal_id} settings: resistance: 1200.0 burnable: false diff --git a/common-files/src/main/resources/resources/internal/configuration/gui.yml b/common-files/src/main/resources/resources/internal/configuration/gui.yml index 344d37d55..42c53dee6 100644 --- a/common-files/src/main/resources/resources/internal/configuration/gui.yml +++ b/common-files/src/main/resources/resources/internal/configuration/gui.yml @@ -74,7 +74,6 @@ images: templates: internal:icon/2d: material: arrow - custom-model-data: ${model_data} data: item-name: ${name} lore: ${lore} @@ -93,7 +92,6 @@ items: internal:next_page_0: template: internal:icon/2d arguments: - model_data: 1000 texture: next_page_0 name: <#FAFAD2> lore: @@ -101,7 +99,6 @@ items: internal:next_page_1: template: internal:icon/2d arguments: - model_data: 1001 texture: next_page_1 name: <#808080> lore: @@ -109,7 +106,6 @@ items: internal:previous_page_0: template: internal:icon/2d arguments: - model_data: 1002 texture: previous_page_0 name: <#FAFAD2> lore: @@ -117,7 +113,6 @@ items: internal:previous_page_1: template: internal:icon/2d arguments: - model_data: 1003 texture: previous_page_1 name: <#808080> lore: @@ -125,34 +120,29 @@ items: internal:return: template: internal:icon/2d arguments: - model_data: 1004 texture: return name: <#DAA520> lore: null internal:next_recipe_0: material: arrow - custom-model-data: 1000 data: item-name: <#FAFAD2> lore: - <#F5F5F5>/ internal:next_recipe_1: material: arrow - custom-model-data: 1001 data: item-name: <#808080> lore: - <#696969>/ internal:previous_recipe_0: material: arrow - custom-model-data: 1002 data: item-name: <#FAFAD2> lore: - <#F5F5F5>/ internal:previous_recipe_1: material: arrow - custom-model-data: 1003 data: item-name: <#808080> lore: @@ -160,7 +150,6 @@ items: internal:get_item: template: internal:icon/2d arguments: - model_data: 1005 texture: get_item name: <#DAA520> lore: @@ -169,7 +158,6 @@ items: internal:cooking_info: template: internal:icon/2d arguments: - model_data: 1006 texture: cooking_info name: <#FF8C00> lore: @@ -178,7 +166,6 @@ items: internal:exit: template: internal:icon/2d arguments: - model_data: 1007 texture: exit name: <#DAA520> lore: null \ No newline at end of file diff --git a/common-files/src/main/resources/resources/internal/configuration/mappings.yml b/common-files/src/main/resources/resources/internal/configuration/mappings.yml new file mode 100644 index 000000000..c113c0d3b --- /dev/null +++ b/common-files/src/main/resources/resources/internal/configuration/mappings.yml @@ -0,0 +1,4451 @@ +# This is one of the plugin's core settings - it basically controls how many block states you can use in your config files. +# Below is the default setup - feel free to add or remove stuff as needed. +block-state-mappings: + #### Anvil #### + # An anvil has four possible orientations, but the east-west and north-south orientations look exactly the same. + minecraft:anvil[facing=north]: minecraft:anvil[facing=south] + minecraft:anvil[facing=east]: minecraft:anvil[facing=west] + minecraft:chipped_anvil[facing=north]: minecraft:chipped_anvil[facing=south] + minecraft:chipped_anvil[facing=east]: minecraft:chipped_anvil[facing=west] + minecraft:damaged_anvil[facing=north]: minecraft:damaged_anvil[facing=south] + minecraft:damaged_anvil[facing=east]: minecraft:damaged_anvil[facing=west] + + #### Sapling #### + # Every sapling has two stages, 0 and 1, but they look exactly the same. + minecraft:oak_sapling[stage=1]: minecraft:oak_sapling[stage=0] + minecraft:birch_sapling[stage=1]: minecraft:birch_sapling[stage=0] + minecraft:spruce_sapling[stage=1]: minecraft:spruce_sapling[stage=0] + minecraft:jungle_sapling[stage=1]: minecraft:jungle_sapling[stage=0] + minecraft:dark_oak_sapling[stage=1]: minecraft:dark_oak_sapling[stage=0] + minecraft:acacia_sapling[stage=1]: minecraft:acacia_sapling[stage=0] + minecraft:cherry_sapling[stage=1]: minecraft:cherry_sapling[stage=0] + $$>=1.21.4#sapling: + minecraft:pale_oak_sapling[stage=1]: minecraft:pale_oak_sapling[stage=0] + + #### Sculk Sensor #### + # The Sculk Sensor's hitbox is exactly half a block. Plus, its appearance only changes based on the sculk_sensor_phase, + # not the power level. That means we can repurpose the extra states to make bottom-half slabs + minecraft:sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:sculk_sensor[power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:sculk_sensor[power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:sculk_sensor[power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:sculk_sensor[power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:sculk_sensor[power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:sculk_sensor[power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:sculk_sensor[power=0,sculk_sensor_phase=cooldown,waterlogged=true] + + #### Calibrated Sculk Sensor #### + # Just like the regular Sculk Sensor, but the Calibrated Sculk Sensor has directional facing - which gives us way more usable states to play with + minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=north,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=north,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=west,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=west,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=east,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=east,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=inactive,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=active,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=cooldown,waterlogged=false]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=false] + minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=inactive,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=inactive,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=active,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=active,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=1,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=2,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=3,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=4,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=5,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=6,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=7,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=8,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=9,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=10,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=11,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=12,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=13,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=14,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + minecraft:calibrated_sculk_sensor[facing=south,power=15,sculk_sensor_phase=cooldown,waterlogged=true]: minecraft:calibrated_sculk_sensor[facing=south,power=0,sculk_sensor_phase=cooldown,waterlogged=true] + + #### Mushroom #### + # Most people probably don't mind that mushroom blocks look the same on all six sides. So that means each type can free up like, 63 different states we could use for other stuff + minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:mushroom_stem[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:mushroom_stem[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:brown_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:brown_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + minecraft:red_mushroom_block[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:red_mushroom_block[down=true,east=true,north=true,south=true,up=true,west=true] + + #### Kelp #### + # 'kelp' here means specifically the top block of the kelp plant. Great for making aquatic crops. + minecraft:kelp[age=1]: minecraft:kelp[age=0] + minecraft:kelp[age=2]: minecraft:kelp[age=0] + minecraft:kelp[age=3]: minecraft:kelp[age=0] + minecraft:kelp[age=4]: minecraft:kelp[age=0] + minecraft:kelp[age=5]: minecraft:kelp[age=0] + minecraft:kelp[age=6]: minecraft:kelp[age=0] + minecraft:kelp[age=7]: minecraft:kelp[age=0] + minecraft:kelp[age=8]: minecraft:kelp[age=0] + minecraft:kelp[age=9]: minecraft:kelp[age=0] + minecraft:kelp[age=10]: minecraft:kelp[age=0] + minecraft:kelp[age=11]: minecraft:kelp[age=0] + minecraft:kelp[age=12]: minecraft:kelp[age=0] + minecraft:kelp[age=13]: minecraft:kelp[age=0] + minecraft:kelp[age=14]: minecraft:kelp[age=0] + minecraft:kelp[age=15]: minecraft:kelp[age=0] + minecraft:kelp[age=16]: minecraft:kelp[age=0] + minecraft:kelp[age=17]: minecraft:kelp[age=0] + minecraft:kelp[age=18]: minecraft:kelp[age=0] + minecraft:kelp[age=19]: minecraft:kelp[age=0] + minecraft:kelp[age=20]: minecraft:kelp[age=0] + minecraft:kelp[age=21]: minecraft:kelp[age=0] + minecraft:kelp[age=22]: minecraft:kelp[age=0] + minecraft:kelp[age=23]: minecraft:kelp[age=0] + minecraft:kelp[age=24]: minecraft:kelp[age=0] + minecraft:kelp[age=25]: minecraft:kelp[age=0] + + #### Vines #### + # Unless you tweak the vine's block tag, the client will always think it's climbable. + # Since vines look identical at different growth stages, we can repurpose those extra states to make custom blocks like ropes. + minecraft:weeping_vines[age=1]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=2]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=3]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=4]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=5]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=6]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=7]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=8]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=9]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=10]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=11]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=12]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=13]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=14]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=15]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=16]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=17]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=18]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=19]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=20]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=21]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=22]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=23]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=24]: minecraft:weeping_vines[age=0] + minecraft:weeping_vines[age=25]: minecraft:weeping_vines[age=0] + minecraft:twisting_vines[age=1]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=2]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=3]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=4]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=5]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=6]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=7]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=8]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=9]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=10]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=11]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=12]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=13]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=14]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=15]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=16]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=17]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=18]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=19]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=20]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=21]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=22]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=23]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=24]: minecraft:twisting_vines[age=0] + minecraft:twisting_vines[age=25]: minecraft:twisting_vines[age=0] + minecraft:cave_vines[age=1,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=2,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=3,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=4,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=5,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=6,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=7,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=8,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=9,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=10,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=11,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=12,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=13,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=14,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=15,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=16,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=17,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=18,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=19,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=20,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=21,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=22,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=23,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=24,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=25,berries=false]: minecraft:cave_vines[age=0,berries=false] + minecraft:cave_vines[age=1,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=2,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=3,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=4,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=5,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=6,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=7,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=8,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=9,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=10,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=11,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=12,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=13,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=14,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=15,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=16,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=17,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=18,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=19,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=20,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=21,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=22,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=23,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=24,berries=true]: minecraft:cave_vines[age=0,berries=true] + minecraft:cave_vines[age=25,berries=true]: minecraft:cave_vines[age=0,berries=true] + + #### SugarCane #### + # Sugar cane looks exactly the same no matter its growth stage. Plus, it's got this perfect hitbox that makes it awesome for taller plants + minecraft:sugar_cane[age=1]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=2]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=3]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=4]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=5]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=6]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=7]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=8]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=9]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=10]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=11]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=12]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=13]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=14]: minecraft:sugar_cane[age=0] + minecraft:sugar_cane[age=15]: minecraft:sugar_cane[age=0] + + #### Cactus #### + # Cactus looks the same at all growth stages.Its hitbox is 14x14x15, making it perfect for creating blocks that are just slightly smaller than full-size + minecraft:cactus[age=1]: minecraft:cactus[age=0] + minecraft:cactus[age=2]: minecraft:cactus[age=0] + minecraft:cactus[age=3]: minecraft:cactus[age=0] + minecraft:cactus[age=4]: minecraft:cactus[age=0] + minecraft:cactus[age=5]: minecraft:cactus[age=0] + minecraft:cactus[age=6]: minecraft:cactus[age=0] + minecraft:cactus[age=7]: minecraft:cactus[age=0] + minecraft:cactus[age=8]: minecraft:cactus[age=0] + minecraft:cactus[age=9]: minecraft:cactus[age=0] + minecraft:cactus[age=10]: minecraft:cactus[age=0] + minecraft:cactus[age=11]: minecraft:cactus[age=0] + minecraft:cactus[age=12]: minecraft:cactus[age=0] + minecraft:cactus[age=13]: minecraft:cactus[age=0] + minecraft:cactus[age=14]: minecraft:cactus[age=0] + minecraft:cactus[age=15]: minecraft:cactus[age=0] + + #### Leaves #### + # The 'distance' and 'persistent' properties are used under the hood to optimize how leaves decay, but visually? They look exactly the same. + # These are some of the few block types that actually support transparent textures. + minecraft:oak_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:oak_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:oak_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:acacia_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:acacia_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:acacia_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:jungle_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:jungle_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:jungle_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:birch_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:birch_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:birch_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:mangrove_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:mangrove_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:mangrove_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:cherry_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:cherry_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:cherry_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:dark_oak_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:dark_oak_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:dark_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:azalea_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:azalea_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:flowering_azalea_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:flowering_azalea_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:spruce_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:spruce_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=true] + + $$>=1.21.4#leaves: + minecraft:pale_oak_leaves[distance=1,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=2,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=3,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=4,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=5,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=6,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=7,persistent=false,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=1,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=2,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=3,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=4,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=5,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=6,persistent=true,waterlogged=false]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + minecraft:pale_oak_leaves[distance=1,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=2,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=3,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=4,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=5,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=6,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=7,persistent=false,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=1,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=2,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=3,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=4,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=5,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + minecraft:pale_oak_leaves[distance=6,persistent=true,waterlogged=true]: minecraft:pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + + #### Tripwire #### + # Tripwires actually have 128 different states, but we're keeping just two of them to match vanilla's visual styles. + # Honestly, as long as the tripwire works properly, most players won't even mind what it looks like. + # Tripwire hitboxes aren't all the same - the triggered ones are way shorter. + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] + + #### Note Block #### + # This block has the most unused states in Minecraft, but the client always thinks it's interactive. + # Plus, there's some visual glitches when the client try predicting instrument changes. + # We've kept a full set of note settings by default - that way it plays nice with resource packs that show notes + minecraft:note_block[instrument=hat,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=hat,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=hat,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=hat,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=hat,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=hat,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=hat,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=hat,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=hat,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=hat,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=hat,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=hat,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=hat,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=hat,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=hat,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=hat,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=hat,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=hat,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=hat,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=hat,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=hat,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=hat,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=hat,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=hat,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=hat,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=hat,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=hat,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=hat,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=hat,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=hat,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=hat,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=hat,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=hat,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=hat,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=hat,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=hat,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=hat,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=hat,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=hat,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=hat,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=hat,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=hat,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=hat,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=hat,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=hat,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=hat,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=hat,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=hat,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=hat,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=hat,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=basedrum,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=basedrum,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=basedrum,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=basedrum,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=basedrum,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=basedrum,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=basedrum,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=basedrum,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=basedrum,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=basedrum,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=basedrum,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=basedrum,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=basedrum,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=basedrum,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=basedrum,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=basedrum,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=basedrum,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=basedrum,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=basedrum,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=basedrum,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=basedrum,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=basedrum,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=basedrum,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=basedrum,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=basedrum,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=basedrum,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=basedrum,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=basedrum,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=basedrum,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=basedrum,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=basedrum,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=basedrum,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=basedrum,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=basedrum,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=basedrum,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=basedrum,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=basedrum,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=basedrum,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=basedrum,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=basedrum,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=basedrum,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=basedrum,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=basedrum,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=basedrum,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=basedrum,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=basedrum,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=basedrum,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=basedrum,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=basedrum,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=basedrum,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=snare,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=snare,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=snare,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=snare,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=snare,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=snare,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=snare,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=snare,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=snare,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=snare,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=snare,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=snare,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=snare,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=snare,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=snare,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=snare,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=snare,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=snare,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=snare,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=snare,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=snare,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=snare,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=snare,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=snare,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=snare,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=snare,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=snare,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=snare,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=snare,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=snare,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=snare,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=snare,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=snare,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=snare,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=snare,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=snare,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=snare,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=snare,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=snare,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=snare,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=snare,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=snare,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=snare,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=snare,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=snare,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=snare,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=snare,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=snare,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=snare,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=snare,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=bass,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=bass,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=bass,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=bass,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=bass,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=bass,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=bass,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=bass,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=bass,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=bass,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=bass,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=bass,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=bass,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=bass,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=bass,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=bass,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=bass,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=bass,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=bass,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=bass,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=bass,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=bass,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=bass,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=bass,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=bass,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=bass,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=bass,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=bass,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=bass,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=bass,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=bass,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=bass,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=bass,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=bass,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=bass,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=bass,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=bass,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=bass,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=bass,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=bass,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=bass,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=bass,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=bass,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=bass,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=bass,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=bass,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=bass,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=bass,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=bass,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=bass,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=flute,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=flute,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=flute,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=flute,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=flute,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=flute,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=flute,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=flute,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=flute,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=flute,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=flute,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=flute,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=flute,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=flute,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=flute,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=flute,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=flute,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=flute,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=flute,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=flute,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=flute,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=flute,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=flute,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=flute,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=flute,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=flute,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=flute,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=flute,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=flute,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=flute,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=flute,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=flute,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=flute,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=flute,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=flute,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=flute,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=flute,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=flute,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=flute,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=flute,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=flute,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=flute,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=flute,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=flute,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=flute,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=flute,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=flute,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=flute,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=flute,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=flute,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=bell,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=bell,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=bell,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=bell,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=bell,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=bell,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=bell,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=bell,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=bell,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=bell,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=bell,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=bell,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=bell,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=bell,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=bell,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=bell,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=bell,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=bell,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=bell,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=bell,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=bell,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=bell,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=bell,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=bell,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=bell,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=bell,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=bell,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=bell,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=bell,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=bell,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=bell,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=bell,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=bell,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=bell,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=bell,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=bell,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=bell,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=bell,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=bell,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=bell,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=bell,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=bell,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=bell,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=bell,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=bell,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=bell,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=bell,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=bell,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=bell,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=bell,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=guitar,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=guitar,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=guitar,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=guitar,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=guitar,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=guitar,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=guitar,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=guitar,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=guitar,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=guitar,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=guitar,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=guitar,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=guitar,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=guitar,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=guitar,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=guitar,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=guitar,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=guitar,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=guitar,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=guitar,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=guitar,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=guitar,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=guitar,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=guitar,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=guitar,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=guitar,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=guitar,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=guitar,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=guitar,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=guitar,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=guitar,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=guitar,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=guitar,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=guitar,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=guitar,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=guitar,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=guitar,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=guitar,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=guitar,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=guitar,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=guitar,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=guitar,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=guitar,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=guitar,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=guitar,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=guitar,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=guitar,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=guitar,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=guitar,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=guitar,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=chime,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=chime,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=chime,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=chime,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=chime,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=chime,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=chime,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=chime,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=chime,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=chime,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=chime,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=chime,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=chime,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=chime,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=chime,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=chime,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=chime,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=chime,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=chime,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=chime,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=chime,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=chime,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=chime,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=chime,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=chime,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=chime,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=chime,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=chime,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=chime,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=chime,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=chime,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=chime,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=chime,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=chime,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=chime,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=chime,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=chime,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=chime,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=chime,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=chime,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=chime,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=chime,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=chime,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=chime,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=chime,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=chime,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=chime,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=chime,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=chime,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=chime,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=xylophone,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=xylophone,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=xylophone,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=xylophone,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=xylophone,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=xylophone,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=xylophone,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=xylophone,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=xylophone,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=xylophone,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=xylophone,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=xylophone,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=xylophone,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=xylophone,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=xylophone,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=xylophone,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=xylophone,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=xylophone,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=xylophone,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=xylophone,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=xylophone,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=xylophone,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=xylophone,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=xylophone,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=xylophone,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=xylophone,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=xylophone,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=xylophone,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=xylophone,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=xylophone,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=xylophone,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=xylophone,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=xylophone,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=xylophone,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=xylophone,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=xylophone,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=xylophone,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=xylophone,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=xylophone,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=xylophone,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=xylophone,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=xylophone,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=xylophone,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=xylophone,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=xylophone,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=xylophone,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=xylophone,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=xylophone,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=xylophone,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=xylophone,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=iron_xylophone,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=iron_xylophone,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=cow_bell,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=cow_bell,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=cow_bell,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=cow_bell,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=cow_bell,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=cow_bell,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=cow_bell,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=cow_bell,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=cow_bell,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=cow_bell,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=cow_bell,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=cow_bell,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=cow_bell,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=cow_bell,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=cow_bell,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=cow_bell,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=cow_bell,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=cow_bell,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=cow_bell,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=cow_bell,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=cow_bell,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=cow_bell,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=cow_bell,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=cow_bell,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=cow_bell,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=cow_bell,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=cow_bell,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=cow_bell,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=cow_bell,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=cow_bell,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=cow_bell,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=cow_bell,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=cow_bell,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=cow_bell,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=cow_bell,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=cow_bell,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=cow_bell,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=cow_bell,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=cow_bell,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=cow_bell,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=cow_bell,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=cow_bell,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=cow_bell,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=cow_bell,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=cow_bell,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=cow_bell,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=cow_bell,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=cow_bell,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=cow_bell,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=cow_bell,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=didgeridoo,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=didgeridoo,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=didgeridoo,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=didgeridoo,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=didgeridoo,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=didgeridoo,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=didgeridoo,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=didgeridoo,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=didgeridoo,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=didgeridoo,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=didgeridoo,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=didgeridoo,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=didgeridoo,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=didgeridoo,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=didgeridoo,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=didgeridoo,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=didgeridoo,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=didgeridoo,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=didgeridoo,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=didgeridoo,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=didgeridoo,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=didgeridoo,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=didgeridoo,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=didgeridoo,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=didgeridoo,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=didgeridoo,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=didgeridoo,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=didgeridoo,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=didgeridoo,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=didgeridoo,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=didgeridoo,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=didgeridoo,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=didgeridoo,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=didgeridoo,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=didgeridoo,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=didgeridoo,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=didgeridoo,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=didgeridoo,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=didgeridoo,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=didgeridoo,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=didgeridoo,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=didgeridoo,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=didgeridoo,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=didgeridoo,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=didgeridoo,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=didgeridoo,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=didgeridoo,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=didgeridoo,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=didgeridoo,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=didgeridoo,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=bit,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=bit,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=bit,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=bit,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=bit,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=bit,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=bit,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=bit,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=bit,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=bit,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=bit,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=bit,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=bit,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=bit,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=bit,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=bit,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=bit,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=bit,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=bit,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=bit,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=bit,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=bit,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=bit,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=bit,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=bit,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=bit,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=bit,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=bit,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=bit,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=bit,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=bit,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=bit,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=bit,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=bit,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=bit,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=bit,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=bit,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=bit,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=bit,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=bit,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=bit,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=bit,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=bit,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=bit,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=bit,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=bit,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=bit,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=bit,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=bit,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=bit,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=banjo,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=banjo,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=banjo,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=banjo,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=banjo,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=banjo,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=banjo,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=banjo,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=banjo,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=banjo,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=banjo,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=banjo,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=banjo,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=banjo,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=banjo,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=banjo,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=banjo,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=banjo,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=banjo,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=banjo,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=banjo,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=banjo,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=banjo,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=banjo,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=banjo,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=banjo,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=banjo,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=banjo,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=banjo,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=banjo,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=banjo,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=banjo,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=banjo,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=banjo,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=banjo,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=banjo,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=banjo,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=banjo,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=banjo,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=banjo,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=banjo,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=banjo,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=banjo,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=banjo,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=banjo,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=banjo,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=banjo,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=banjo,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=banjo,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=banjo,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=pling,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=pling,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=pling,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=pling,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=pling,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=pling,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=pling,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=pling,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=pling,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=pling,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=pling,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=pling,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=pling,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=pling,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=pling,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=pling,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=pling,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=pling,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=pling,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=pling,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=pling,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=pling,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=pling,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=pling,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=pling,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=pling,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=pling,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=pling,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=pling,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=pling,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=pling,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=pling,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=pling,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=pling,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=pling,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=pling,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=pling,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=pling,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=pling,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=pling,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=pling,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=pling,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=pling,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=pling,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=pling,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=pling,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=pling,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=pling,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=pling,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=pling,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=zombie,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=zombie,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=zombie,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=zombie,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=zombie,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=zombie,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=zombie,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=zombie,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=zombie,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=zombie,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=zombie,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=zombie,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=zombie,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=zombie,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=zombie,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=zombie,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=zombie,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=zombie,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=zombie,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=zombie,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=zombie,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=zombie,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=zombie,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=zombie,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=zombie,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=zombie,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=zombie,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=zombie,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=zombie,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=zombie,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=zombie,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=zombie,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=zombie,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=zombie,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=zombie,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=zombie,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=zombie,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=zombie,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=zombie,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=zombie,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=zombie,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=zombie,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=zombie,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=zombie,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=zombie,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=zombie,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=zombie,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=zombie,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=zombie,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=zombie,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=skeleton,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=skeleton,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=skeleton,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=skeleton,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=skeleton,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=skeleton,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=skeleton,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=skeleton,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=skeleton,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=skeleton,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=skeleton,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=skeleton,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=skeleton,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=skeleton,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=skeleton,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=skeleton,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=skeleton,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=skeleton,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=skeleton,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=skeleton,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=skeleton,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=skeleton,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=skeleton,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=skeleton,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=skeleton,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=skeleton,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=skeleton,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=skeleton,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=skeleton,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=skeleton,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=skeleton,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=skeleton,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=skeleton,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=skeleton,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=skeleton,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=skeleton,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=skeleton,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=skeleton,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=skeleton,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=skeleton,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=skeleton,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=skeleton,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=skeleton,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=skeleton,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=skeleton,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=skeleton,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=skeleton,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=skeleton,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=skeleton,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=skeleton,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=creeper,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=creeper,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=creeper,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=creeper,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=creeper,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=creeper,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=creeper,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=creeper,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=creeper,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=creeper,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=creeper,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=creeper,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=creeper,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=creeper,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=creeper,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=creeper,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=creeper,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=creeper,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=creeper,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=creeper,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=creeper,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=creeper,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=creeper,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=creeper,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=creeper,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=creeper,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=creeper,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=creeper,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=creeper,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=creeper,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=creeper,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=creeper,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=creeper,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=creeper,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=creeper,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=creeper,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=creeper,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=creeper,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=creeper,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=creeper,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=creeper,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=creeper,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=creeper,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=creeper,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=creeper,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=creeper,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=creeper,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=creeper,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=creeper,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=creeper,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=dragon,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=dragon,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=dragon,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=dragon,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=dragon,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=dragon,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=dragon,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=dragon,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=dragon,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=dragon,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=dragon,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=dragon,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=dragon,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=dragon,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=dragon,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=dragon,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=dragon,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=dragon,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=dragon,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=dragon,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=dragon,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=dragon,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=dragon,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=dragon,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=dragon,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=dragon,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=dragon,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=dragon,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=dragon,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=dragon,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=dragon,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=dragon,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=dragon,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=dragon,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=dragon,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=dragon,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=dragon,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=dragon,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=dragon,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=dragon,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=dragon,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=dragon,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=dragon,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=dragon,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=dragon,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=dragon,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=dragon,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=dragon,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=dragon,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=dragon,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=wither_skeleton,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=wither_skeleton,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=piglin,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=piglin,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=piglin,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=piglin,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=piglin,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=piglin,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=piglin,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=piglin,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=piglin,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=piglin,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=piglin,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=piglin,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=piglin,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=piglin,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=piglin,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=piglin,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=piglin,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=piglin,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=piglin,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=piglin,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=piglin,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=piglin,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=piglin,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=piglin,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=piglin,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=piglin,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=piglin,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=piglin,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=piglin,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=piglin,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=piglin,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=piglin,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=piglin,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=piglin,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=piglin,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=piglin,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=piglin,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=piglin,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=piglin,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=piglin,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=piglin,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=piglin,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=piglin,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=piglin,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=piglin,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=piglin,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=piglin,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=piglin,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=piglin,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=piglin,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + minecraft:note_block[instrument=custom_head,note=0,powered=false]: minecraft:note_block[instrument=harp,note=0,powered=false] + minecraft:note_block[instrument=custom_head,note=1,powered=false]: minecraft:note_block[instrument=harp,note=1,powered=false] + minecraft:note_block[instrument=custom_head,note=2,powered=false]: minecraft:note_block[instrument=harp,note=2,powered=false] + minecraft:note_block[instrument=custom_head,note=3,powered=false]: minecraft:note_block[instrument=harp,note=3,powered=false] + minecraft:note_block[instrument=custom_head,note=4,powered=false]: minecraft:note_block[instrument=harp,note=4,powered=false] + minecraft:note_block[instrument=custom_head,note=5,powered=false]: minecraft:note_block[instrument=harp,note=5,powered=false] + minecraft:note_block[instrument=custom_head,note=6,powered=false]: minecraft:note_block[instrument=harp,note=6,powered=false] + minecraft:note_block[instrument=custom_head,note=7,powered=false]: minecraft:note_block[instrument=harp,note=7,powered=false] + minecraft:note_block[instrument=custom_head,note=8,powered=false]: minecraft:note_block[instrument=harp,note=8,powered=false] + minecraft:note_block[instrument=custom_head,note=9,powered=false]: minecraft:note_block[instrument=harp,note=9,powered=false] + minecraft:note_block[instrument=custom_head,note=10,powered=false]: minecraft:note_block[instrument=harp,note=10,powered=false] + minecraft:note_block[instrument=custom_head,note=11,powered=false]: minecraft:note_block[instrument=harp,note=11,powered=false] + minecraft:note_block[instrument=custom_head,note=12,powered=false]: minecraft:note_block[instrument=harp,note=12,powered=false] + minecraft:note_block[instrument=custom_head,note=13,powered=false]: minecraft:note_block[instrument=harp,note=13,powered=false] + minecraft:note_block[instrument=custom_head,note=14,powered=false]: minecraft:note_block[instrument=harp,note=14,powered=false] + minecraft:note_block[instrument=custom_head,note=15,powered=false]: minecraft:note_block[instrument=harp,note=15,powered=false] + minecraft:note_block[instrument=custom_head,note=16,powered=false]: minecraft:note_block[instrument=harp,note=16,powered=false] + minecraft:note_block[instrument=custom_head,note=17,powered=false]: minecraft:note_block[instrument=harp,note=17,powered=false] + minecraft:note_block[instrument=custom_head,note=18,powered=false]: minecraft:note_block[instrument=harp,note=18,powered=false] + minecraft:note_block[instrument=custom_head,note=19,powered=false]: minecraft:note_block[instrument=harp,note=19,powered=false] + minecraft:note_block[instrument=custom_head,note=20,powered=false]: minecraft:note_block[instrument=harp,note=20,powered=false] + minecraft:note_block[instrument=custom_head,note=21,powered=false]: minecraft:note_block[instrument=harp,note=21,powered=false] + minecraft:note_block[instrument=custom_head,note=22,powered=false]: minecraft:note_block[instrument=harp,note=22,powered=false] + minecraft:note_block[instrument=custom_head,note=23,powered=false]: minecraft:note_block[instrument=harp,note=23,powered=false] + minecraft:note_block[instrument=custom_head,note=24,powered=false]: minecraft:note_block[instrument=harp,note=24,powered=false] + minecraft:note_block[instrument=custom_head,note=0,powered=true]: minecraft:note_block[instrument=harp,note=0,powered=true] + minecraft:note_block[instrument=custom_head,note=1,powered=true]: minecraft:note_block[instrument=harp,note=1,powered=true] + minecraft:note_block[instrument=custom_head,note=2,powered=true]: minecraft:note_block[instrument=harp,note=2,powered=true] + minecraft:note_block[instrument=custom_head,note=3,powered=true]: minecraft:note_block[instrument=harp,note=3,powered=true] + minecraft:note_block[instrument=custom_head,note=4,powered=true]: minecraft:note_block[instrument=harp,note=4,powered=true] + minecraft:note_block[instrument=custom_head,note=5,powered=true]: minecraft:note_block[instrument=harp,note=5,powered=true] + minecraft:note_block[instrument=custom_head,note=6,powered=true]: minecraft:note_block[instrument=harp,note=6,powered=true] + minecraft:note_block[instrument=custom_head,note=7,powered=true]: minecraft:note_block[instrument=harp,note=7,powered=true] + minecraft:note_block[instrument=custom_head,note=8,powered=true]: minecraft:note_block[instrument=harp,note=8,powered=true] + minecraft:note_block[instrument=custom_head,note=9,powered=true]: minecraft:note_block[instrument=harp,note=9,powered=true] + minecraft:note_block[instrument=custom_head,note=10,powered=true]: minecraft:note_block[instrument=harp,note=10,powered=true] + minecraft:note_block[instrument=custom_head,note=11,powered=true]: minecraft:note_block[instrument=harp,note=11,powered=true] + minecraft:note_block[instrument=custom_head,note=12,powered=true]: minecraft:note_block[instrument=harp,note=12,powered=true] + minecraft:note_block[instrument=custom_head,note=13,powered=true]: minecraft:note_block[instrument=harp,note=13,powered=true] + minecraft:note_block[instrument=custom_head,note=14,powered=true]: minecraft:note_block[instrument=harp,note=14,powered=true] + minecraft:note_block[instrument=custom_head,note=15,powered=true]: minecraft:note_block[instrument=harp,note=15,powered=true] + minecraft:note_block[instrument=custom_head,note=16,powered=true]: minecraft:note_block[instrument=harp,note=16,powered=true] + minecraft:note_block[instrument=custom_head,note=17,powered=true]: minecraft:note_block[instrument=harp,note=17,powered=true] + minecraft:note_block[instrument=custom_head,note=18,powered=true]: minecraft:note_block[instrument=harp,note=18,powered=true] + minecraft:note_block[instrument=custom_head,note=19,powered=true]: minecraft:note_block[instrument=harp,note=19,powered=true] + minecraft:note_block[instrument=custom_head,note=20,powered=true]: minecraft:note_block[instrument=harp,note=20,powered=true] + minecraft:note_block[instrument=custom_head,note=21,powered=true]: minecraft:note_block[instrument=harp,note=21,powered=true] + minecraft:note_block[instrument=custom_head,note=22,powered=true]: minecraft:note_block[instrument=harp,note=22,powered=true] + minecraft:note_block[instrument=custom_head,note=23,powered=true]: minecraft:note_block[instrument=harp,note=23,powered=true] + minecraft:note_block[instrument=custom_head,note=24,powered=true]: minecraft:note_block[instrument=harp,note=24,powered=true] + + #### Trapdoor #### + # Trapdoors look identical whether they're powered or not - which means we can double our trapdoors by using both states + minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:iron_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:acacia_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:spruce_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:birch_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:jungle_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:dark_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:mangrove_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:cherry_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:bamboo_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:crimson_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:warped_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + + $$>=1.20.3#trapdoor: + minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + # Fun fact: copper blocks look the same whether waxed or not. + # We're playing it safe with the default setup - keeping vanilla's waxed states recognizable. + # But you can always change it to convert waxed blocks back to regular ones. + minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_exposed_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_weathered_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:waxed_oxidized_copper_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + + $$>=1.21.4#trapdoor: + minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=top,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=top,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=top,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=top,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=false]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=false] + minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=true,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=north,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=south,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=east,half=bottom,open=false,powered=false,waterlogged=true] + minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=true,waterlogged=true]: minecraft:pale_oak_trapdoor[facing=west,half=bottom,open=false,powered=false,waterlogged=true] + + #### Door #### + # A door look exactly the same whether it's powered on or off, just like how a trapdoor works + minecraft:oak_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:oak_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:oak_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:oak_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:oak_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:oak_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:spruce_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:spruce_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:spruce_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:spruce_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:spruce_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:birch_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:birch_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:birch_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:birch_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:birch_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:jungle_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:jungle_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:jungle_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:jungle_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:jungle_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:acacia_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:acacia_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:acacia_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:acacia_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:acacia_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:dark_oak_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:mangrove_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:cherry_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:cherry_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:cherry_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:cherry_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:cherry_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:bamboo_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:crimson_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:crimson_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:crimson_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:crimson_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:crimson_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:warped_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:warped_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:warped_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:warped_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:warped_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:iron_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:iron_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:iron_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:iron_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:iron_door[facing=west,half=upper,hinge=right,open=true,powered=false] + + $$>=1.20.3#door: + minecraft:copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_exposed_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_weathered_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:waxed_oxidized_copper_door[facing=west,half=upper,hinge=right,open=true,powered=false] + + $$>=1.21.4#door: + minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=lower,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=east,half=upper,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=lower,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=north,half=upper,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=lower,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=south,half=upper,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=false,powered=false] + minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=false,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=false,powered=false] + minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=lower,hinge=right,open=true,powered=false] + minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=left,open=true,powered=false] + minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=true,powered=true]: minecraft:pale_oak_door[facing=west,half=upper,hinge=right,open=true,powered=false] + + #### Fence Gate #### + minecraft:oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:oak_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:oak_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:oak_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:oak_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:oak_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:oak_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:oak_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:oak_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:oak_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:spruce_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:spruce_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:spruce_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:spruce_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:spruce_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:spruce_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:spruce_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:spruce_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:spruce_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:spruce_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:spruce_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:spruce_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:spruce_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:spruce_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:spruce_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:spruce_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:spruce_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:birch_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:birch_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:birch_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:birch_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:birch_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:birch_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:birch_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:birch_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:birch_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:birch_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:birch_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:birch_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:birch_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:birch_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:birch_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:birch_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:birch_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:jungle_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:jungle_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:jungle_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:jungle_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:jungle_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:jungle_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:jungle_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:jungle_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:jungle_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:jungle_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:jungle_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:jungle_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:jungle_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:jungle_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:jungle_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:jungle_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:jungle_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:acacia_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:acacia_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:acacia_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:acacia_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:acacia_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:acacia_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:acacia_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:acacia_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:acacia_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:acacia_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:acacia_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:acacia_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:acacia_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:acacia_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:acacia_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:acacia_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:acacia_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:dark_oak_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:mangrove_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:cherry_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:cherry_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:cherry_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:cherry_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:cherry_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:cherry_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:cherry_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:cherry_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:cherry_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:cherry_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:cherry_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:cherry_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:cherry_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:cherry_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:cherry_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:cherry_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:cherry_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:bamboo_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:crimson_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:crimson_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:crimson_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:crimson_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:crimson_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:crimson_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:crimson_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:crimson_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:crimson_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:crimson_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:crimson_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:crimson_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:crimson_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:crimson_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:crimson_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:crimson_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:crimson_fence_gate[facing=north,in_wall=true,open=true,powered=false] + minecraft:warped_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:warped_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:warped_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:warped_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:warped_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:warped_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:warped_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:warped_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:warped_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:warped_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:warped_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:warped_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:warped_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:warped_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:warped_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:warped_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:warped_fence_gate[facing=north,in_wall=true,open=true,powered=false] + + $$>=1.21.4#fence_gate: + minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=false,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=east,in_wall=true,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=false,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=south,in_wall=true,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=false,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=west,in_wall=true,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=false,open=true,powered=false] + minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=false,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=false,powered=false] + minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=true,powered=true]: minecraft:pale_oak_fence_gate[facing=north,in_wall=true,open=true,powered=false] + + #### Slab #### + minecraft:petrified_oak_slab[type=bottom,waterlogged=false]: minecraft:oak_slab[type=bottom,waterlogged=false] + minecraft:petrified_oak_slab[type=top,waterlogged=false]: minecraft:oak_slab[type=top,waterlogged=false] + minecraft:petrified_oak_slab[type=double,waterlogged=false]: minecraft:oak_slab[type=double,waterlogged=false] + minecraft:petrified_oak_slab[type=bottom,waterlogged=true]: minecraft:oak_slab[type=bottom,waterlogged=true] + minecraft:petrified_oak_slab[type=top,waterlogged=true]: minecraft:oak_slab[type=top,waterlogged=true] + minecraft:petrified_oak_slab[type=double,waterlogged=true]: minecraft:oak_slab[type=double,waterlogged=true] + minecraft:cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_cut_copper_slab[type=bottom,waterlogged=false] + minecraft:cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_cut_copper_slab[type=top,waterlogged=false] + minecraft:cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_cut_copper_slab[type=double,waterlogged=false] + minecraft:cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_cut_copper_slab[type=bottom,waterlogged=true] + minecraft:cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_cut_copper_slab[type=top,waterlogged=true] + minecraft:cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_cut_copper_slab[type=double,waterlogged=true] + minecraft:exposed_cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_exposed_cut_copper_slab[type=bottom,waterlogged=false] + minecraft:exposed_cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_exposed_cut_copper_slab[type=top,waterlogged=false] + minecraft:exposed_cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_exposed_cut_copper_slab[type=double,waterlogged=false] + minecraft:exposed_cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_exposed_cut_copper_slab[type=bottom,waterlogged=true] + minecraft:exposed_cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_exposed_cut_copper_slab[type=top,waterlogged=true] + minecraft:exposed_cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_exposed_cut_copper_slab[type=double,waterlogged=true] + minecraft:weathered_cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_weathered_cut_copper_slab[type=bottom,waterlogged=false] + minecraft:weathered_cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_weathered_cut_copper_slab[type=top,waterlogged=false] + minecraft:weathered_cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_weathered_cut_copper_slab[type=double,waterlogged=false] + minecraft:weathered_cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_weathered_cut_copper_slab[type=bottom,waterlogged=true] + minecraft:weathered_cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_weathered_cut_copper_slab[type=top,waterlogged=true] + minecraft:weathered_cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_weathered_cut_copper_slab[type=double,waterlogged=true] + minecraft:oxidized_cut_copper_slab[type=bottom,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_slab[type=bottom,waterlogged=false] + minecraft:oxidized_cut_copper_slab[type=top,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_slab[type=top,waterlogged=false] + minecraft:oxidized_cut_copper_slab[type=double,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_slab[type=double,waterlogged=false] + minecraft:oxidized_cut_copper_slab[type=bottom,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_slab[type=bottom,waterlogged=true] + minecraft:oxidized_cut_copper_slab[type=top,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_slab[type=top,waterlogged=true] + minecraft:oxidized_cut_copper_slab[type=double,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_slab[type=double,waterlogged=true] + + #### Stairs #### + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] + minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] + minecraft:cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] + minecraft:exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_exposed_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] + minecraft:weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_weathered_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=bottom,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=east,half=top,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=bottom,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=south,half=top,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=bottom,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=west,half=top,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=bottom,shape=outer_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=straight,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=inner_right,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_left,waterlogged=true] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=false] + minecraft:oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true]: minecraft:waxed_oxidized_cut_copper_stairs[facing=north,half=top,shape=outer_right,waterlogged=true] + + #### Grate #### + # Suitable for making glass because it is completely transparent + $$>=1.20.3#grate: + minecraft:copper_grate[waterlogged=false]: minecraft:waxed_copper_grate[waterlogged=false] + minecraft:copper_grate[waterlogged=true]: minecraft:waxed_copper_grate[waterlogged=true] + minecraft:weathered_copper_grate[waterlogged=false]: minecraft:waxed_weathered_copper_grate[waterlogged=false] + minecraft:weathered_copper_grate[waterlogged=true]: minecraft:waxed_weathered_copper_grate[waterlogged=true] + minecraft:exposed_copper_grate[waterlogged=false]: minecraft:waxed_exposed_copper_grate[waterlogged=false] + minecraft:exposed_copper_grate[waterlogged=true]: minecraft:waxed_exposed_copper_grate[waterlogged=true] + minecraft:oxidized_copper_grate[waterlogged=false]: minecraft:waxed_oxidized_copper_grate[waterlogged=false] + minecraft:oxidized_copper_grate[waterlogged=true]: minecraft:waxed_oxidized_copper_grate[waterlogged=true] + + #### Pressure Plate #### + # Triggered pressure plates appear identical, even though they output different signal strengths. + minecraft:light_weighted_pressure_plate[power=2]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=3]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=4]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=5]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=6]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=7]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=8]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=9]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=10]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=11]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=12]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=13]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=14]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:light_weighted_pressure_plate[power=15]: minecraft:light_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=2]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=3]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=4]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=5]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=6]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=7]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=8]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=9]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=10]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=11]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=12]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=13]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=14]: minecraft:heavy_weighted_pressure_plate[power=1] + minecraft:heavy_weighted_pressure_plate[power=15]: minecraft:heavy_weighted_pressure_plate[power=1] + + #### Corals #### + # Coral blocks are ideal for creating water blocks or wall-mounted blocks. But you have to sacrifice its dry appearance. + # minecraft:dead_brain_coral[waterlogged=false]: minecraft:brain_coral[waterlogged=false] + # minecraft:dead_brain_coral[waterlogged=true]: minecraft:brain_coral[waterlogged=true] + # minecraft:dead_brain_coral_fan[waterlogged=false]: minecraft:brain_coral_fan[waterlogged=false] + # minecraft:dead_brain_coral_fan[waterlogged=true]: minecraft:brain_coral_fan[waterlogged=true] + # minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=east]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=east] + # minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=north]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=north] + # minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=south]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=south] + # minecraft:dead_brain_coral_wall_fan[waterlogged=false,facing=west]: minecraft:brain_coral_wall_fan[waterlogged=false,facing=west] + # minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=east]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=east] + # minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=north]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=north] + # minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=south]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=south] + # minecraft:dead_brain_coral_wall_fan[waterlogged=true,facing=west]: minecraft:brain_coral_wall_fan[waterlogged=true,facing=west] + # minecraft:dead_bubble_coral[waterlogged=false]: minecraft:bubble_coral[waterlogged=false] + # minecraft:dead_bubble_coral[waterlogged=true]: minecraft:bubble_coral[waterlogged=true] + # minecraft:dead_bubble_coral_fan[waterlogged=false]: minecraft:bubble_coral_fan[waterlogged=false] + # minecraft:dead_bubble_coral_fan[waterlogged=true]: minecraft:bubble_coral_fan[waterlogged=true] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=east] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=north] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=south] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=false,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=false,facing=west] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=east]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=east] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=north]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=north] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=south]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=south] + # minecraft:dead_bubble_coral_wall_fan[waterlogged=true,facing=west]: minecraft:bubble_coral_wall_fan[waterlogged=true,facing=west] + # minecraft:dead_fire_coral[waterlogged=false]: minecraft:fire_coral[waterlogged=false] + # minecraft:dead_fire_coral[waterlogged=true]: minecraft:fire_coral[waterlogged=true] + # minecraft:dead_fire_coral_fan[waterlogged=false]: minecraft:fire_coral_fan[waterlogged=false] + # minecraft:dead_fire_coral_fan[waterlogged=true]: minecraft:fire_coral_fan[waterlogged=true] + # minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=east] + # minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=north] + # minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=south] + # minecraft:dead_fire_coral_wall_fan[waterlogged=false,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=false,facing=west] + # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=east]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=east] + # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=north]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=north] + # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=south]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=south] + # minecraft:dead_fire_coral_wall_fan[waterlogged=true,facing=west]: minecraft:fire_coral_wall_fan[waterlogged=true,facing=west] + # minecraft:dead_horn_coral[waterlogged=false]: minecraft:horn_coral[waterlogged=false] + # minecraft:dead_horn_coral[waterlogged=true]: minecraft:horn_coral[waterlogged=true] + # minecraft:dead_horn_coral_fan[waterlogged=false]: minecraft:horn_coral_fan[waterlogged=false] + # minecraft:dead_horn_coral_fan[waterlogged=true]: minecraft:horn_coral_fan[waterlogged=true] + # minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=east] + # minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=north] + # minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=south] + # minecraft:dead_horn_coral_wall_fan[waterlogged=false,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=false,facing=west] + # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=east]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=east] + # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=north]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=north] + # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=south]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=south] + # minecraft:dead_horn_coral_wall_fan[waterlogged=true,facing=west]: minecraft:horn_coral_wall_fan[waterlogged=true,facing=west] + # minecraft:dead_tube_coral[waterlogged=false]: minecraft:tube_coral[waterlogged=false] + # minecraft:dead_tube_coral[waterlogged=true]: minecraft:tube_coral[waterlogged=true] + # minecraft:dead_tube_coral_fan[waterlogged=false]: minecraft:tube_coral_fan[waterlogged=false] + # minecraft:dead_tube_coral_fan[waterlogged=true]: minecraft:tube_coral_fan[waterlogged=true] + # minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=east]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=east] + # minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=north]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=north] + # minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=south]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=south] + # minecraft:dead_tube_coral_wall_fan[waterlogged=false,facing=west]: minecraft:tube_coral_wall_fan[waterlogged=false,facing=west] + # minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=east]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=east] + # minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=north]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=north] + # minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=south]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=south] + # minecraft:dead_tube_coral_wall_fan[waterlogged=true,facing=west]: minecraft:tube_coral_wall_fan[waterlogged=true,facing=west] + + #### Chorus Plant #### + # Chorus Plant does support transparent textures, but man... its hitbox is super weird. You're probably better off using leaves. + # minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] + # minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] \ No newline at end of file diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index f22dc1863..4e28bf879 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -264,7 +264,6 @@ warning.config.block.state.unavailable_vanilla: "Problem in Datei Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Block-State '', der den verfügbaren Slot-Bereich '0~' überschreitet." warning.config.block.state.conflict: "Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Block-State '', der bereits von '' belegt ist." warning.config.block.state.bind_failed: "Problem in Datei gefunden - Der Block '' konnte den echten Block-State für '' nicht binden, da der State bereits von '' belegt ist." -warning.config.block.state.invalid_real_id: "Problem in Datei gefunden - Der Block '' verwendet einen echten Block-State '', der den verfügbaren Slot-Bereich '0~' überschreitet. Erwäge, weitere echte States in 'additional-real-blocks.yml' hinzuzufügen, wenn die Slots aufgebraucht sind." warning.config.block.state.model.missing_path: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'path'-Option für 'model'." warning.config.block.state.model.invalid_path: "Problem in Datei gefunden - Der Block '' hat ein 'path'-Argument '', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.block.state.model.conflict: "Problem in Datei gefunden - Der Block '' versucht, das Model '' an den Block-State '' zu binden, der bereits an das Model '' gebunden ist." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 092ca02ce..8bfe8c7ad 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -255,6 +255,8 @@ warning.config.item.model.special.head.missing_kind: "Issue found in fil warning.config.item.updater.missing_type: "Issue found in file - The item '' is missing the required 'type' argument for item updater." warning.config.item.updater.invalid_type: "Issue found in file - The item '' is using an invalid 'type' argument '' for item updater." warning.config.item.updater.transmute.missing_material: "Issue found in file - The item '' is missing the required 'material' argument for 'transmute' item updater." +warning.config.block_state_mapping.invalid_state: "Issue found in file - The config '' is using an invalid block state ''." +warning.config.block_state_mapping.conflict: "Issue found in file - The config '' is unable to map block state to block state because the state has already been mapped to ." warning.config.block.duplicate: "Issue found in file - Duplicated block ''. Please check if there is the same configuration in other files." warning.config.block.missing_state: "Issue found in file - The block '' is missing the required 'state' argument." warning.config.block.state.property.missing_type: "Issue found in file - The block '' is missing the required 'type' argument for property ''." @@ -278,7 +280,7 @@ warning.config.block.state.unavailable_vanilla: "Issue found in file Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." warning.config.block.state.conflict: "Issue found in file - The block '' is using a vanilla block state '' that has been occupied by ''." warning.config.block.state.bind_failed: "Issue found in file - The block '' failed to bind real block state '' for '' as the state has been occupied by ''." -warning.config.block.state.invalid_real_id: "Issue found in file - The block '' is using a real block state '' that exceeds the available slot range '0~'. Consider adding more real states in 'additional-real-blocks.yml' if the slots are used up." +warning.config.block.state.invalid_real_id: "Issue found in file - The block '' is using a real block state '' that exceeds the available slot range '0~'. Consider adding more serverside blocks in 'config.yml' if the slots are used up." warning.config.block.state.model.missing_path: "Issue found in file - The block '' is missing the required 'path' option for 'model'." warning.config.block.state.model.invalid_path: "Issue found in file - The block '' has a 'path' argument '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.block.state.model.conflict: "Issue found in file - The block '' is trying to bind model '' to block state '' which has already been bound to model ''" diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index 77b0cc709..0429bbb86 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -187,7 +187,6 @@ warning.config.block.state.unavailable_vanilla: "Problema encontrado en warning.config.block.state.invalid_vanilla_id: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla '' que excede el rango de slots disponible '0~'." warning.config.block.state.conflict: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla '' que está ocupado por ''." warning.config.block.state.bind_failed: "Problema encontrado en el archivo - El bloque '' falló al vincular el estado de bloque real para '' porque está ocupado por el estado ''." -warning.config.block.state.invalid_real_id: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque real '' que excede el rango de slots disponible '0~'. Si los slots están usados, considera agregar más estados reales a 'additional-real-blocks.yml'." warning.config.block.state.model.missing_path: "Problema encontrado en el archivo - El bloque '' carece de la opción requerida 'path' para 'model'." warning.config.block.state.model.invalid_path: "Problema encontrado en el archivo - El bloque '' tiene un argumento 'path' '' que contiene caracteres prohibidos. Por favor lee https://minecraft.wiki/w/Resource_location#Legal_characters" warning.config.block.settings.unknown: "Problema encontrado en el archivo - El bloque '' está usando un tipo de configuración desconocido ''." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index 209c6cf28..dcb624e7a 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -237,7 +237,6 @@ warning.config.block.state.unavailable_vanilla: "Проблема най warning.config.block.state.invalid_vanilla_id: "Проблема найдена в файле - Блок '' использует состояние ванильного блока '', что превышает доступный диапазон слотов '0~'." warning.config.block.state.conflict: "Проблема найдена в файле - Блок '' использует состояние ванильного блока '' которое занято ''." warning.config.block.state.bind_failed: "Проблема найдена в файле - Блоку '' не удалось привязать реальное состояние блока для '', так как состояние занято ''." -warning.config.block.state.invalid_real_id: "Проблема найдена в файле - Блок '' использует реальное состояние блока '', которое превышает доступный диапазон слотов '0~'. Рассмотрите возможность добавления большего количества реальных состояний в 'additional-real-blocks.yml' если слоты израсходованы." warning.config.block.state.model.missing_path: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'path' опция для 'model'." warning.config.block.state.model.invalid_path: "Проблема найдена в файле - Блок '' имеет 'path' аргумент '' содержит недопустимые символы. Пожалуйста, прочтите https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.block.settings.unknown: "Проблема найдена в файле - Блок '' использует неизвестный тип настройки ''." diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index d1cff3a7c..e5b79c186 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -185,7 +185,6 @@ warning.config.block.state.unavailable_vanilla: " dosyasında sor warning.config.block.state.invalid_vanilla_id: " dosyasında sorun bulundu - '' bloğu, mevcut yuva aralığı '0~' aşan bir vanilya blok durumu '' kullanıyor." warning.config.block.state.conflict: " dosyasında sorun bulundu - '' bloğu, '' tarafından işgal edilmiş bir vanilya blok durumu '' kullanıyor." warning.config.block.state.bind_failed: " dosyasında sorun bulundu - '' bloğu, durum '' tarafından işgal edildiği için '' için gerçek blok durumu bağlamada başarısız oldu." -warning.config.block.state.invalid_real_id: " dosyasında sorun bulundu - '' bloğu, mevcut yuva aralığı '0~' aşan bir gerçek blok durumu '' kullanıyor. Yuvalar kullanılmışsa, 'additional-real-blocks.yml' dosyasına daha fazla gerçek durum eklemeyi düşünün." warning.config.block.state.model.missing_path: " dosyasında sorun bulundu - '' bloğu, 'model' için gerekli 'path' seçeneği eksik." warning.config.block.state.model.invalid_path: " dosyasında sorun bulundu - '' bloğunun, yasak karakterler içeren bir 'path' argümanı '' var. Lütfen https://minecraft.wiki/w/Resource_location#Legal_characters sayfasını okuyun." warning.config.block.settings.unknown: " dosyasında sorun bulundu - '' bloğu bilinmeyen bir ayar türü '' kullanıyor." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 8b6fc7227..56720fb27 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -272,7 +272,7 @@ warning.config.block.state.unavailable_vanilla: "在文件 发 warning.config.block.state.invalid_vanilla_id: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 超出可用槽位范围 '0~'" warning.config.block.state.conflict: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 已被 '' 占用" warning.config.block.state.bind_failed: "在文件 发现问题 - 方块 '' 无法为 '' 绑定真实方块状态 '' 因该状态已被 '' 占用" -warning.config.block.state.invalid_real_id: "在文件 发现问题 - 方块 '' 使用的真实方块状态 '' 超出可用槽位范围 '0~' 如果槽位已用尽 请在 additional-real-blocks.yml 中添加更多真实状态" +warning.config.block.state.invalid_real_id: "在文件 发现问题 - 方块 '' 使用的真实方块状态 '' 超出可用槽位范围 '0~' 如果槽位已用尽 请在 config.yml 中添加更多服务端侧方块" warning.config.block.state.model.missing_path: "在文件 发现问题 - 方块 '' 的 'model' 缺少必需的 'path' 选项" warning.config.block.state.model.invalid_path: "在文件 发现问题 - 方块 '' 的 'path' 参数 '' 包含非法字符 请参考 https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" warning.config.block.state.model.conflict: "在文件 发现问题 - 方块 '' 正尝试将模型 '' 绑定到方块状态 '' 上, 但是此状态已绑定了另一个模型 ''" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 458587234..aeeebaea8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.core.block; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigs; @@ -16,54 +16,81 @@ import net.momirealms.craftengine.core.pack.ResourceLocation; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.*; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.util.GsonHelper; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.util.*; import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.nio.file.Path; import java.util.*; -import java.util.function.Function; +import java.util.function.Predicate; public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager { protected final BlockParser blockParser; - // CraftEngine objects + protected final BlockStateMappingParser blockStateMappingParser; + // 根据id获取自定义方块 protected final Map byId = new HashMap<>(); - // Cached command suggestions + // 缓存的指令建议 protected final List cachedSuggestions = new ArrayList<>(); - // Cached Namespace + // 缓存的使用中的命名空间 protected final Set namespacesInUse = new HashSet<>(); - // for mod, real block id -> state models - protected final Map modBlockStates = new HashMap<>(); - // A temporary map that stores the model path of a certain vanilla block state + // 用于检测单个外观方块状态是否被绑定了不同模型 protected final Map tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>(); - // A temporary map used to detect whether the same block state corresponds to multiple models. - protected final Map tempRegistryIdConflictMap = new Int2ObjectOpenHashMap<>(); - // A temporary map that converts the custom block registered on the server to the vanilla block ID. - protected final Map tempBlockAppearanceConvertor = new Int2IntOpenHashMap(); - // Used to store override information of json files + // Map<方块类型, Map<方块状态NBT,模型>>,用于生成block state json protected final Map> blockStateOverrides = new HashMap<>(); - // a reverted mapper + // 用于生成mod使用的block state json + protected final Map modBlockStateOverrides = new HashMap<>(); + // 根据外观查找真实状态,用于debug指令 protected final Map> appearanceToRealState = new Int2ObjectOpenHashMap<>(); + // 声音映射表,和使用了哪些视觉方块有关 + protected final Map soundReplacements = new HashMap<>(512, 0.5f); + // 用于note_block:0这样格式的自动分配 + protected final Map> blockStateArranger = new HashMap<>(); + // 全方块状态映射文件,用于网络包映射 + protected final int[] blockStateMappings; + // 原版方块状态数量 + protected final int vanillaBlockStateCount; + // 注册的大宝贝 + protected final DelegatingBlock[] customBlocks; + protected final DelegatingBlockState[] customBlockStates; + protected final Object[] customBlockHolders; + // 自定义状态列表,会随着重载变化 + protected final ImmutableBlockState[] immutableBlockStates; + // 原版方块的属性缓存 + protected final BlockSettings[] vanillaBlockSettings; - // client side block tags - protected Map> clientBoundTags = Map.of(); - protected Map> previousClientBoundTags = Map.of(); - // Used to automatically arrange block states for strings such as minecraft:note_block:0 - protected Map> blockAppearanceArranger; - protected Map> realBlockArranger; - protected Map internalId2StateId; - protected Map registeredBlocks; - - protected AbstractBlockManager(CraftEngine plugin) { + protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) { super(plugin); + this.vanillaBlockStateCount = vanillaBlockStateCount; this.blockParser = new BlockParser(); + this.blockStateMappingParser = new BlockStateMappingParser(); + this.customBlocks = new DelegatingBlock[customBlockCount]; + this.customBlockHolders = new Object[customBlockCount]; + this.customBlockStates = new DelegatingBlockState[customBlockCount]; + this.vanillaBlockSettings = new BlockSettings[vanillaBlockStateCount]; + this.immutableBlockStates = new ImmutableBlockState[customBlockCount]; + this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount]; + Arrays.fill(this.blockStateMappings, -1); + } + + @NotNull + @Override + public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) { + return this.immutableBlockStates[stateId - this.vanillaBlockStateCount]; + } + + @Nullable + @Override + public ImmutableBlockState getImmutableBlockState(int stateId) { + if (!isVanillaBlockState(stateId)) { + return this.immutableBlockStates[stateId - this.vanillaBlockStateCount]; + } + return null; } @Override @@ -71,12 +98,15 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem super.clearModelsToGenerate(); this.clearCache(); this.cachedSuggestions.clear(); + this.namespacesInUse.clear(); this.blockStateOverrides.clear(); - this.modBlockStates.clear(); + this.modBlockStateOverrides.clear(); this.byId.clear(); - this.previousClientBoundTags = this.clientBoundTags; - this.clientBoundTags = new HashMap<>(); + this.soundReplacements.clear(); + this.blockStateArranger.clear(); this.appearanceToRealState.clear(); + Arrays.fill(this.blockStateMappings, -1); + Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); } @Override @@ -97,23 +127,39 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } protected void addBlockInternal(Key id, CustomBlock customBlock) { - this.byId.put(id, customBlock); - // generate mod assets - if (Config.generateModAssets()) { - for (ImmutableBlockState state : customBlock.variantProvider().states()) { - this.modBlockStates.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId())); + ExceptionCollector exceptionCollector = new ExceptionCollector<>(); + // 绑定外观状态等 + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + int internalId = state.customBlockState().registryId(); + int appearanceId = state.vanillaBlockState().registryId(); + int index = internalId - this.vanillaBlockStateCount; + ImmutableBlockState previous = this.immutableBlockStates[index]; + // todo 应当提前判断位置 + if (previous != null && !previous.isEmpty()) { + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.bind_failed", + state.toString(), previous.toString(), getBlockOwnerId(previous.customBlockState()).toString())); + continue; + } + this.immutableBlockStates[index] = state; + this.blockStateMappings[internalId] = appearanceId; + this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); + // generate mod assets + if (Config.generateModAssets()) { + this.modBlockStateOverrides.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(appearanceId)); } } + this.byId.put(id, customBlock); + exceptionCollector.throwIfPresent(); } @Override - public ConfigParser parser() { - return this.blockParser; + public ConfigParser[] parsers() { + return new ConfigParser[]{this.blockParser, this.blockStateMappingParser}; } @Override public Map modBlockStates() { - return Collections.unmodifiableMap(this.modBlockStates); + return Collections.unmodifiableMap(this.modBlockStateOverrides); } @Override @@ -126,13 +172,21 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Collections.unmodifiableCollection(this.cachedSuggestions); } + @Nullable + public Key replaceSoundIfExist(Key id) { + return this.soundReplacements.get(id); + } + + @Override + public Map soundReplacements() { + return Collections.unmodifiableMap(this.soundReplacements); + } + public Set namespacesInUse() { return Collections.unmodifiableSet(this.namespacesInUse); } protected void clearCache() { - this.tempRegistryIdConflictMap.clear(); - this.tempBlockAppearanceConvertor.clear(); this.tempVanillaBlockStateModels.clear(); } @@ -161,15 +215,55 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected abstract boolean isVanillaBlock(Key id); - protected abstract int getBlockRegistryId(Key id); - - protected abstract String stateRegistryIdToStateSNBT(int id); - protected abstract Key getBlockOwnerId(int id); protected abstract CustomBlock.Builder platformBuilder(Key id); - public class BlockParser implements ConfigParser { + protected abstract void setVanillaBlockTags(Key id, List tags); + + public abstract int vanillaBlockStateCount(); + + public class BlockStateMappingParser implements SectionConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"}; + + @Override + public String[] sectionId() { + return CONFIG_SECTION_NAME; + } + + @Override + public int loadingSequence() { + return LoadingSequence.BLOCK_STATE_MAPPING; + } + + @Override + public void parseSection(Pack pack, Path path, Map section) throws LocalizedException { + for (Map.Entry entry : section.entrySet()) { + String before = entry.getKey(); + String after = entry.getValue().toString(); + // 先解析为唯一的wrapper + BlockStateWrapper beforeState = createVanillaBlockState(before); + BlockStateWrapper afterState = createVanillaBlockState(before); + if (beforeState == null) { + TranslationManager.instance().log("warning.config.block_state_mapping.invalid_state", path.toString(), before); + return; + } + if (afterState == null) { + TranslationManager.instance().log("warning.config.block_state_mapping.invalid_state", path.toString(), after); + return; + } + int previous = AbstractBlockManager.this.blockStateMappings[beforeState.registryId()]; + if (previous != -1 && previous != afterState.registryId()) { + TranslationManager.instance().log("warning.config.block_state_mapping.conflict", path.toString(), beforeState.toString(), afterState.toString(), BlockRegistryMirror.byId(previous).toString()); + return; + } + AbstractBlockManager.this.blockStateMappings[beforeState.registryId()] = afterState.registryId(); + AbstractBlockManager.this.blockStateArranger.computeIfAbsent(getBlockOwnerId(beforeState), k -> new ArrayList<>()).add(afterState); + } + } + } + + public class BlockParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; @Override @@ -201,7 +295,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem Object clientBoundTags = settings.get("client-bound-tags"); if (clientBoundTags instanceof List list) { List clientSideTags = MiscUtils.getAsStringList(list).stream().filter(ResourceLocation::isValid).toList(); - AbstractBlockManager.this.clientBoundTags.put(getBlockRegistryId(id), clientSideTags); + AbstractBlockManager.this.setVanillaBlockTags(id, clientSideTags); } } } @@ -219,19 +313,16 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem boolean singleState = !stateSection.containsKey("properties"); // 单方块状态 if (singleState) { - int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow( - stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); + int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); // 获取原版外观的注册表id - int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow( - stateSection.get("state"), "warning.config.block.state.missing_state")); + BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state")); Optional[]> blockEntityRenderer = parseBlockEntityRender(stateSection.get("entity-renderer")); - // 为原版外观赋予外观模型并检查模型冲突 - this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(stateSection, "model", "models")); + this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models")); // 设置参数 properties = Map.of(); - appearances = Map.of("", new BlockStateAppearance(appearanceId, blockEntityRenderer)); - variants = Map.of("", new BlockStateVariant("", settings, getInternalBlockId(internalId, appearanceId))); + appearances = Map.of("", new BlockStateAppearance(appearanceState, blockEntityRenderer)); + variants = Map.of("", new BlockStateVariant("", settings, getInternalBlockState(internalId))); } // 多方块状态 else { @@ -239,10 +330,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem appearances = parseBlockAppearances(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances")); variants = parseBlockVariants( ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"), - it -> { - BlockStateAppearance blockStateAppearance = appearances.get(it); - return blockStateAppearance == null ? -1 : blockStateAppearance.stateRegistryId(); - }, settings + appearances::containsKey, settings ); } @@ -258,39 +346,35 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } private Map parseBlockVariants(Map variantsSection, - Function appearanceValidator, + Predicate appearanceValidator, BlockSettings parentSettings) { Map variants = new HashMap<>(); for (Map.Entry entry : variantsSection.entrySet()) { Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); String variantNBT = entry.getKey(); String appearance = ResourceConfigUtils.requireNonEmptyStringOrThrow(variantSection.get("appearance"), "warning.config.block.state.variant.missing_appearance"); - int appearanceId = appearanceValidator.apply(appearance); - if (appearanceId == -1) { + if (!appearanceValidator.test(appearance)) { throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearance); } - int internalId = getInternalBlockId(ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id"), appearanceId); + BlockStateWrapper internalBlockState = getInternalBlockState(ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id")); Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); - variants.put(variantNBT, new BlockStateVariant(appearance, anotherSetting == null ? parentSettings : BlockSettings.ofFullCopy(parentSettings, anotherSetting), internalId)); + variants.put(variantNBT, new BlockStateVariant(appearance, anotherSetting == null ? parentSettings : BlockSettings.ofFullCopy(parentSettings, anotherSetting), internalBlockState)); } return variants; } - private int getInternalBlockId(int internalId, int appearanceId) { - Key baseBlock = getBlockOwnerId(appearanceId); - Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, baseBlock.value() + "_" + internalId); - int internalBlockRegistryId = Optional.ofNullable(AbstractBlockManager.this.internalId2StateId.get(internalBlockId)).orElse(-1); - if (internalBlockRegistryId == -1) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(baseBlock) - 1)); + private BlockStateWrapper getInternalBlockState(int internalId) { + if (internalId >= Config.serverSideBlocks()) { + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", BlockManager.createCustomBlockKey(internalId).asString(), String.valueOf(Config.serverSideBlocks() - 1)); } - return internalBlockRegistryId; + return BlockRegistryMirror.byId(internalId + vanillaBlockStateCount()); } private Map parseBlockAppearances(Map appearancesSection) { Map appearances = new HashMap<>(); for (Map.Entry entry : appearancesSection.entrySet()) { Map appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow( + BlockStateWrapper appearanceId = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow( appearanceSection.get("state"), "warning.config.block.state.missing_state")); this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(appearanceSection, "model", "models")); appearances.put(entry.getKey(), new BlockStateAppearance(appearanceId, parseBlockEntityRender(appearanceSection.get("entity-renderer")))); @@ -316,7 +400,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return properties; } - private void arrangeModelForStateAndVerify(int registryId, Object modelOrModels) { + private void arrangeModelForStateAndVerify(BlockStateWrapper blockStateWrapper, Object modelOrModels) { // 如果没有配置models if (modelOrModels == null) { return; @@ -334,13 +418,13 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } // 拆分方块id与属性 - String blockState = stateRegistryIdToStateSNBT(registryId); + String blockState = blockStateWrapper.toString(); Key blockId = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}'))); String propertyNBT = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']')); // 结合variants JsonElement combinedVariant = GsonHelper.combine(variants); Map overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()); - AbstractBlockManager.this.tempVanillaBlockStateModels.put(registryId, combinedVariant); + AbstractBlockManager.this.tempVanillaBlockStateModels.put(blockStateWrapper.registryId(), combinedVariant); JsonElement previous = overrideMap.get(propertyNBT); if (previous != null && !previous.equals(combinedVariant)) { throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous)); @@ -370,7 +454,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } // 从方块外观的state里获取其原版方块的state id - private int pluginFormattedBlockStateToRegistryId(String blockState) { + private BlockStateWrapper parsePluginFormattedBlockState(String blockState) { // 五种合理情况 // minecraft:note_block:10 // note_block:10 @@ -381,7 +465,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (split.length >= 4) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState); } - int registryId; + BlockStateWrapper wrapper; String stateOrId = split[split.length - 1]; boolean isId = false; int arrangerIndex = 0; @@ -401,14 +485,14 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 获取原版方块的id Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]); try { - List arranger = blockAppearanceArranger.get(block); + List arranger =blockStateArranger.get(block); if (arranger == null) { throw new LocalizedResourceConfigException("warning.config.block.state.unavailable_vanilla", blockState); } if (arrangerIndex >= arranger.size()) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla_id", blockState, String.valueOf(arranger.size() - 1)); } - registryId = arranger.get(arrangerIndex); + wrapper = arranger.get(arrangerIndex); } catch (NumberFormatException e) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", e, blockState); } @@ -418,9 +502,21 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (packedBlockState == null) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState); } - registryId = packedBlockState.registryId(); + wrapper = packedBlockState; } - return registryId; + return wrapper; } } + + public boolean isVanillaBlockState(int id) { + return id < this.vanillaBlockStateCount && id >= 0; + } + + public BlockParser blockParser() { + return blockParser; + } + + public BlockStateMappingParser blockStateMappingParser() { + return blockStateMappingParser; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index 92593999c..d62c103d8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -61,6 +61,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { this.placementFunction = composite(placements); EntityBlockBehavior entityBlockBehavior = this.behavior.getEntityBehavior(); boolean isEntityBlock = entityBlockBehavior != null; + for (Map.Entry entry : variantMapper.entrySet()) { String nbtString = entry.getKey(); CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); @@ -72,20 +73,12 @@ public abstract class AbstractCustomBlock implements CustomBlock { throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); } BlockStateVariant blockStateVariant = entry.getValue(); - - BlockStateAppearance blockStateAppearance = appearances.getOrDefault(blockStateVariant.appearance(), BlockStateAppearance.INVALID); - int stateId; - // This should never happen - if (blockStateAppearance.isInvalid()) { - stateId = appearances.values().iterator().next().stateRegistryId(); - } else { - stateId = blockStateAppearance.stateRegistryId(); - } + BlockStateAppearance blockStateAppearance = appearances.get(blockStateVariant.appearance()); // Late init states ImmutableBlockState state = possibleStates.getFirst(); state.setSettings(blockStateVariant.settings()); - state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(stateId)); - state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); + state.setVanillaBlockState(blockStateAppearance.blockState()); + state.setCustomBlockState(blockStateVariant.blockState()); blockStateAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } @@ -99,7 +92,6 @@ public abstract class AbstractCustomBlock implements CustomBlock { state.setBlockEntityType(entityBlockBehavior.blockEntityType()); } } - this.applyPlatformSettings(); } protected BlockBehavior setupBehavior(List> behaviorConfig) { @@ -124,8 +116,6 @@ public abstract class AbstractCustomBlock implements CustomBlock { }; } - protected abstract void applyPlatformSettings(); - @Override public @Nullable LootTable lootTable() { return this.lootTable; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java index 785d13b17..1246a2bae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java @@ -16,7 +16,7 @@ import java.util.Optional; public interface BlockManager extends Manageable, ModelGenerator { - ConfigParser parser(); + ConfigParser[] parsers(); Collection modelsToGenerate(); @@ -35,9 +35,7 @@ public interface BlockManager extends Manageable, ModelGenerator { Collection cachedSuggestions(); - Map soundMapper(); - - int availableAppearances(Key blockType); + Map soundReplacements(); Key getBlockOwnerId(BlockStateWrapper state); @@ -49,4 +47,11 @@ public interface BlockManager extends Manageable, ModelGenerator { @Nullable BlockStateWrapper createBlockState(String blockState); + + @Nullable + BlockStateWrapper createVanillaBlockState(String blockState); + + static Key createCustomBlockKey(int id) { + return Key.of("craftengine", "custom_" + id); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java index acd70d21e..ccc19d42e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java @@ -5,14 +5,14 @@ public final class BlockRegistryMirror { private static BlockStateWrapper stoneState; public static void init(BlockStateWrapper[] states, BlockStateWrapper state) { - if (blockStates != null) throw new IllegalStateException("block states are already set"); + if (blockStates != null) throw new IllegalStateException("block states have already been set"); blockStates = states; stoneState = state; } - public static BlockStateWrapper stateByRegistryId(int vanillaId) { - if (vanillaId < 0) return stoneState; - return blockStates[vanillaId]; + public static BlockStateWrapper byId(int stateId) { + if (stateId < 0) return stoneState; + return blockStates[stateId]; } public static int size() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java index 88d517857..d393d4b9d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java @@ -15,25 +15,20 @@ public final class BlockSounds { Land 0.3 1 Destroy 1 1 */ - public static final SoundData EMPTY_SOUND = new SoundData(Key.of("minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1); - public static final BlockSounds EMPTY = new BlockSounds(EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND); + public static final BlockSounds EMPTY = new BlockSounds(SoundData.EMPTY, SoundData.EMPTY, SoundData.EMPTY, SoundData.EMPTY, SoundData.EMPTY); private final SoundData breakSound; private final SoundData stepSound; private final SoundData placeSound; private final SoundData hitSound; private final SoundData fallSound; - private final SoundData landSound; - private final SoundData destroySound; - public BlockSounds(SoundData breakSound, SoundData stepSound, SoundData placeSound, SoundData hitSound, SoundData fallSound, SoundData landSound, SoundData destroySound) { + public BlockSounds(SoundData breakSound, SoundData stepSound, SoundData placeSound, SoundData hitSound, SoundData fallSound) { this.breakSound = breakSound; this.stepSound = stepSound; this.placeSound = placeSound; this.hitSound = hitSound; this.fallSound = fallSound; - this.landSound = landSound; - this.destroySound = destroySound; } public static BlockSounds fromMap(Map map) { @@ -43,16 +38,10 @@ public final class BlockSounds { SoundData.create(map.getOrDefault("step", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_15, SoundData.SoundValue.FIXED_1), SoundData.create(map.getOrDefault("place", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8), SoundData.create(map.getOrDefault("hit", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_5), - SoundData.create(map.getOrDefault("fall", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_75), - SoundData.create(map.getOrDefault("land", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_3, SoundData.SoundValue.FIXED_1), - SoundData.create(map.getOrDefault("destroy", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1) + SoundData.create(map.getOrDefault("fall", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_0_5, SoundData.SoundValue.FIXED_0_75) ); } - public SoundData destroySound() { - return destroySound; - } - public SoundData breakSound() { return breakSound; } @@ -69,10 +58,6 @@ public final class BlockSounds { return hitSound; } - public SoundData landSound() { - return landSound; - } - public SoundData fallSound() { return fallSound; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java index 0e2c7acc6..75e39df37 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -5,10 +5,5 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import java.util.Optional; -public record BlockStateAppearance(int stateRegistryId, Optional[]> blockEntityRenderer) { - public static final BlockStateAppearance INVALID = new BlockStateAppearance(-1, Optional.empty()); - - public boolean isInvalid() { - return this.stateRegistryId < 0; - } +public record BlockStateAppearance(BlockStateWrapper blockState, Optional[]> blockEntityRenderer) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java index cfe81345b..8a27403c8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java @@ -3,12 +3,12 @@ package net.momirealms.craftengine.core.block; public class BlockStateVariant { private final String appearance; private final BlockSettings settings; - private final int internalId; + private final BlockStateWrapper blockState; - public BlockStateVariant(String appearance, BlockSettings settings, int internalId) { + public BlockStateVariant(String appearance, BlockSettings settings, BlockStateWrapper blockState) { this.appearance = appearance; this.settings = settings; - this.internalId = internalId; + this.blockState = blockState; } public String appearance() { @@ -19,7 +19,7 @@ public class BlockStateVariant { return settings; } - public int internalRegistryId() { - return internalId; + public BlockStateWrapper blockState() { + return blockState; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java index fb880292e..846900088 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java @@ -1,8 +1,12 @@ package net.momirealms.craftengine.core.block; +import net.momirealms.craftengine.core.util.Key; + public interface BlockStateWrapper { Object literalObject(); int registryId(); + + Key ownerId(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/DelegatingBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/DelegatingBlock.java index ee4b7687f..e3bb1ae92 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/DelegatingBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/DelegatingBlock.java @@ -32,9 +32,11 @@ public interface DelegatingBlock { */ ObjectHolder behaviorDelegate(); + // 其实是错误的做法 @Deprecated boolean isNoteBlock(); + // 其实是错误的做法 @Deprecated boolean isTripwire(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java index 6570e95e8..a6aa4e0f6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java @@ -15,8 +15,4 @@ public final class EmptyBlock extends AbstractCustomBlock { INSTANCE = this; STATE = defaultState(); } - - @Override - protected void applyPlatformSettings() { - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java index a68e0c461..3c16dcae3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java @@ -16,10 +16,6 @@ public final class InactiveCustomBlock extends AbstractCustomBlock { super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null); } - @Override - protected void applyPlatformSettings() { - } - @Override public ImmutableBlockState getBlockState(CompoundTag nbt) { return this.cachedData.computeIfAbsent(nbt, k -> { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java b/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java index db1d653be..5a7f65756 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/PushReaction.java @@ -5,5 +5,7 @@ public enum PushReaction { DESTROY, BLOCK, IGNORE, - PUSH_ONLY + PUSH_ONLY; + + public static final PushReaction[] VALUES = values(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java index 233ad526b..fc3370fac 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.block.properties; -import com.google.common.base.MoreObjects; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index 2e5b56471..3794e85f2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -6,7 +6,7 @@ import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; @@ -31,7 +31,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { } @Override - public ConfigParser parser() { + public IdSectionConfigParser parser() { return this.furnitureParser; } @@ -74,7 +74,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { protected abstract CustomFurniture.Builder furnitureBuilder(); - public class FurnitureParser implements ConfigParser { + public class FurnitureParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" }; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index cc5c6cdfb..11ef5e872 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -371,7 +372,7 @@ public abstract class AbstractFontManager implements FontManager { return this.fonts.computeIfAbsent(key, Font::new); } - public class EmojiParser implements ConfigParser { + public class EmojiParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"emoji", "emojis"}; @Override @@ -438,7 +439,7 @@ public abstract class AbstractFontManager implements FontManager { } } - public class ImageParser implements ConfigParser { + public class ImageParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"images", "image"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 61a06df52..75b4e4038 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -22,6 +22,7 @@ import net.momirealms.craftengine.core.pack.model.select.TrimMaterialSelectPrope import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; @@ -267,7 +268,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected abstract void registerArmorTrimPattern(Collection equipments); - public class EquipmentParser implements ConfigParser { + public class EquipmentParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"equipments", "equipment"}; @Override @@ -310,7 +311,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } } - public class ItemParser implements ConfigParser { + public class ItemParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"}; private boolean isModernFormatRequired() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java index 670407315..c9db7e549 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.UniqueKey; @@ -120,7 +121,7 @@ public abstract class AbstractRecipeManager implements RecipeManager { return true; } - public class RecipeParser implements ConfigParser { + public class RecipeParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"recipes", "recipe"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 2777d7c88..eab304ce6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -27,9 +27,7 @@ import net.momirealms.craftengine.core.pack.obfuscation.ObfA; import net.momirealms.craftengine.core.pack.revision.Revision; import net.momirealms.craftengine.core.pack.revision.Revisions; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; +import net.momirealms.craftengine.core.plugin.config.*; import net.momirealms.craftengine.core.plugin.locale.I18NData; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -374,6 +372,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/internal/configuration/fix_client_visual.yml"); plugin.saveResource("resources/internal/configuration/offset_chars.yml"); plugin.saveResource("resources/internal/configuration/gui.yml"); + plugin.saveResource("resources/internal/configuration/mappings.yml"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/offset/space_split.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/item_browser.png"); plugin.saveResource("resources/internal/resourcepack/assets/minecraft/textures/font/gui/custom/category.png"); @@ -609,6 +608,7 @@ public abstract class AbstractPackManager implements PackManager { return cachedConfigs; } + // todo 本地化日志 private void loadResourceConfigs(Predicate predicate) { long o1 = System.nanoTime(); TreeMap> cachedConfigs = this.updateCachedConfigFiles(); @@ -619,34 +619,62 @@ public abstract class AbstractPackManager implements PackManager { if (!predicate.test(parser)) continue; long t1 = System.nanoTime(); parser.preProcess(); - for (CachedConfigSection cached : entry.getValue()) { - for (Map.Entry configEntry : cached.config().entrySet()) { - String key = configEntry.getKey(); - Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); - try { - if (parser.supportsParsingObject()) { - // do not apply templates - parser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue()); - } else { - if (configEntry.getValue() instanceof Map configSection0) { - Map config = castToMap(configSection0, false); - if ((boolean) config.getOrDefault("debug", false)) { - this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config))); - } - if ((boolean) config.getOrDefault("enable", true)) { - parser.parseSection(cached.pack(), cached.filePath(), id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)); - } - } else { - TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); - } + switch (parser) { + case SectionConfigParser configParser -> { + for (CachedConfigSection cached : entry.getValue()) { + try { + configParser.parseSection(cached.pack(), cached.filePath(), cached.config()); + } catch (LocalizedException e) { + printWarningRecursively(e, cached.filePath(), cached.prefix()); + } catch (Exception e) { + this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(cached.config()), e); } - } catch (LocalizedException e) { - printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key); - } catch (Exception e) { - this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e); } } + case IdObjectConfigParser configParser -> { + for (CachedConfigSection cached : entry.getValue()) { + for (Map.Entry configEntry : cached.config().entrySet()) { + String key = configEntry.getKey(); + Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); + try { + configParser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue()); + } catch (LocalizedException e) { + printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key); + } catch (Exception e) { + this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e); + } + } + } + } + case IdSectionConfigParser configParser -> { + for (CachedConfigSection cached : entry.getValue()) { + for (Map.Entry configEntry : cached.config().entrySet()) { + String key = configEntry.getKey(); + Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); + try { + if (configEntry.getValue() instanceof Map configSection0) { + Map config = castToMap(configSection0, false); + if ((boolean) config.getOrDefault("debug", false)) { + this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config))); + } + if ((boolean) config.getOrDefault("enable", true)) { + configParser.parseSection(cached.pack(), cached.filePath(), id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)); + } + } else { + TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); + } + } catch (LocalizedException e) { + printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key); + } catch (Exception e) { + this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e); + } + } + } + } + default -> { + } } + parser.postProcess(); long t2 = System.nanoTime(); this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms"); @@ -1722,7 +1750,7 @@ public abstract class AbstractPackManager implements PackManager { soundJson = new JsonObject(); } - for (Map.Entry mapper : plugin.blockManager().soundMapper().entrySet()) { + for (Map.Entry mapper : plugin.blockManager().soundReplacements().entrySet()) { Key originalKey = mapper.getKey(); JsonObject empty = new JsonObject(); empty.add("sounds", new JsonArray()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java index 82604386c..1868cd5fe 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java @@ -4,6 +4,7 @@ public final class LoadingSequence { private LoadingSequence() {} public static final int TEMPLATE = 0; + public static final int BLOCK_STATE_MAPPING = 5; public static final int GLOBAL_VAR = 10; public static final int LANG = 20; public static final int TRANSLATION = 30; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 25bb1acf6..31327c6c2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -92,6 +92,8 @@ public abstract class CraftEngine implements Plugin { protected CraftEngine(Consumer reloadEventDispatcher) { instance = this; this.reloadEventDispatcher = reloadEventDispatcher; + ((Logger) LogManager.getRootLogger()).addFilter(new LogFilter()); + ((Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter()); } public static CraftEngine instance() { @@ -105,9 +107,6 @@ public abstract class CraftEngine implements Plugin { RecipeDisplayTypes.init(); SlotDisplayTypes.init(); LegacyRecipeTypes.init(); - ((Logger) LogManager.getRootLogger()).addFilter(new LogFilter()); - ((Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter()); - this.config.load(); } public record ReloadResult(boolean success, long asyncTime, long syncTime) { @@ -281,7 +280,7 @@ public abstract class CraftEngine implements Plugin { // register furniture parser this.packManager.registerConfigSectionParser(this.furnitureManager.parser()); // register block parser - this.packManager.registerConfigSectionParser(this.blockManager.parser()); + this.packManager.registerConfigSectionParsers(this.blockManager.parsers()); // register recipe parser this.packManager.registerConfigSectionParser(this.recipeManager.parser()); // register category parser diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 65681858e..c2da59fa2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -44,6 +44,7 @@ public class Config { protected boolean checkUpdate; protected boolean metrics; protected boolean filterConfigurationPhaseDisconnect; + protected Locale forcedLocale; protected boolean debug$common; protected boolean debug$packet; @@ -123,7 +124,7 @@ public class Config { protected int block$predict_breaking_interval; protected double block$extended_interaction_range; protected boolean block$chunk_relighter; - protected int block$serverside_blocks; + protected int block$serverside_blocks = -1; protected boolean recipe$enable; protected boolean recipe$disable_vanilla_recipes$all; @@ -177,7 +178,7 @@ public class Config { instance = this; } - public void load() { + public boolean updateConfigCache() { // 文件不存在,则保存 if (!Files.exists(this.configFilePath)) { this.plugin.saveResource("config.yml"); @@ -195,13 +196,20 @@ public class Config { this.updateConfigVersion(configFileBytes); } } - // 加载配置文件 - this.loadSettings(); this.lastModified = lastModified; this.size = size; + return true; } } catch (IOException e) { - this.plugin.logger().severe("Failed to load config.yml", e); + this.plugin.logger().severe("Failed to update config.yml", e); + } + return false; + } + + public void load() { + boolean isUpdated = updateConfigCache(); + if (isUpdated) { + loadFullSettings(); } } @@ -240,9 +248,14 @@ public class Config { } } - private void loadSettings() { + public void loadForcedLocale() { YamlDocument config = settings(); - plugin.translationManager().forcedLocale(TranslationManager.parseLocale(config.getString("forced-locale", ""))); + forcedLocale = TranslationManager.parseLocale(config.getString("forced-locale", "")); + } + + public void loadFullSettings() { + YamlDocument config = settings(); + forcedLocale = TranslationManager.parseLocale(config.getString("forced-locale", "")); // basics metrics = config.getBoolean("metrics", false); @@ -379,7 +392,7 @@ public class Config { equipment$sacrificed_vanilla_armor$humanoid_leggings = Key.of(config.getString("equipment.sacrificed-vanilla-armor.humanoid-leggings", "minecraft:trims/entity/humanoid_leggings/chainmail")); // item - item$client_bound_model = config.getBoolean("item.client-bound-model", false); + item$client_bound_model = config.getBoolean("item.client-bound-model", true) && VersionHelper.PREMIUM; item$non_italic_tag = config.getBoolean("item.non-italic-tag", false); item$update_triggers$attack = config.getBoolean("item.update-triggers.attack", false); item$update_triggers$click_in_inventory = config.getBoolean("item.update-triggers.click-in-inventory", false); @@ -394,7 +407,10 @@ public class Config { block$predict_breaking_interval = Math.max(config.getInt("block.predict-breaking.interval", 10), 1); block$extended_interaction_range = Math.max(config.getDouble("block.predict-breaking.extended-interaction-range", 0.5), 0.0); block$chunk_relighter = config.getBoolean("block.chunk-relighter", true); - block$serverside_blocks = config.getInt("block.serverside-blocks", 2000); + if (firstTime) { + block$serverside_blocks = config.getInt("block.serverside-blocks", 2000); + if (block$serverside_blocks < 0) block$serverside_blocks = 0; + } // recipe recipe$enable = config.getBoolean("recipe.enable", true); @@ -445,6 +461,10 @@ public class Config { return MinecraftVersion.parse(version); } + public static Locale forcedLocale() { + return instance.forcedLocale; + } + public static String configVersion() { return instance.configVersion; } @@ -465,6 +485,10 @@ public class Config { return false; } + public static boolean debugBlock() { + return false; + } + public static boolean debugFurniture() { return instance.debug$furniture; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java index 901fbe8a9..48ba108f9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java @@ -1,30 +1,13 @@ package net.momirealms.craftengine.core.plugin.config; -import net.momirealms.craftengine.core.pack.Pack; -import net.momirealms.craftengine.core.plugin.locale.LocalizedException; -import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.NotNull; -import java.nio.file.Path; -import java.util.Map; - public interface ConfigParser extends Comparable { String[] sectionId(); - default void parseSection(Pack pack, Path path, Key id, Map section) throws LocalizedException { - this.parseObject(pack, path, id, section); - } - - default void parseObject(Pack pack, Path path, Key id, Object object) throws LocalizedException { - } - int loadingSequence(); - default boolean supportsParsingObject() { - return false; - } - @Override default int compareTo(@NotNull ConfigParser another) { return Integer.compare(loadingSequence(), another.loadingSequence()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java new file mode 100644 index 000000000..2b2eb5125 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.plugin.config; + +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.Key; + +import java.nio.file.Path; + +public interface IdObjectConfigParser extends ConfigParser { + + default void parseObject(Pack pack, Path path, Key id, Object object) throws LocalizedException { + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java new file mode 100644 index 000000000..b137ebfe6 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.core.plugin.config; + +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.Key; + +import java.nio.file.Path; +import java.util.Map; + +public interface IdSectionConfigParser extends ConfigParser { + + default void parseSection(Pack pack, Path path, Key id, Map section) throws LocalizedException { + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java new file mode 100644 index 000000000..871d1674f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.plugin.config; + +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.locale.LocalizedException; + +import java.nio.file.Path; +import java.util.Map; + +public interface SectionConfigParser extends ConfigParser { + + default void parseSection(Pack pack, Path path, Map section) throws LocalizedException { + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java index 398af24f7..9d1a505aa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.plugin.config.template; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdObjectConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -36,7 +37,7 @@ public class TemplateManagerImpl implements TemplateManager { return this.templateParser; } - public class TemplateParser implements ConfigParser { + public class TemplateParser implements IdObjectConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"templates", "template"}; @Override @@ -49,11 +50,6 @@ public class TemplateManagerImpl implements TemplateManager { return LoadingSequence.TEMPLATE; } - @Override - public boolean supportsParsingObject() { - return true; - } - @Override public void parseObject(Pack pack, Path path, Key id, Object obj) { if (templates.containsKey(id)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java index bb2ab886d..d858deed2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdObjectConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import org.jetbrains.annotations.Nullable; @@ -34,7 +35,7 @@ public class GlobalVariableManager implements Manageable { return this.parser; } - public class GlobalVariableParser implements ConfigParser { + public class GlobalVariableParser implements IdObjectConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"global-variables", "global-variable"}; @Override @@ -47,11 +48,6 @@ public class GlobalVariableManager implements Manageable { return CONFIG_SECTION_NAME; } - @Override - public boolean supportsParsingObject() { - return true; - } - @Override public void parseObject(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Object object) throws LocalizedException { if (object != null) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index 44930a9fc..7edb1900e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.gui.*; @@ -95,7 +96,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { return Optional.ofNullable(this.byId.get(key)); } - public class CategoryParser implements ConfigParser { + public class CategoryParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"categories", "category"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java index d4ec4c773..7c95068a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java @@ -50,8 +50,6 @@ public interface TranslationManager extends Manageable { return miniMessageTranslation(key, null); } - void forcedLocale(Locale locale); - String miniMessageTranslation(String key, @Nullable Locale locale); default Component render(Component component) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index 041436ec6..f1e119a26 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -7,9 +7,7 @@ import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.plugin.PluginProperties; -import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; -import net.momirealms.craftengine.core.plugin.config.TranslationConfigConstructor; +import net.momirealms.craftengine.core.plugin.config.*; import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag; import net.momirealms.craftengine.core.util.AdventureHelper; import org.jetbrains.annotations.NotNull; @@ -38,7 +36,6 @@ public class TranslationManagerImpl implements TranslationManager { private final String langVersion; private final String[] supportedLanguages; private final Map translationFallback = new LinkedHashMap<>(); - private Locale forcedLocale = null; private Locale selectedLocale = DEFAULT_LOCALE; private MiniMessageTranslationRegistry registry; private final Map clientLangData = new HashMap<>(); @@ -67,11 +64,6 @@ public class TranslationManagerImpl implements TranslationManager { return new ConfigParser[] {this.langParser, this.i18nParser}; } - @Override - public void forcedLocale(Locale locale) { - this.forcedLocale = locale; - } - @Override public void delayedLoad() { this.clientLangData.values().forEach(I18NData::processTranslations); @@ -98,8 +90,8 @@ public class TranslationManagerImpl implements TranslationManager { } private void setSelectedLocale() { - if (this.forcedLocale != null) { - this.selectedLocale = forcedLocale; + if (Config.forcedLocale() != null) { + this.selectedLocale = Config.forcedLocale(); return; } @@ -251,7 +243,7 @@ public class TranslationManagerImpl implements TranslationManager { } } - public class I18NParser implements ConfigParser { + public class I18NParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"i18n", "internationalization", "translation", "translations"}; @Override @@ -282,7 +274,7 @@ public class TranslationManagerImpl implements TranslationManager { } } - public class LangParser implements ConfigParser { + public class LangParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"lang", "language", "languages"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java index 9ac5d509d..49dcb3ef7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java @@ -11,6 +11,7 @@ public enum Debugger { FURNITURE(Config::debugFurniture), RESOURCE_PACK(Config::debugResourcePack), ITEM(Config::debugItem), + BLOCK(Config::debugBlock), BLOCK_ENTITY(Config::debugBlockEntity); private final Supplier condition; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index baa955b1a..501200a8a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -97,6 +97,7 @@ public interface NetWorkUser { void removeTrackedChunk(long chunkPos); + @Nullable IntIdentityList clientBlockList(); void setClientBlockList(IntIdentityList integers); diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java index a0892da26..6ca57ab61 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java @@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.*; @@ -65,7 +66,7 @@ public abstract class AbstractSoundManager implements SoundManager { protected abstract void registerSounds(Collection sounds); - public class SongParser implements ConfigParser { + public class SongParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"jukebox_songs", "jukebox_song", "jukebox-songs", "jukebox-song"}; @Override @@ -92,7 +93,7 @@ public abstract class AbstractSoundManager implements SoundManager { } } - public class SoundParser implements ConfigParser { + public class SoundParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"sounds", "sound"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java index 949b5cf74..339c860ae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java @@ -10,6 +10,7 @@ import java.util.Optional; import java.util.function.Supplier; public record SoundData(Key id, SoundValue volume, SoundValue pitch) { + public static final SoundData EMPTY = new SoundData(Key.of("minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1); public static SoundData create(Object obj, SoundValue volume, SoundValue pitch) { if (obj instanceof String key) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Instrument.java b/core/src/main/java/net/momirealms/craftengine/core/util/Instrument.java index 7c1f60dad..a43d10a2a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Instrument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Instrument.java @@ -34,4 +34,6 @@ public enum Instrument { public String id() { return id; } + + public static final Instrument[] VALUES = Instrument.values(); } From 67ff30dceab10a60dd714e7c9f15b6c0afa8c9fa Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 01:12:01 +0800 Subject: [PATCH 196/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/pack/cache/AutoId.java | 200 ++++++++++++++++++ .../core/pack/cache/AutoIdCache.java | 27 --- 2 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java new file mode 100644 index 000000000..15e4cf7fc --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java @@ -0,0 +1,200 @@ +package net.momirealms.craftengine.core.pack.cache; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.momirealms.craftengine.core.util.FileUtils; +import net.momirealms.craftengine.core.util.GsonHelper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public class AutoId { + private final Path cachePath; + private final BiMap forcedIds = HashBiMap.create(128); + + private final Map cachedIds = new HashMap<>(); + private final BitSet occupiedIds = new BitSet(); + private final Map> autoIds = new HashMap<>(); + private int currentAutoId; + private int minId; + private int maxId; + + public AutoId(Path cachePath) { + this.cachePath = cachePath; + } + + public void reset(int startIndex, int endIndex) { + this.minId = startIndex; + this.currentAutoId = startIndex; + this.maxId = endIndex; + this.occupiedIds.clear(); + this.forcedIds.clear(); + this.autoIds.clear(); + this.cachedIds.clear(); + } + + public void arrangeForTheRest() { + // 然后处理自动分配的ID + for (Map.Entry> entry : this.autoIds.entrySet()) { + String name = entry.getKey(); + CompletableFuture future = entry.getValue(); + + // 不应该触发 + if (future.isDone()) { + continue; + } + + // 尝试使用缓存的ID,并且其有效 + Integer cachedId = this.cachedIds.get(name); + if (cachedId != null && !this.occupiedIds.get(cachedId) && cachedId >= this.minId && cachedId <= this.maxId) { + this.occupiedIds.set(cachedId); + future.complete(cachedId); + continue; + } + + // 寻找下一个可用的自动ID + int autoId = findNextAvailableAutoId(); + if (autoId == -1) { + // 没有可用的ID + future.completeExceptionally(new AutoIdExhaustedException(name, this.minId, this.maxId)); + continue; + } + + // 分配找到的ID + this.occupiedIds.set(autoId); + future.complete(autoId); + this.cachedIds.put(name, autoId); + } + + // 清空futureIds,因为所有请求都已处理 + this.autoIds.clear(); + } + + private int findNextAvailableAutoId() { + // 如果已经用尽 + if (this.currentAutoId > this.maxId) { + return -1; + } + // 寻找下一个可用的id + this.currentAutoId = this.occupiedIds.nextClearBit(this.currentAutoId); + // 已经用完了 + if (this.currentAutoId > maxId) { + return -1; + } + // 找到了 + return this.currentAutoId; + } + + // 强制使用某个id,这时候直接标记到occupiedIds,如果被占用,则直接抛出异常 + public CompletableFuture forceId(final String name, int index) { + // 检查ID是否在有效范围内,一般不会在这触发 + if (index < this.minId || index > this.maxId) { + return CompletableFuture.failedFuture(new AutoIdOutOfRangeException(name, index, this.minId, this.maxId)); + } + + // 检查ID是否已被其他名称占用 + String previous = this.forcedIds.inverse().get(index); + if (previous != null && !previous.equals(name)) { + return CompletableFuture.failedFuture(new AutoIdConflictException(previous, index)); + } + + this.forcedIds.put(name, index); + this.cachedIds.remove(name); // 如果曾经被缓存过,那么移除 + return CompletableFuture.completedFuture(index); + } + + // 自动分配id,优先使用缓存的值 + public CompletableFuture autoId(final String name) { + CompletableFuture future = new CompletableFuture<>(); + this.autoIds.put(name, future); + return future; + } + + // 大多数时候通过指令,移除那些已经不再被使用的id,使用完以后记得调用saveCache以保存更改 + public int clearUnusedIds(Predicate predicate) { + List toRemove = new ArrayList<>(); + for (String id : this.cachedIds.keySet()) { + if (predicate.test(id)) { + toRemove.add(id); + } + } + for (String id : toRemove) { + Integer removedId = this.cachedIds.remove(id); + if (removedId != null) { + // 只有当这个ID不是强制ID时才从occupiedIds中移除 + if (!forcedIds.containsValue(removedId)) { + occupiedIds.clear(removedId); + } + } + } + return toRemove.size(); + } + + // 获取已分配的ID(用于调试或查询) + public Integer getId(String name) { + if (forcedIds.containsKey(name)) { + return forcedIds.get(name); + } + return cachedIds.get(name); + } + + // 获取所有已分配的ID映射 + public Map getAllocatedIds() { + Map result = new HashMap<>(); + result.putAll(forcedIds); + result.putAll(cachedIds); + return Collections.unmodifiableMap(result); + } + + // 检查某个ID是否已被占用 + public boolean isIdOccupied(int id) { + return occupiedIds.get(id); + } + + // 从缓存中加载文件 + public void loadCache() throws IOException { + if (!Files.exists(this.cachePath)) { + return; + } + JsonElement element = GsonHelper.readJsonFile(this.cachePath); + if (element instanceof JsonObject jsonObject) { + for (Map.Entry entry : jsonObject.entrySet()) { + if (entry.getValue() instanceof JsonPrimitive primitive) { + int id = primitive.getAsInt(); + this.cachedIds.put(entry.getKey(), id); + } + } + } + } + + // 保存缓存到文件 + public void saveCache() throws IOException { + FileUtils.createDirectoriesSafe(this.cachePath.getParent()); + GsonHelper.writeJsonFile(GsonHelper.get().toJsonTree(this.cachedIds), this.cachePath); + } + + public static class AutoIdConflictException extends RuntimeException { + public AutoIdConflictException(String previousOwner, int id) { + super("ID " + id + " is already occupied by: " + previousOwner); + } + } + + public static class AutoIdOutOfRangeException extends RuntimeException { + public AutoIdOutOfRangeException(String name, int id, int min, int max) { + super("ID " + id + " for '" + name + "' is out of range. Valid range: " + min + "-" + max); + } + } + + public static class AutoIdExhaustedException extends RuntimeException { + public AutoIdExhaustedException(String name, int min, int max) { + super("No available auto ID for '" + name + "'. All IDs in range " + min + "-" + max + " are occupied."); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java deleted file mode 100644 index 1588a2d1b..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoIdCache.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.momirealms.craftengine.core.pack.cache; - -import java.util.BitSet; -import java.util.HashMap; -import java.util.Map; - -public class AutoIdCache { - private final Map forcedIds = new HashMap<>(); - private final Map cachedIds = new HashMap<>(); - private final BitSet occupiedIds = new BitSet(); - private int currentAutoId; - - public AutoIdCache(int startIndex) { - this.currentAutoId = startIndex; - } - - public boolean setForcedId(final String name, int index) { - if (this.occupiedIds.get(index)) { - return false; - } - this.occupiedIds.set(index); - this.forcedIds.put(name, index); - return true; - } - - -} From 511db380f3a80dede46f6691fd4581d8152a489d Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 03:23:01 +0800 Subject: [PATCH 197/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A3=B0=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BlockEventListener.java | 6 +- .../behavior/FlintAndSteelItemBehavior.java | 10 ++-- .../plugin/network/BukkitNetworkManager.java | 59 +++++++++++++------ .../plugin/user/BukkitServerPlayer.java | 4 +- .../core/entity/player/Player.java | 17 +++++- 5 files changed, 67 insertions(+), 29 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 1daab5e9d..4b6dd1711 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.ItemUtils; import net.momirealms.craftengine.core.util.VersionHelper; @@ -168,7 +169,7 @@ public final class BlockEventListener implements Listener { } // play sound - world.playBlockSound(position, state.settings().sounds().breakSound()); + serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK); } } else { // override vanilla block loots @@ -193,7 +194,6 @@ public final class BlockEventListener implements Listener { } }); } - // sound system if (Config.enableSoundSystem()) { Object ownerBlock = BlockStateUtils.getBlockOwner(blockState); @@ -201,7 +201,7 @@ public final class BlockEventListener implements Listener { try { Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); Object breakSound = CoreReflections.field$SoundType$breakSound.get(soundType); - block.getWorld().playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + player.playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to get sound type", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java index 2be578ff4..df3e667e0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java @@ -90,7 +90,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { } // 且没有shift if (!player.isSecondaryUseActive()) { - player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); + player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); } } else { // 玩家觉得自定义方块不可燃,且点击了侧面,那么就要判断火源下方的方块是否可燃,如果不可燃,则补发声音 @@ -113,16 +113,16 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { if (player.isSecondaryUseActive()) { // 如果底部不能燃烧,则燃烧点位为侧面,需要补发 if (!belowCanBurn) { - player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); + player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); player.swingHand(context.getHand()); } } else { - player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); + player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); } } else { // 如果底部方块不可燃烧才补发 if (!belowCanBurn) { - player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); + player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); player.swingHand(context.getHand()); } } @@ -153,7 +153,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { } } } - player.playSound(FLINT_SOUND, firePos, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); + player.playSound(firePos, FLINT_SOUND, SoundSource.BLOCK, 1f, RandomUtils.generateRandomFloat(0.8f, 1.2f)); player.swingHand(context.getHand()); } return InteractionResult.PASS; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 7ced7ec1b..109ecadc9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -50,6 +50,7 @@ import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; +import net.momirealms.craftengine.core.block.BlockSounds; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.font.FontManager; @@ -2286,28 +2287,50 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes int state = buf.readInt(); boolean global = buf.readBoolean(); int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; - Object blockState = BlockStateUtils.idToBlockState(newState); - Object block = BlockStateUtils.getBlockOwner(blockState); - if (BukkitBlockManager.instance().isBlockSoundRemoved(block) && !FastNMS.INSTANCE.method$BlockStateBase$isAir(blockState)) { + if (BlockStateUtils.isVanillaBlock(state)) { + Object blockState = BlockStateUtils.idToBlockState(state); + Object block = BlockStateUtils.getBlockOwner(blockState); + if (BukkitBlockManager.instance().isBlockSoundRemoved(block)) { + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); + Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); + Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); + if (mappedSoundId != null) { + Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty()); + Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); + Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( + mappedBreakSoundHolder, + CoreReflections.instance$SoundSource$BLOCKS, + blockPos.x() + 0.5, + blockPos.y() + 0.5, + blockPos.z() + 0.5, + 1f, + 0.8F, + RandomUtils.generateRandomLong() + ); + user.sendPacket(packet, true); + } + } + } else { + Object blockState = BlockStateUtils.idToBlockState(state); Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); - if (mappedSoundId != null) { - Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty()); - Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); - Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( - mappedBreakSoundHolder, - CoreReflections.instance$SoundSource$BLOCKS, - blockPos.x() + 0.5, - blockPos.y() + 0.5, - blockPos.z() + 0.5, - (FastNMS.INSTANCE.field$SoundType$volume(soundType) + 1.0F) / 2.0F, - FastNMS.INSTANCE.field$SoundType$pitch(soundType) * 0.8F, - RandomUtils.generateRandomLong() - ); - user.sendPacket(packet, true); - } + Object finalSoundId = KeyUtils.toResourceLocation(mappedSoundId == null ? soundId : mappedSoundId); + Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(finalSoundId, Optional.empty()); + Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); + Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( + mappedBreakSoundHolder, + CoreReflections.instance$SoundSource$BLOCKS, + blockPos.x() + 0.5, + blockPos.y() + 0.5, + blockPos.z() + 0.5, + 1f, + 0.8F, + RandomUtils.generateRandomLong() + ); + user.sendPacket(packet, true); } if (newState == state) { return; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index c08d6af2d..f2481c485 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -370,8 +370,8 @@ public class BukkitServerPlayer extends Player { } @Override - public void playSound(Key sound, BlockPos blockPos, SoundSource source, float volume, float pitch) { - platformPlayer().playSound(new Location(null, blockPos.x() + 0.5, blockPos.y() + 0.5, blockPos.z() + 0.5), sound.toString(), SoundUtils.toBukkit(source), volume, pitch); + public void playSound(Position pos, Key sound, SoundSource source, float volume, float pitch) { + platformPlayer().playSound(new Location(null, pos.x(), pos.y(), pos.z()), sound.toString(), SoundUtils.toBukkit(source), volume, pitch); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 9a33c2dbc..6df8b25c7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -6,9 +6,12 @@ import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Position; +import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.WorldPosition; import org.jetbrains.annotations.NotNull; @@ -100,7 +103,19 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void playSound(Key sound, SoundSource source, float volume, float pitch); - public abstract void playSound(Key sound, BlockPos pos, SoundSource source, float volume, float pitch); + public abstract void playSound(Position pos, Key sound, SoundSource source, float volume, float pitch); + + public void playSound(BlockPos pos, Key sound, SoundSource source, float volume, float pitch) { + this.playSound(Vec3d.atCenterOf(pos), sound, source, volume, pitch); + } + + public void playSound(BlockPos pos, SoundData data, SoundSource source) { + this.playSound(pos, data.id(), source, data.volume().get(), data.pitch().get()); + } + + public void playSound(Position pos, SoundData data, SoundSource source) { + this.playSound(pos, data.id(), source, data.volume().get(), data.pitch().get()); + } public abstract void giveItem(Item item); From a7a49dd9ce5bfbca074a5c8b4aa5e3ce447bc532 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 03:23:15 +0800 Subject: [PATCH 198/226] =?UTF-8?q?=E6=B8=85=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/behavior/ChimeBlockBehavior.java | 2 -- .../craftengine/bukkit/plugin/network/BukkitNetworkManager.java | 1 - .../momirealms/craftengine/core/block/properties/Property.java | 1 - gradle.properties | 2 +- 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java index 56e68b810..4b286d52e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChimeBlockBehavior.java @@ -10,10 +10,8 @@ import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; -import java.util.stream.Stream; public class ChimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 109ecadc9..0936fa242 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -50,7 +50,6 @@ import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.advancement.network.AdvancementHolder; import net.momirealms.craftengine.core.advancement.network.AdvancementProgress; -import net.momirealms.craftengine.core.block.BlockSounds; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.font.FontManager; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java index 233ad526b..fc3370fac 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.block.properties; -import com.google.common.base.MoreObjects; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; diff --git a/gradle.properties b/gradle.properties index f078e28e6..e56947eb0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.6 +project_version=0.0.63.7 config_version=46 lang_version=31 project_group=net.momirealms From e64a8d5fed10b8f35bb42701b380cf7f2532afb9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 18:32:02 +0800 Subject: [PATCH 199/226] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../advancement/BukkitAdvancementManager.java | 2 +- .../bukkit/item/behavior/AxeItemBehavior.java | 2 +- .../item/behavior/BlockItemBehavior.java | 6 +- .../behavior/CompostableItemBehavior.java | 2 +- .../behavior/DoubleHighBlockItemBehavior.java | 6 +- .../behavior/FlintAndSteelItemBehavior.java | 2 +- .../item/behavior/FurnitureItemBehavior.java | 6 +- .../LiquidCollisionBlockItemBehavior.java | 6 +- .../item/behavior/WallBlockItemBehavior.java | 6 +- .../bukkit/loot/BukkitVanillaLootManager.java | 2 +- common-files/src/main/resources/config.yml | 5 + .../core/block/AbstractBlockManager.java | 28 +- .../furniture/AbstractFurnitureManager.java | 2 +- .../core/font/AbstractFontManager.java | 42 +- .../core/item/AbstractItemManager.java | 559 ++++++++++-------- .../core/item/behavior/EmptyItemBehavior.java | 2 +- .../item/behavior/ItemBehaviorFactory.java | 2 +- .../core/item/behavior/ItemBehaviors.java | 23 +- .../item/recipe/AbstractRecipeManager.java | 4 +- .../core/pack/AbstractPackManager.java | 76 +-- .../craftengine/core/pack/cache/AutoId.java | 200 ------- .../core/pack/cache/IdAllocator.java | 226 +++++++ .../core/plugin/config/Config.java | 26 + .../plugin/config/IdObjectConfigParser.java | 2 +- .../plugin/config/IdSectionConfigParser.java | 2 +- .../config/template/TemplateManagerImpl.java | 4 +- .../plugin/context/GlobalVariableManager.java | 3 +- .../gui/category/ItemBrowserManagerImpl.java | 2 +- .../LocalizedResourceConfigException.java | 2 +- .../plugin/locale/TranslationManagerImpl.java | 6 +- .../core/sound/AbstractSoundManager.java | 4 +- .../core/util/ResourceConfigUtils.java | 30 + 32 files changed, 715 insertions(+), 575 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java index 1cbff1a44..ee34b3993 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java @@ -120,7 +120,7 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (advancements.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.advancement.duplicate", path, id); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java index e928f9233..2cca3f5ae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java @@ -131,7 +131,7 @@ public class AxeItemBehavior extends ItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { return INSTANCE; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index 45d9c1ef7..a71c46f64 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -234,7 +234,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { Object id = arguments.get("block"); if (id == null) { throw new LocalizedResourceConfigException("warning.config.item.behavior.block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for block_item behavior")); @@ -242,9 +242,9 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); } return new BlockItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CompostableItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CompostableItemBehavior.java index e4d92d241..50015c355 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CompostableItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CompostableItemBehavior.java @@ -79,7 +79,7 @@ public class CompostableItemBehavior extends ItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { double chance = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("chance", 0.55), "chance"); return new CompostableItemBehavior(chance); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java index 122bb18b4..403d8eea3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java @@ -40,7 +40,7 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { Object id = arguments.get("block"); if (id == null) { throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block", new IllegalArgumentException("Missing required parameter 'block' for double_high_block_item behavior")); @@ -48,9 +48,9 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); } return new DoubleHighBlockItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java index df3e667e0..db9c29878 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java @@ -161,7 +161,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key id, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key id, Map arguments) { return INSTANCE; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java index c7caee0a0..82a1f8457 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java @@ -177,7 +177,7 @@ public class FurnitureItemBehavior extends ItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { Object id = arguments.get("furniture"); if (id == null) { throw new LocalizedResourceConfigException("warning.config.item.behavior.furniture.missing_furniture", new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior")); @@ -185,9 +185,9 @@ public class FurnitureItemBehavior extends ItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitFurnitureManager.instance().parser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitFurnitureManager.instance().parser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); } return new FurnitureItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java index 9e058c549..11a852cac 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionBlockItemBehavior.java @@ -70,7 +70,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { Object id = arguments.get("block"); if (id == null) { throw new LocalizedResourceConfigException("warning.config.item.behavior.liquid_collision.missing_block", new IllegalArgumentException("Missing required parameter 'block' for liquid_collision_block_item behavior")); @@ -79,9 +79,9 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); } return new LiquidCollisionBlockItemBehavior(key, offset); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java index bbc48120b..8f8232d4f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java @@ -35,7 +35,7 @@ public class WallBlockItemBehavior extends BlockItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key key, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { Object id = arguments.get("block"); if (id == null) { throw new LocalizedResourceConfigException("warning.config.item.behavior.wall_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for wall_block_item behavior")); @@ -43,9 +43,9 @@ public class WallBlockItemBehavior extends BlockItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false)); + BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); } return new WallBlockItemBehavior(key); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java index 31ae7e70d..805808e92 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java @@ -105,7 +105,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("type"), "warning.config.vanilla_loot.missing_type"); VanillaLoot.Type typeEnum; try { diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 6a773846c..4f14758e1 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -150,6 +150,11 @@ item: click-in-inventory: false # this option won't work for players in creative mode drop: false pick-up: false + # Decided the starting value for automatic custom model data assignment. + custom-model-data-starting-value: + default: 10000 + overrides: + paper: 20000 equipment: # The sacrificed-vanilla-armor argument determines which vanilla armor gets completely removed (loses all its trims) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index aeeebaea8..9cb662404 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.plugin.config.*; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.*; import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; @@ -238,6 +237,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem @Override public void parseSection(Pack pack, Path path, Map section) throws LocalizedException { + ExceptionCollector exceptionCollector = new ExceptionCollector<>(); for (Map.Entry entry : section.entrySet()) { String before = entry.getKey(); String after = entry.getValue().toString(); @@ -245,21 +245,25 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem BlockStateWrapper beforeState = createVanillaBlockState(before); BlockStateWrapper afterState = createVanillaBlockState(before); if (beforeState == null) { - TranslationManager.instance().log("warning.config.block_state_mapping.invalid_state", path.toString(), before); - return; + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", before)); + continue; } if (afterState == null) { - TranslationManager.instance().log("warning.config.block_state_mapping.invalid_state", path.toString(), after); - return; + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", after)); + continue; } int previous = AbstractBlockManager.this.blockStateMappings[beforeState.registryId()]; if (previous != -1 && previous != afterState.registryId()) { - TranslationManager.instance().log("warning.config.block_state_mapping.conflict", path.toString(), beforeState.toString(), afterState.toString(), BlockRegistryMirror.byId(previous).toString()); - return; + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.conflict", + beforeState.toString(), + afterState.toString(), + BlockRegistryMirror.byId(previous).toString())); + continue; } AbstractBlockManager.this.blockStateMappings[beforeState.registryId()] = afterState.registryId(); AbstractBlockManager.this.blockStateArranger.computeIfAbsent(getBlockOwnerId(beforeState), k -> new ArrayList<>()).add(afterState); } + exceptionCollector.throwIfPresent(); } } @@ -277,19 +281,19 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (isVanillaBlock(id)) { - parseVanillaBlock(pack, path, id, section); + parseVanillaBlock(id, section); } else { // check duplicated config if (AbstractBlockManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.block.duplicate"); } - parseCustomBlock(pack, path, id, section); + parseCustomBlock(id, section); } } - private void parseVanillaBlock(Pack pack, Path path, Key id, Map section) { + private void parseVanillaBlock(Key id, Map section) { Map settings = MiscUtils.castToMap(section.get("settings"), true); if (settings != null) { Object clientBoundTags = settings.get("client-bound-tags"); @@ -300,7 +304,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - private void parseCustomBlock(Pack pack, Path path, Key id, Map section) { + private void parseCustomBlock(Key id, Map section) { // 获取方块设置 BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true)); // 读取基础外观配置 diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index 3794e85f2..0d5a714d9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -89,7 +89,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { @SuppressWarnings("unchecked") @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.furniture.duplicate"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 11ef5e872..89721f922 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -386,18 +386,18 @@ public abstract class AbstractFontManager implements FontManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (emojis.containsKey(id)) { - throw new LocalizedResourceConfigException("warning.config.emoji.duplicate", path, id); + throw new LocalizedResourceConfigException("warning.config.emoji.duplicate"); } String permission = (String) section.get("permission"); Object keywordsRaw = section.get("keywords"); if (keywordsRaw == null) { - throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords", path, id); + throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords"); } List keywords = MiscUtils.getAsStringList(keywordsRaw); if (keywords.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords", path, id); + throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords"); } Object rawContent = section.getOrDefault("content", ""); String content; @@ -416,7 +416,7 @@ public abstract class AbstractFontManager implements FontManager { if (bitmapImage.isPresent()) { image = bitmapImage.get().miniMessageAt(0, 0); } else { - throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage); + throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage); } } else if (split.length == 4) { Key imageId = new Key(split[0], split[1]); @@ -425,13 +425,13 @@ public abstract class AbstractFontManager implements FontManager { try { image = bitmapImage.get().miniMessageAt(Integer.parseInt(split[2]), Integer.parseInt(split[3])); } catch (ArrayIndexOutOfBoundsException e) { - throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage); + throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage); } } else { - throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage); + throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage); } } else { - throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage); + throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage); } } Emoji emoji = new Emoji(content, permission, image, keywords); @@ -453,24 +453,24 @@ public abstract class AbstractFontManager implements FontManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (images.containsKey(id)) { - throw new LocalizedResourceConfigException("warning.config.image.duplicate", path, id); + throw new LocalizedResourceConfigException("warning.config.image.duplicate"); } Object file = section.get("file"); if (file == null) { - throw new LocalizedResourceConfigException("warning.config.image.missing_file", path, id); + throw new LocalizedResourceConfigException("warning.config.image.missing_file"); } String resourceLocation = CharacterUtils.replaceBackslashWithSlash(file.toString()); if (!ResourceLocation.isValid(resourceLocation)) { - throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", path, id, resourceLocation); + throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", resourceLocation); } String fontName = section.getOrDefault("font", "minecraft:default").toString(); if (!ResourceLocation.isValid(fontName)) { - throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", path, id, fontName); + throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", fontName); } Key fontKey = Key.withDefaultNamespace(fontName, id.namespace()); @@ -478,7 +478,7 @@ public abstract class AbstractFontManager implements FontManager { List chars; Object charsObj = ResourceConfigUtils.get(section, "chars", "char"); if (charsObj == null) { - throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id); + throw new LocalizedResourceConfigException("warning.config.image.missing_char"); } if (charsObj instanceof List list) { chars = MiscUtils.getAsStringList(list).stream().map(it -> { @@ -489,7 +489,7 @@ public abstract class AbstractFontManager implements FontManager { } }).toList(); if (chars.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id); + throw new LocalizedResourceConfigException("warning.config.image.missing_char"); } } else { if (charsObj instanceof Integer integer) { @@ -497,7 +497,7 @@ public abstract class AbstractFontManager implements FontManager { } else { String character = charsObj.toString(); if (character.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id); + throw new LocalizedResourceConfigException("warning.config.image.missing_char"); } if (character.length() == 1) { chars = List.of(character.toCharArray()); @@ -522,7 +522,7 @@ public abstract class AbstractFontManager implements FontManager { for (int codepoint : codepoints) { if (font.isCodepointInUse(codepoint)) { BitmapImage image = font.bitmapImageByCodepoint(codepoint); - throw new LocalizedResourceConfigException("warning.config.image.codepoint_conflict", path, id, + throw new LocalizedResourceConfigException("warning.config.image.codepoint_conflict", fontKey.toString(), CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)), new String(Character.toChars(codepoint)), @@ -530,12 +530,12 @@ public abstract class AbstractFontManager implements FontManager { } } if (codepoints.length == 0) { - throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id); + throw new LocalizedResourceConfigException("warning.config.image.missing_char"); } codepointGrid[i] = codepoints; if (size == -1) size = codepoints.length; if (size != codepoints.length) { - throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid", path, id); + throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid"); } } @@ -558,14 +558,14 @@ public abstract class AbstractFontManager implements FontManager { return; } } else { - throw new LocalizedResourceConfigException("warning.config.image.missing_height", path, id); + throw new LocalizedResourceConfigException("warning.config.image.missing_height"); } } int height = ResourceConfigUtils.getAsInt(heightObj, "height"); int ascent = ResourceConfigUtils.getAsInt(section.getOrDefault("ascent", height - 1), "ascent"); if (height < ascent) { - throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", path, id, String.valueOf(height), String.valueOf(ascent)); + throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", String.valueOf(height), String.valueOf(ascent)); } BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, resourceLocation, codepointGrid); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 75b4e4038..b31c26ff3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; +import net.momirealms.craftengine.core.pack.cache.IdAllocator; import net.momirealms.craftengine.core.pack.model.*; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; @@ -32,8 +33,10 @@ import net.momirealms.craftengine.core.util.*; import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.type.Either; +import java.io.IOException; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Stream; @@ -282,7 +285,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractItemManager.this.equipments.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.equipment.duplicate"); } @@ -313,6 +316,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl public class ItemParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"}; + private final Map idAllocators = new HashMap<>(); private boolean isModernFormatRequired() { return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4); @@ -322,6 +326,18 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return Config.packMinVersion().isBelow(MinecraftVersions.V1_21_4); } + private boolean needsCustomModelDataCompatibility() { + return Config.packMinVersion().isBelow(MinecraftVersions.V1_21_2); + } + + private boolean needsItemModelCompatibility() { + return Config.packMaxVersion().isAbove(MinecraftVersions.V1_21_2); + } + + public Map idAllocators() { + return this.idAllocators; + } + @Override public String[] sectionId() { return CONFIG_SECTION_NAME; @@ -333,261 +349,326 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void preProcess() { + this.idAllocators.clear(); + } + + @Override + public void postProcess() { + for (Map.Entry entry : this.idAllocators.entrySet()) { + entry.getValue().processPendingAllocations(); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + AbstractItemManager.this.plugin.logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e); + } + } + } + + // 创建或获取已有的自动分配器 + private IdAllocator getOrCreateIdAllocator(Key key) { + return this.idAllocators.computeIfAbsent(key, k -> { + IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("custom-model-data").resolve(k.value() + ".json")); + newAllocator.reset(Config.customModelDataStartingValue(k), 16_777_216); + try { + newAllocator.loadFromCache(); + } catch (IOException e) { + AbstractItemManager.this.plugin.logger().warn("Error while loading custom model data from cache for material " + k.asString(), e); + } + return newAllocator; + }); + } + + @Override + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractItemManager.this.customItemsById.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.item.duplicate"); } - + // 创建UniqueKey,仅缓存用 UniqueKey uniqueId = UniqueKey.create(id); // 判断是不是原版物品 boolean isVanillaItem = isVanillaItem(id); - Key material = Key.from(isVanillaItem ? id.value() : ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ENGLISH)); - Key clientBoundMaterial = section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ENGLISH)) : material; - // 如果是原版物品,那么custom-model-data只能是0,即使用户设置了其他值 - int customModelData = isVanillaItem ? 0 : ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data"); - boolean clientBoundModel = section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel(); - if (customModelData < 0) { - throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData)); - } - if (customModelData > 16_777_216) { - throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData)); - } - // item-model值 - Key itemModelKey = null; + // 读取服务端侧材质 + Key material = isVanillaItem ? id : Key.from(ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ROOT)); + // 读取客户端侧材质 + Key clientBoundMaterial = VersionHelper.PREMIUM && section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ROOT)) : material; - CustomItem.Builder itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial); - boolean hasItemModelSection = section.containsKey("item-model"); + // custom model data + CompletableFuture customModelDataFuture; - // 如果custom-model-data不为0 - if (customModelData > 0) { - if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData)); - else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); - } - // 如果没有item-model选项被配置,同时这个物品又含有 model 区域 - else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) { - // 那么使用物品id当成item-model的值 - itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString()); - // 但是有个前提,id必须是有效的resource location - if (ResourceLocation.isValid(itemModelKey.toString())) { - if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey)); - else itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); - } else { - itemModelKey = null; - } - } - - // 如果有item-model - if (hasItemModelSection && VersionHelper.isOrAbove1_21_2()) { - itemModelKey = Key.from(section.get("item-model").toString()); - if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey)); - else itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); - } - - // 对于不重要的配置,可以仅警告,不返回 - ExceptionCollector collector = new ExceptionCollector<>(); - - // 应用物品数据 - try { - applyDataModifiers(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier); - } catch (LocalizedResourceConfigException e) { - collector.add(e); - } - // 应用客户端侧数据 - try { - if (VersionHelper.PREMIUM) { - applyDataModifiers(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier); - } - } catch (LocalizedResourceConfigException e) { - collector.add(e); - } - - // 如果不是原版物品,那么加入ce的标识符 - if (!isVanillaItem) - itemBuilder.dataModifier(new IdModifier<>(id)); - - // 事件 - Map>> eventTriggerListMap; - try { - eventTriggerListMap = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")); - } catch (LocalizedResourceConfigException e) { - collector.add(e); - eventTriggerListMap = Map.of(); - } - - // 设置 - ItemSettings settings; - try { - settings = Optional.ofNullable(ResourceConfigUtils.get(section, "settings")) - .map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true))) - .map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it) - .orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem)); - } catch (LocalizedResourceConfigException e) { - collector.add(e); - settings = ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem); - } - - // 行为 - List behaviors; - try { - behaviors = ItemBehaviors.fromObj(pack, path, id, ResourceConfigUtils.get(section, "behavior", "behaviors")); - } catch (LocalizedResourceConfigException e) { - collector.add(e); - behaviors = Collections.emptyList(); - } - - // 如果有物品更新器 - if (section.containsKey("updater")) { - Map updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater"); - List versions = new ArrayList<>(2); - for (Map.Entry entry : updater.entrySet()) { - try { - int version = Integer.parseInt(entry.getKey()); - versions.add(new ItemUpdateConfig.Version( - version, - ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0]) - )); - } catch (NumberFormatException ignored) { - } - } - ItemUpdateConfig config = new ItemUpdateConfig(versions); - itemBuilder.updater(config); - itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion())); - } - - // 构建自定义物品 - CustomItem customItem = itemBuilder - .isVanillaItem(isVanillaItem) - .behaviors(behaviors) - .settings(settings) - .events(eventTriggerListMap) - .build(); - - // 添加到缓存 - addCustomItem(customItem); - - // 如果有类别,则添加 - if (section.containsKey("category")) { - AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList()); - } - - // 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model - Map modelSection = MiscUtils.castToMap(section.get("model"), true); - Map legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true); - if (modelSection == null && legacyModelSection == null) { - collector.throwIfPresent(); - return; - } - - boolean needsModelSection = isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null); - // 只对自定义物品有这个限制 if (!isVanillaItem) { - // 既没有模型值也没有item-model - if (customModelData == 0 && itemModelKey == null) { - collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id")); + // 如果用户指定了,说明要手动分配,不管他是什么版本,都强制设置模型值 + if (section.containsKey("custom-model-data")) { + int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data"); + if (customModelData < 0) { + throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData)); + } + if (customModelData > 16_777_216) { + throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData)); + } + customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).assignFixedId(id.asString(), customModelData); } - } - - // 新版格式 - ItemModel modernModel = null; - // 旧版格式 - TreeSet legacyOverridesModels = null; - // 如果需要支持新版item model 或者用户需要旧版本兼容,但是没配置legacy-model - if (needsModelSection) { - // 1.21.4+必须要配置model区域,如果不需要高版本兼容,则可以只写legacy-model - if (modelSection == null) { - collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model")); - return; - } - try { - modernModel = ItemModels.fromMap(modelSection); - for (ModelGeneration generation : modernModel.modelsToGenerate()) { - prepareModelGeneration(generation); + // 用户没指定custom-model-data,则看当前资源包版本兼容需求 + else { + // 如果最低版本要1.21.1以下支持 + if (needsCustomModelDataCompatibility()) { + customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).requestAutoId(id.asString()); } - } catch (LocalizedResourceConfigException e) { - collector.addAndThrow(e); - } - } - // 如果需要旧版本兼容 - if (needsLegacyCompatibility()) { - if (legacyModelSection != null) { - try { - LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData); - for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) { - prepareModelGeneration(generation); - } - legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides()); - } catch (LocalizedResourceConfigException e) { - collector.addAndThrow(e); - } - } else { - legacyOverridesModels = new TreeSet<>(); - processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData); - if (legacyOverridesModels.isEmpty()) { - collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString())); - } - } - } - - // 自定义物品的model处理 - if (!isVanillaItem) { - // 这个item-model是否存在,且是原版item-model - boolean isVanillaItemModel = itemModelKey != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModelKey); - // 使用了自定义模型值 - if (customModelData != 0) { - // 如果用户主动设置了item-model且为原版物品,则使用item-model为基础模型,否则使用其视觉材质对应的item-model - Key finalBaseModel = isVanillaItemModel ? itemModelKey : clientBoundMaterial; - // 检查cmd冲突 - Map conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(finalBaseModel, k -> new HashMap<>()); - if (conflict.containsKey(customModelData)) { - collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString())); - } - conflict.put(customModelData, id); - // 添加新版item model - if (isModernFormatRequired() && modernModel != null) { - TreeMap map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>()); - map.put(customModelData, new ModernItemModel( - modernModel, - ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), - ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") - )); - } - // 添加旧版 overrides - if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { - TreeSet lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>()); - lom.addAll(legacyOverridesModels); - } - } else if (isVanillaItemModel) { - collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModelKey.asString())); - } - - // 使用了item-model组件,且不是原版物品的 - if (itemModelKey != null && !isVanillaItemModel) { - if (isModernFormatRequired() && modernModel != null) { - AbstractItemManager.this.modernItemModels1_21_4.put(itemModelKey, new ModernItemModel( - modernModel, - ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), - ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") - )); - } - if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { - TreeSet lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModelKey, k -> new TreeSet<>()); - lom.addAll(legacyOverridesModels); + // 否则不主动分配模型值 + else { + customModelDataFuture = CompletableFuture.completedFuture(0); } } } else { - // 原版物品的item model覆写 - if (isModernFormatRequired()) { - AbstractItemManager.this.modernItemModels1_21_4.put(id, new ModernItemModel( - modernModel, - ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), - ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") - )); - } + // 原版物品不应该有这个 + customModelDataFuture = CompletableFuture.completedFuture(0); } - // 抛出异常 - collector.throwIfPresent(); + // 是否使用客户端侧模型 + boolean clientBoundModel = VersionHelper.PREMIUM && (section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel()); + + // 当模型值完成分配的时候 + customModelDataFuture.thenAccept(customModelData -> ResourceConfigUtils.runCatching(path, node, () -> { + + // item model + Key itemModel = null; + + // 如果这个版本可以使用 item model + if (!isVanillaItem && needsItemModelCompatibility()) { + // 如果用户主动设定了item model,那么肯定要设置 + if (section.containsKey("item-model")) { + itemModel = Key.from(section.get("item-model").toString()); + } + // 用户没设置item model也没设置custom model data,那么为他生成一个基于物品id的item model + else if (customModelData == 0) { + itemModel = id; + } + // 用户没设置item model但是有custom model data,那么就使用custom model data + } + + CustomItem.Builder itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial); + if (customModelData > 0) { + if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData)); + else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); + } + if (itemModel != null) { + if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModel)); + else itemBuilder.dataModifier(new ItemModelModifier<>(itemModel)); + } + + // 对于不重要的配置,可以仅警告,不返回 + ExceptionCollector collector = new ExceptionCollector<>(); + + // 应用物品数据 + try { + applyDataModifiers(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier); + } catch (LocalizedResourceConfigException e) { + collector.add(e); + } + + // 应用客户端侧数据 + try { + if (VersionHelper.PREMIUM) { + applyDataModifiers(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier); + } + } catch (LocalizedResourceConfigException e) { + collector.add(e); + } + + // 如果不是原版物品,那么加入ce的标识符 + if (!isVanillaItem) + itemBuilder.dataModifier(new IdModifier<>(id)); + + // 事件 + Map>> eventTriggerListMap; + try { + eventTriggerListMap = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")); + } catch (LocalizedResourceConfigException e) { + collector.add(e); + eventTriggerListMap = Map.of(); + } + + // 设置 + ItemSettings settings; + try { + settings = Optional.ofNullable(ResourceConfigUtils.get(section, "settings")) + .map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true))) + .map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it) + .orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem)); + } catch (LocalizedResourceConfigException e) { + collector.add(e); + settings = ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem); + } + + // 行为 + List behaviors; + try { + behaviors = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(section, "behavior", "behaviors"), map -> ItemBehaviors.fromMap(pack, path, node, id, map)); + } catch (LocalizedResourceConfigException e) { + collector.add(e); + behaviors = Collections.emptyList(); + } + + // 如果有物品更新器 + if (section.containsKey("updater")) { + Map updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater"); + List versions = new ArrayList<>(2); + for (Map.Entry entry : updater.entrySet()) { + try { + int version = Integer.parseInt(entry.getKey()); + versions.add(new ItemUpdateConfig.Version( + version, + ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0]) + )); + } catch (NumberFormatException ignored) { + } + } + ItemUpdateConfig config = new ItemUpdateConfig(versions); + itemBuilder.updater(config); + itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion())); + } + + // 构建自定义物品 + CustomItem customItem = itemBuilder + .isVanillaItem(isVanillaItem) + .behaviors(behaviors) + .settings(settings) + .events(eventTriggerListMap) + .build(); + + // 添加到缓存 + addCustomItem(customItem); + + // 如果有类别,则添加 + if (section.containsKey("category")) { + AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList()); + } + + /* + * ======================== + * + * 模型配置分界线 + * + * ======================== + */ + + // 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model + Map modelSection = MiscUtils.castToMap(section.get("model"), true); + Map legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true); + if (modelSection == null && legacyModelSection == null) { + collector.throwIfPresent(); + return; + } + + boolean needsModelSection = isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null); + // 只对自定义物品有这个限制,既没有模型值也没有item-model + if (!isVanillaItem && customModelData == 0 && itemModel == null) { + collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id")); + } + + // 新版格式 + ItemModel modernModel = null; + // 旧版格式 + TreeSet legacyOverridesModels = null; + // 如果需要支持新版item model 或者用户需要旧版本兼容,但是没配置legacy-model + if (needsModelSection) { + // 1.21.4+必须要配置model区域,如果不需要高版本兼容,则可以只写legacy-model + if (modelSection == null) { + collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model")); + return; + } + try { + modernModel = ItemModels.fromMap(modelSection); + for (ModelGeneration generation : modernModel.modelsToGenerate()) { + prepareModelGeneration(generation); + } + } catch (LocalizedResourceConfigException e) { + collector.addAndThrow(e); + } + } + // 如果需要旧版本兼容 + if (needsLegacyCompatibility()) { + if (legacyModelSection != null) { + try { + LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData); + for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) { + prepareModelGeneration(generation); + } + legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides()); + } catch (LocalizedResourceConfigException e) { + collector.addAndThrow(e); + } + } else { + legacyOverridesModels = new TreeSet<>(); + processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData); + if (legacyOverridesModels.isEmpty()) { + collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString())); + } + } + } + + // 自定义物品的model处理 + if (!isVanillaItem) { + // 这个item-model是否存在,且是原版item-model + boolean isVanillaItemModel = itemModel != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModel); + // 使用了自定义模型值 + if (customModelData != 0) { + // 如果用户主动设置了item-model且为原版物品,则使用item-model为基础模型,否则使用其视觉材质对应的item-model + Key finalBaseModel = isVanillaItemModel ? itemModel : clientBoundMaterial; + // 检查cmd冲突 + Map conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(finalBaseModel, k -> new HashMap<>()); + if (conflict.containsKey(customModelData)) { + collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString())); + } + conflict.put(customModelData, id); + // 添加新版item model + if (isModernFormatRequired() && modernModel != null) { + TreeMap map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>()); + map.put(customModelData, new ModernItemModel( + modernModel, + ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), + ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") + )); + } + // 添加旧版 overrides + if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { + TreeSet lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>()); + lom.addAll(legacyOverridesModels); + } + } else if (isVanillaItemModel) { + collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModel.asString())); + } + + // 使用了item-model组件,且不是原版物品的 + if (itemModel != null && !isVanillaItemModel) { + if (isModernFormatRequired() && modernModel != null) { + AbstractItemManager.this.modernItemModels1_21_4.put(itemModel, new ModernItemModel( + modernModel, + ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), + ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") + )); + } + if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { + TreeSet lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModel, k -> new TreeSet<>()); + lom.addAll(legacyOverridesModels); + } + } + } else { + // 原版物品的item model覆写 + if (isModernFormatRequired()) { + AbstractItemManager.this.modernItemModels1_21_4.put(id, new ModernItemModel( + modernModel, + ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), + ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") + )); + } + } + + // 抛出异常 + collector.throwIfPresent(); + + }, () -> GsonHelper.get().toJson(section))); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/EmptyItemBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/EmptyItemBehavior.java index 68c0c1e49..6546c56c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/EmptyItemBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/EmptyItemBehavior.java @@ -13,7 +13,7 @@ public class EmptyItemBehavior extends ItemBehavior { public static class Factory implements ItemBehaviorFactory { @Override - public ItemBehavior create(Pack pack, Path path, Key id, Map arguments) { + public ItemBehavior create(Pack pack, Path path, String node, Key id, Map arguments) { return INSTANCE; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviorFactory.java b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviorFactory.java index 4b5ebe888..0f90c6223 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviorFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviorFactory.java @@ -8,5 +8,5 @@ import java.util.Map; public interface ItemBehaviorFactory { - ItemBehavior create(Pack pack, Path path, Key id, Map arguments); + ItemBehavior create(Pack pack, Path path, String node, Key id, Map arguments); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java index 6ab3f1d38..21399de3d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java @@ -23,7 +23,7 @@ public class ItemBehaviors { .register(ResourceKey.create(Registries.ITEM_BEHAVIOR_FACTORY.location(), key), factory); } - public static ItemBehavior fromMap(Pack pack, Path path, Key id, Map map) { + public static ItemBehavior fromMap(Pack pack, Path path, String node, Key id, Map map) { if (map == null || map.isEmpty()) return EmptyItemBehavior.INSTANCE; String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.item.behavior.missing_type"); Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); @@ -31,25 +31,6 @@ public class ItemBehaviors { if (factory == null) { throw new LocalizedResourceConfigException("warning.config.item.behavior.invalid_type", type); } - return factory.create(pack, path, id, map); - } - - public static List fromList(Pack pack, Path path, Key id, List> list) { - List behaviors = new ArrayList<>(list.size()); - for (Map map : list) { - behaviors.add(fromMap(pack, path, id, map)); - } - return behaviors; - } - - @SuppressWarnings("unchecked") - public static List fromObj(Pack pack, Path path, Key id, Object behaviorObj) { - if (behaviorObj instanceof Map) { - return List.of(fromMap(pack, path, id, MiscUtils.castToMap(behaviorObj, false))); - } else if (behaviorObj instanceof List) { - return fromList(pack, path, id, (List>) behaviorObj); - } else { - return List.of(); - } + return factory.create(pack, path, node, id, map); } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java index c9db7e549..6a9fc4273 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java @@ -135,10 +135,10 @@ public abstract class AbstractRecipeManager implements RecipeManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (!Config.enableRecipeSystem()) return; if (AbstractRecipeManager.this.byId.containsKey(id)) { - throw new LocalizedResourceConfigException("warning.config.recipe.duplicate", path, id); + throw new LocalizedResourceConfigException("warning.config.recipe.duplicate"); } Recipe recipe = RecipeSerializers.fromMap(id, section); try { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index eab304ce6..268e9bdc9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -608,7 +608,6 @@ public abstract class AbstractPackManager implements PackManager { return cachedConfigs; } - // todo 本地化日志 private void loadResourceConfigs(Predicate predicate) { long o1 = System.nanoTime(); TreeMap> cachedConfigs = this.updateCachedConfigFiles(); @@ -622,13 +621,12 @@ public abstract class AbstractPackManager implements PackManager { switch (parser) { case SectionConfigParser configParser -> { for (CachedConfigSection cached : entry.getValue()) { - try { - configParser.parseSection(cached.pack(), cached.filePath(), cached.config()); - } catch (LocalizedException e) { - printWarningRecursively(e, cached.filePath(), cached.prefix()); - } catch (Exception e) { - this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(cached.config()), e); - } + ResourceConfigUtils.runCatching( + cached.filePath(), + cached.prefix(), + () -> configParser.parseSection(cached.pack(), cached.filePath(), cached.config()), + () -> GsonHelper.get().toJson(cached.config()) + ); } } case IdObjectConfigParser configParser -> { @@ -636,13 +634,13 @@ public abstract class AbstractPackManager implements PackManager { for (Map.Entry configEntry : cached.config().entrySet()) { String key = configEntry.getKey(); Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); - try { - configParser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue()); - } catch (LocalizedException e) { - printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key); - } catch (Exception e) { - this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e); - } + String node = cached.prefix() + "." + key; + ResourceConfigUtils.runCatching( + cached.filePath(), + node, + () -> configParser.parseObject(cached.pack(), cached.filePath(), node, id, configEntry.getValue()), + () -> GsonHelper.get().toJson(configEntry.getValue()) + ); } } } @@ -651,49 +649,37 @@ public abstract class AbstractPackManager implements PackManager { for (Map.Entry configEntry : cached.config().entrySet()) { String key = configEntry.getKey(); Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); - try { - if (configEntry.getValue() instanceof Map configSection0) { - Map config = castToMap(configSection0, false); - if ((boolean) config.getOrDefault("debug", false)) { - this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config))); - } - if ((boolean) config.getOrDefault("enable", true)) { - configParser.parseSection(cached.pack(), cached.filePath(), id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)); - } - } else { - TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); - } - } catch (LocalizedException e) { - printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key); - } catch (Exception e) { - this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e); + if (!(configEntry.getValue() instanceof Map section)) { + TranslationManager.instance().log("warning.config.structure.not_section", + cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); + continue; } + Map config = castToMap(section, false); + if ((boolean) config.getOrDefault("debug", false)) { + this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config))); + } + if (!(boolean) config.getOrDefault("enable", true)) { + continue; + } + String node = cached.prefix() + "." + key; + ResourceConfigUtils.runCatching( + cached.filePath(), + node, + () -> configParser.parseSection(cached.pack(), cached.filePath(), node, id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)), + () -> GsonHelper.get().toJson(section) + ); } } } default -> { } } - parser.postProcess(); long t2 = System.nanoTime(); this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms"); } } - private void printWarningRecursively(LocalizedException e, Path path, String prefix) { - for (Throwable t : e.getSuppressed()) { - if (t instanceof LocalizedException suppressed) { - printWarningRecursively(suppressed, path, prefix); - } - } - if (e instanceof LocalizedResourceConfigException exception) { - exception.setPath(path); - exception.setId(prefix); - } - TranslationManager.instance().log(e.node(), e.arguments()); - } - private void processConfigEntry(Map.Entry entry, Path path, Pack pack, BiConsumer callback) { if (entry.getValue() instanceof Map typeSections0) { String key = entry.getKey(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java deleted file mode 100644 index 15e4cf7fc..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/AutoId.java +++ /dev/null @@ -1,200 +0,0 @@ -package net.momirealms.craftengine.core.pack.cache; - -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import net.momirealms.craftengine.core.util.FileUtils; -import net.momirealms.craftengine.core.util.GsonHelper; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.function.Predicate; - -public class AutoId { - private final Path cachePath; - private final BiMap forcedIds = HashBiMap.create(128); - - private final Map cachedIds = new HashMap<>(); - private final BitSet occupiedIds = new BitSet(); - private final Map> autoIds = new HashMap<>(); - private int currentAutoId; - private int minId; - private int maxId; - - public AutoId(Path cachePath) { - this.cachePath = cachePath; - } - - public void reset(int startIndex, int endIndex) { - this.minId = startIndex; - this.currentAutoId = startIndex; - this.maxId = endIndex; - this.occupiedIds.clear(); - this.forcedIds.clear(); - this.autoIds.clear(); - this.cachedIds.clear(); - } - - public void arrangeForTheRest() { - // 然后处理自动分配的ID - for (Map.Entry> entry : this.autoIds.entrySet()) { - String name = entry.getKey(); - CompletableFuture future = entry.getValue(); - - // 不应该触发 - if (future.isDone()) { - continue; - } - - // 尝试使用缓存的ID,并且其有效 - Integer cachedId = this.cachedIds.get(name); - if (cachedId != null && !this.occupiedIds.get(cachedId) && cachedId >= this.minId && cachedId <= this.maxId) { - this.occupiedIds.set(cachedId); - future.complete(cachedId); - continue; - } - - // 寻找下一个可用的自动ID - int autoId = findNextAvailableAutoId(); - if (autoId == -1) { - // 没有可用的ID - future.completeExceptionally(new AutoIdExhaustedException(name, this.minId, this.maxId)); - continue; - } - - // 分配找到的ID - this.occupiedIds.set(autoId); - future.complete(autoId); - this.cachedIds.put(name, autoId); - } - - // 清空futureIds,因为所有请求都已处理 - this.autoIds.clear(); - } - - private int findNextAvailableAutoId() { - // 如果已经用尽 - if (this.currentAutoId > this.maxId) { - return -1; - } - // 寻找下一个可用的id - this.currentAutoId = this.occupiedIds.nextClearBit(this.currentAutoId); - // 已经用完了 - if (this.currentAutoId > maxId) { - return -1; - } - // 找到了 - return this.currentAutoId; - } - - // 强制使用某个id,这时候直接标记到occupiedIds,如果被占用,则直接抛出异常 - public CompletableFuture forceId(final String name, int index) { - // 检查ID是否在有效范围内,一般不会在这触发 - if (index < this.minId || index > this.maxId) { - return CompletableFuture.failedFuture(new AutoIdOutOfRangeException(name, index, this.minId, this.maxId)); - } - - // 检查ID是否已被其他名称占用 - String previous = this.forcedIds.inverse().get(index); - if (previous != null && !previous.equals(name)) { - return CompletableFuture.failedFuture(new AutoIdConflictException(previous, index)); - } - - this.forcedIds.put(name, index); - this.cachedIds.remove(name); // 如果曾经被缓存过,那么移除 - return CompletableFuture.completedFuture(index); - } - - // 自动分配id,优先使用缓存的值 - public CompletableFuture autoId(final String name) { - CompletableFuture future = new CompletableFuture<>(); - this.autoIds.put(name, future); - return future; - } - - // 大多数时候通过指令,移除那些已经不再被使用的id,使用完以后记得调用saveCache以保存更改 - public int clearUnusedIds(Predicate predicate) { - List toRemove = new ArrayList<>(); - for (String id : this.cachedIds.keySet()) { - if (predicate.test(id)) { - toRemove.add(id); - } - } - for (String id : toRemove) { - Integer removedId = this.cachedIds.remove(id); - if (removedId != null) { - // 只有当这个ID不是强制ID时才从occupiedIds中移除 - if (!forcedIds.containsValue(removedId)) { - occupiedIds.clear(removedId); - } - } - } - return toRemove.size(); - } - - // 获取已分配的ID(用于调试或查询) - public Integer getId(String name) { - if (forcedIds.containsKey(name)) { - return forcedIds.get(name); - } - return cachedIds.get(name); - } - - // 获取所有已分配的ID映射 - public Map getAllocatedIds() { - Map result = new HashMap<>(); - result.putAll(forcedIds); - result.putAll(cachedIds); - return Collections.unmodifiableMap(result); - } - - // 检查某个ID是否已被占用 - public boolean isIdOccupied(int id) { - return occupiedIds.get(id); - } - - // 从缓存中加载文件 - public void loadCache() throws IOException { - if (!Files.exists(this.cachePath)) { - return; - } - JsonElement element = GsonHelper.readJsonFile(this.cachePath); - if (element instanceof JsonObject jsonObject) { - for (Map.Entry entry : jsonObject.entrySet()) { - if (entry.getValue() instanceof JsonPrimitive primitive) { - int id = primitive.getAsInt(); - this.cachedIds.put(entry.getKey(), id); - } - } - } - } - - // 保存缓存到文件 - public void saveCache() throws IOException { - FileUtils.createDirectoriesSafe(this.cachePath.getParent()); - GsonHelper.writeJsonFile(GsonHelper.get().toJsonTree(this.cachedIds), this.cachePath); - } - - public static class AutoIdConflictException extends RuntimeException { - public AutoIdConflictException(String previousOwner, int id) { - super("ID " + id + " is already occupied by: " + previousOwner); - } - } - - public static class AutoIdOutOfRangeException extends RuntimeException { - public AutoIdOutOfRangeException(String name, int id, int min, int max) { - super("ID " + id + " for '" + name + "' is out of range. Valid range: " + min + "-" + max); - } - } - - public static class AutoIdExhaustedException extends RuntimeException { - public AutoIdExhaustedException(String name, int min, int max) { - super("No available auto ID for '" + name + "'. All IDs in range " + min + "-" + max + " are occupied."); - } - } -} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java new file mode 100644 index 000000000..9e6d44b2f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java @@ -0,0 +1,226 @@ +package net.momirealms.craftengine.core.pack.cache; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.momirealms.craftengine.core.util.FileUtils; +import net.momirealms.craftengine.core.util.GsonHelper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public class IdAllocator { + private final Path cacheFilePath; + private final BiMap forcedIdMap = HashBiMap.create(128); + private final Map cachedIdMap = new HashMap<>(); + private final BitSet occupiedIdSet = new BitSet(); + private final Map> pendingAllocations = new HashMap<>(); + + private int nextAutoId; + private int minId; + private int maxId; + + public IdAllocator(Path cacheFilePath) { + this.cacheFilePath = cacheFilePath; + } + + /** + * 重置分配器状态 + * @param minId 最小ID(包含) + * @param maxId 最大ID(包含) + */ + public void reset(int minId, int maxId) { + this.minId = minId; + this.nextAutoId = minId; + this.maxId = maxId; + this.occupiedIdSet.clear(); + this.forcedIdMap.clear(); + this.pendingAllocations.clear(); + this.cachedIdMap.clear(); + } + + /** + * 处理所有待分配的自动ID请求 + */ + public void processPendingAllocations() { + for (Map.Entry> entry : this.pendingAllocations.entrySet()) { + String name = entry.getKey(); + CompletableFuture future = entry.getValue(); + + if (future.isDone()) { + continue; // 不应该发生的情况 + } + + // 优先尝试使用缓存的ID + Integer cachedId = this.cachedIdMap.get(name); + if (isIdAvailable(cachedId)) { + allocateId(name, cachedId, future); + continue; + } + + // 分配新的自动ID + int newId = findNextAvailableId(); + if (newId == -1) { + future.completeExceptionally(new IdExhaustedException(name, this.minId, this.maxId)); + continue; + } + + allocateId(name, newId, future); + this.cachedIdMap.put(name, newId); + } + + this.pendingAllocations.clear(); + } + + private boolean isIdAvailable(Integer id) { + return id != null && id >= this.minId && id <= this.maxId + && !this.occupiedIdSet.get(id); + } + + private void allocateId(String name, int id, CompletableFuture future) { + this.occupiedIdSet.set(id); + future.complete(id); + } + + private int findNextAvailableId() { + if (this.nextAutoId > this.maxId) { + return -1; + } + + this.nextAutoId = this.occupiedIdSet.nextClearBit(this.nextAutoId); + return this.nextAutoId <= this.maxId ? this.nextAutoId : -1; + } + + /** + * 强制分配指定ID,无视限制 + * @param name 名称 + * @param id 要分配的ID + * @return 分配结果的Future + */ + public CompletableFuture assignFixedId(String name, int id) { + // 检查ID是否被其他名称占用 + String existingOwner = this.forcedIdMap.inverse().get(id); + if (existingOwner != null && !existingOwner.equals(name)) { + return CompletableFuture.failedFuture(new IdConflictException(existingOwner, id)); + } + + this.forcedIdMap.put(name, id); + this.cachedIdMap.remove(name); // 清除可能的缓存 + this.occupiedIdSet.set(id); + return CompletableFuture.completedFuture(id); + } + + /** + * 请求自动分配ID + * @param name 名称 + * @return 分配结果的Future + */ + public CompletableFuture requestAutoId(String name) { + CompletableFuture future = new CompletableFuture<>(); + this.pendingAllocations.put(name, future); + return future; + } + + /** + * 清理不再使用的ID + * @param shouldRemove 判断是否应该移除的谓词 + * @return 被移除的ID数量 + */ + public int cleanupUnusedIds(Predicate shouldRemove) { + List idsToRemove = new ArrayList<>(); + for (String id : this.cachedIdMap.keySet()) { + if (shouldRemove.test(id)) { + idsToRemove.add(id); + } + } + + int removedCount = 0; + for (String id : idsToRemove) { + Integer removedId = this.cachedIdMap.remove(id); + if (removedId != null && !this.forcedIdMap.containsValue(removedId)) { + this.occupiedIdSet.clear(removedId); + removedCount++; + } + } + return removedCount; + } + + /** + * 获取指定名称的ID + * @param name 名称 + * @return ID,如果不存在返回null + */ + public Integer getId(String name) { + Integer forcedId = this.forcedIdMap.get(name); + return forcedId != null ? forcedId : this.cachedIdMap.get(name); + } + + /** + * 获取所有已分配的ID映射(不可修改) + */ + public Map getAllAllocatedIds() { + Map result = new HashMap<>(); + result.putAll(this.forcedIdMap); + result.putAll(this.cachedIdMap); + return Collections.unmodifiableMap(result); + } + + /** + * 检查ID是否已被占用 + */ + public boolean isIdOccupied(int id) { + return this.occupiedIdSet.get(id); + } + + /** + * 从文件加载缓存 + */ + public void loadFromCache() throws IOException { + if (!Files.exists(this.cacheFilePath)) { + return; + } + + JsonElement element = GsonHelper.readJsonFile(this.cacheFilePath); + if (element instanceof JsonObject jsonObject) { + for (Map.Entry entry : jsonObject.entrySet()) { + if (entry.getValue() instanceof JsonPrimitive primitive) { + int id = primitive.getAsInt(); + this.cachedIdMap.put(entry.getKey(), id); + } + } + } + } + + /** + * 保存缓存到文件 + */ + public void saveToCache() throws IOException { + FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent()); + GsonHelper.writeJsonFile(GsonHelper.get().toJsonTree(this.cachedIdMap), this.cacheFilePath); + } + + // 异常类保持不变,但建议也重命名以保持一致性 + public static class IdConflictException extends RuntimeException { + public IdConflictException(String previousOwner, int id) { + super("ID " + id + " is already occupied by: " + previousOwner); + } + } + + public static class IdOutOfRangeException extends RuntimeException { + public IdOutOfRangeException(String name, int id, int min, int max) { + super("ID " + id + " for '" + name + "' is out of range. Valid range: " + min + "-" + max); + } + } + + public static class IdExhaustedException extends RuntimeException { + public IdExhaustedException(String name, int min, int max) { + super("No available auto ID for '" + name + "'. All IDs in range " + min + "-" + max + " are occupied."); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index c2da59fa2..1cb3d542b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -159,6 +159,8 @@ public class Config { protected boolean item$update_triggers$click_in_inventory; protected boolean item$update_triggers$drop; protected boolean item$update_triggers$pick_up; + protected int item$custom_model_data_starting_value$default; + protected Map item$custom_model_data_starting_value$overrides; protected String equipment$sacrificed_vanilla_armor$type; protected Key equipment$sacrificed_vanilla_armor$asset_id; @@ -239,6 +241,7 @@ public class Config { .addIgnoredRoute(PluginProperties.getValue("config"), "resource-pack.delivery.hosting", '.') .addIgnoredRoute(PluginProperties.getValue("config"), "chunk-system.process-invalid-blocks.convert", '.') .addIgnoredRoute(PluginProperties.getValue("config"), "chunk-system.process-invalid-furniture.convert", '.') + .addIgnoredRoute(PluginProperties.getValue("config"), "item.custom-model-data-starting-value.overrides", '.') .build()); } try { @@ -398,6 +401,22 @@ public class Config { item$update_triggers$click_in_inventory = config.getBoolean("item.update-triggers.click-in-inventory", false); item$update_triggers$drop = config.getBoolean("item.update-triggers.drop", false); item$update_triggers$pick_up = config.getBoolean("item.update-triggers.pick-up", false); + item$custom_model_data_starting_value$default = config.getInt("item.custom-model-data-starting-value.default", 10000); + + Section customModelDataOverridesSection = config.getSection("item.custom-model-data-starting-value.overrides"); + if (customModelDataOverridesSection != null) { + Map customModelDataOverrides = new HashMap<>(); + for (Map.Entry entry : customModelDataOverridesSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof String s) { + customModelDataOverrides.put(Key.of(entry.getKey()), Integer.parseInt(s)); + } else if (entry.getValue() instanceof Integer i) { + customModelDataOverrides.put(Key.of(entry.getKey()), i); + } + } + item$custom_model_data_starting_value$overrides = customModelDataOverrides; + } else { + item$custom_model_data_starting_value$overrides = Map.of(); + } // block block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); @@ -752,6 +771,13 @@ public class Config { return instance.furniture$hide_base_entity; } + public static int customModelDataStartingValue(Key material) { + if (instance.item$custom_model_data_starting_value$overrides.containsKey(material)) { + return instance.item$custom_model_data_starting_value$overrides.get(material); + } + return instance.item$custom_model_data_starting_value$default; + } + public static int compressionMethod() { int id = instance.chunk_system$compression_method; if (id <= 0 || id > CompressionMethod.METHOD_COUNT) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java index 2b2eb5125..9238a31cc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java @@ -8,6 +8,6 @@ import java.nio.file.Path; public interface IdObjectConfigParser extends ConfigParser { - default void parseObject(Pack pack, Path path, Key id, Object object) throws LocalizedException { + default void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException { } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java index b137ebfe6..e14e42ba2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java @@ -9,6 +9,6 @@ import java.util.Map; public interface IdSectionConfigParser extends ConfigParser { - default void parseSection(Pack pack, Path path, Key id, Map section) throws LocalizedException { + default void parseSection(Pack pack, Path path, String node, Key id, Map section) throws LocalizedException { } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java index 9d1a505aa..33c2622f4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java @@ -51,9 +51,9 @@ public class TemplateManagerImpl implements TemplateManager { } @Override - public void parseObject(Pack pack, Path path, Key id, Object obj) { + public void parseObject(Pack pack, Path path, String node, Key id, Object obj) { if (templates.containsKey(id)) { - throw new LocalizedResourceConfigException("warning.config.template.duplicate", path.toString(), id.toString()); + throw new LocalizedResourceConfigException("warning.config.template.duplicate"); } // 预处理会将 string类型的键或值解析为ArgumentString,以加速模板应用。所以处理后不可能存在String类型。 templates.put(id, preprocessUnknownValue(obj)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java index d858deed2..ee677e725 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.IdObjectConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.Nullable; import java.nio.file.Path; @@ -49,7 +50,7 @@ public class GlobalVariableManager implements Manageable { } @Override - public void parseObject(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Object object) throws LocalizedException { + public void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException { if (object != null) { GlobalVariableManager.this.globalVariables.put(id.value(), object.toString()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index 7edb1900e..ed34e31a9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -110,7 +110,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { String name = section.getOrDefault("name", id).toString(); List members = MiscUtils.getAsStringList(section.getOrDefault("list", List.of())); Key icon = Key.of(section.getOrDefault("icon", ItemKeys.STONE).toString()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/LocalizedResourceConfigException.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/LocalizedResourceConfigException.java index 994e4c785..5017b6012 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/LocalizedResourceConfigException.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/LocalizedResourceConfigException.java @@ -37,7 +37,7 @@ public class LocalizedResourceConfigException extends LocalizedException { super.setArgument(0, path.toString()); } - public void setId(String id) { + public void setNode(String id) { super.setArgument(1, id); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index f1e119a26..41b8bb69f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -257,10 +257,10 @@ public class TranslationManagerImpl implements TranslationManager { } @Override - public void parseSection(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map section) { Locale locale = TranslationManager.parseLocale(id.value()); if (locale == null) { - throw new LocalizedResourceConfigException("warning.config.i18n.unknown_locale", path, id); + throw new LocalizedResourceConfigException("warning.config.i18n.unknown_locale"); } Map bundle = new HashMap<>(); @@ -288,7 +288,7 @@ public class TranslationManagerImpl implements TranslationManager { } @Override - public void parseSection(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map section) { String langId = id.value().toLowerCase(Locale.ENGLISH); Map sectionData = section.entrySet().stream() .collect(Collectors.toMap( diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java index 6ca57ab61..2e6def245 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java @@ -80,7 +80,7 @@ public abstract class AbstractSoundManager implements SoundManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractSoundManager.this.songs.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.jukebox_song.duplicate"); } @@ -107,7 +107,7 @@ public abstract class AbstractSoundManager implements SoundManager { } @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { + public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractSoundManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.sound.duplicate"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index 58c09669e..b1a135387 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -1,13 +1,16 @@ package net.momirealms.craftengine.core.util; import com.mojang.datafixers.util.Either; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.world.Vec3d; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; +import java.nio.file.Path; import java.util.*; import java.util.function.Function; import java.util.function.Supplier; @@ -280,4 +283,31 @@ public final class ResourceConfigUtils { } } } + + public static void runCatching(Path configPath, String node, Runnable runnable, Supplier config) { + try { + runnable.run(); + } catch (LocalizedException e) { + printWarningRecursively(e, configPath, node); + } catch (Exception e) { + String message = "Unexpected error loading file " + configPath + " - '" + node + "'."; + if (config != null) { + message += " Configuration details: " + config.get(); + } + CraftEngine.instance().logger().warn(message, e); + } + } + + private static void printWarningRecursively(LocalizedException e, Path path, String node) { + for (Throwable t : e.getSuppressed()) { + if (t instanceof LocalizedException suppressed) { + printWarningRecursively(suppressed, path, node); + } + } + if (e instanceof LocalizedResourceConfigException exception) { + exception.setPath(path); + exception.setNode(node); + } + TranslationManager.instance().log(e.node(), e.arguments()); + } } From 20382babbbc6b72ffb1db099e6f2b68cb276eb64 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 18:59:00 +0800 Subject: [PATCH 200/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/cache/IdAllocator.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java index 9e6d44b2f..da851ec1d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java @@ -49,19 +49,23 @@ public class IdAllocator { * 处理所有待分配的自动ID请求 */ public void processPendingAllocations() { + for (Map.Entry entry : this.cachedIdMap.entrySet()) { + CompletableFuture future = this.pendingAllocations.get(entry.getKey()); + if (future != null) { + int id = entry.getValue(); + if (!isIdAvailable(id)) { + continue; + } + allocateId(id, future); + } + } + for (Map.Entry> entry : this.pendingAllocations.entrySet()) { String name = entry.getKey(); CompletableFuture future = entry.getValue(); if (future.isDone()) { - continue; // 不应该发生的情况 - } - - // 优先尝试使用缓存的ID - Integer cachedId = this.cachedIdMap.get(name); - if (isIdAvailable(cachedId)) { - allocateId(name, cachedId, future); - continue; + continue; // 已经在前面分配过了 } // 分配新的自动ID @@ -71,7 +75,7 @@ public class IdAllocator { continue; } - allocateId(name, newId, future); + allocateId(newId, future); this.cachedIdMap.put(name, newId); } @@ -83,7 +87,7 @@ public class IdAllocator { && !this.occupiedIdSet.get(id); } - private void allocateId(String name, int id, CompletableFuture future) { + private void allocateId(int id, CompletableFuture future) { this.occupiedIdSet.set(id); future.complete(id); } From 4a9f7cacde85b7f2cb7ce270bdf79d0092ec6e61 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 19:18:30 +0800 Subject: [PATCH 201/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BD=BD=E5=85=A5?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/cache/IdAllocator.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java index da851ec1d..63913d3e5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java @@ -20,7 +20,7 @@ public class IdAllocator { private final BiMap forcedIdMap = HashBiMap.create(128); private final Map cachedIdMap = new HashMap<>(); private final BitSet occupiedIdSet = new BitSet(); - private final Map> pendingAllocations = new HashMap<>(); + private final Map> pendingAllocations = new LinkedHashMap<>(); private int nextAutoId; private int minId; @@ -206,10 +206,22 @@ public class IdAllocator { */ public void saveToCache() throws IOException { FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent()); - GsonHelper.writeJsonFile(GsonHelper.get().toJsonTree(this.cachedIdMap), this.cacheFilePath); + + // 创建按ID排序的TreeMap + Map sortedById = new TreeMap<>(); + for (Map.Entry entry : this.cachedIdMap.entrySet()) { + sortedById.put(entry.getValue(), entry.getKey()); + } + + // 创建有序的JSON对象 + JsonObject sortedJsonObject = new JsonObject(); + for (Map.Entry entry : sortedById.entrySet()) { + sortedJsonObject.addProperty(entry.getValue(), entry.getKey()); + } + + GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath); } - // 异常类保持不变,但建议也重命名以保持一致性 public static class IdConflictException extends RuntimeException { public IdConflictException(String previousOwner, int id) { super("ID " + id + " is already occupied by: " + previousOwner); From 6bd2ed2ef217d1bcebcc1040eeeae8a43a2f1983 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sat, 27 Sep 2025 19:51:52 +0800 Subject: [PATCH 202/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=80=BC=E8=87=AA=E5=8A=A8=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/translations/en.yml | 1 + .../core/item/AbstractItemManager.java | 119 +++++++++--------- .../core/pack/cache/IdAllocator.java | 34 ++++- 3 files changed, 90 insertions(+), 64 deletions(-) diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 8bfe8c7ad..eaf5c8740 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -193,6 +193,7 @@ warning.config.item.invalid_custom_model_data: "Issue found in file Issue found in file - The item '' is using a custom model data '' that is too large. It's recommended to use a value lower than 16,777,216." warning.config.item.item_model.conflict: "Issue found in file - The item '' is using an invalid 'item-model' option because this item model has been occupied by a vanilla item." warning.config.item.custom_model_data_conflict: "Issue found in file - The item '' is using a custom model data '' that has been occupied by item ''." +warning.config.item.custom_model_data_exhausted: "Issue found in file - Cannot allocate custom model data for item '' as the custom model data has already been exhausted." warning.config.item.invalid_component: "Issue found in file - The item '' is using a non-existing component type ''." warning.config.item.missing_model_id: "Issue found in file - The item '' is missing the required 'custom-model-data' or 'item-model' argument." warning.config.item.missing_model: "Issue found in file - The item '' is missing the required 'model' section for 1.21.4+ resource pack support." diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index b31c26ff3..0dee19193 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -52,7 +52,6 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected final Map> customItemsById = new HashMap<>(); protected final Map> customItemsByPath = new HashMap<>(); protected final Map> customItemTags = new HashMap<>(); - protected final Map> cmdConflictChecker = new HashMap<>(); protected final Map modernItemModels1_21_4 = new HashMap<>(); protected final Map> modernItemModels1_21_2 = new HashMap<>(); protected final Map> legacyOverrides = new HashMap<>(); @@ -128,7 +127,6 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl this.modernOverrides.clear(); this.customItemTags.clear(); this.equipments.clear(); - this.cmdConflictChecker.clear(); this.modernItemModels1_21_4.clear(); this.modernItemModels1_21_2.clear(); } @@ -331,7 +329,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } private boolean needsItemModelCompatibility() { - return Config.packMaxVersion().isAbove(MinecraftVersions.V1_21_2); + return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2); } public Map idAllocators() { @@ -425,11 +423,19 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl customModelDataFuture = CompletableFuture.completedFuture(0); } - // 是否使用客户端侧模型 - boolean clientBoundModel = VersionHelper.PREMIUM && (section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel()); - // 当模型值完成分配的时候 - customModelDataFuture.thenAccept(customModelData -> ResourceConfigUtils.runCatching(path, node, () -> { + customModelDataFuture.whenComplete((customModelData, throwable) -> ResourceConfigUtils.runCatching(path, node, () -> { + + if (throwable != null) { + // 检测custom model data 冲突 + if (throwable instanceof IdAllocator.IdConflictException exception) { + throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(exception.id()), exception.previousOwner()); + } + // custom model data 已被用尽,不太可能 + else if (throwable instanceof IdAllocator.IdExhaustedException) { + throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_exhausted"); + } + } // item model Key itemModel = null; @@ -447,6 +453,9 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // 用户没设置item model但是有custom model data,那么就使用custom model data } + // 是否使用客户端侧模型 + boolean clientBoundModel = VersionHelper.PREMIUM && (section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel()); + CustomItem.Builder itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial); if (customModelData > 0) { if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData)); @@ -553,6 +562,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl * ======================== */ + // 原版物品还改模型?自己替换json去 + if (isVanillaItem) { + return; + } + // 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model Map modelSection = MiscUtils.castToMap(section.get("model"), true); Map legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true); @@ -561,9 +575,8 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return; } - boolean needsModelSection = isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null); // 只对自定义物品有这个限制,既没有模型值也没有item-model - if (!isVanillaItem && customModelData == 0 && itemModel == null) { + if (customModelData == 0 && itemModel == null) { collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id")); } @@ -572,7 +585,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // 旧版格式 TreeSet legacyOverridesModels = null; // 如果需要支持新版item model 或者用户需要旧版本兼容,但是没配置legacy-model - if (needsModelSection) { + if (isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null)) { // 1.21.4+必须要配置model区域,如果不需要高版本兼容,则可以只写legacy-model if (modelSection == null) { collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model")); @@ -603,66 +616,52 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl legacyOverridesModels = new TreeSet<>(); processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData); if (legacyOverridesModels.isEmpty()) { - collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString())); + collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert")); } } } - // 自定义物品的model处理 - if (!isVanillaItem) { - // 这个item-model是否存在,且是原版item-model - boolean isVanillaItemModel = itemModel != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModel); - // 使用了自定义模型值 - if (customModelData != 0) { - // 如果用户主动设置了item-model且为原版物品,则使用item-model为基础模型,否则使用其视觉材质对应的item-model - Key finalBaseModel = isVanillaItemModel ? itemModel : clientBoundMaterial; - // 检查cmd冲突 - Map conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(finalBaseModel, k -> new HashMap<>()); - if (conflict.containsKey(customModelData)) { - collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString())); - } - conflict.put(customModelData, id); - // 添加新版item model - if (isModernFormatRequired() && modernModel != null) { - TreeMap map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>()); - map.put(customModelData, new ModernItemModel( - modernModel, - ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), - ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") - )); - } - // 添加旧版 overrides - if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { - TreeSet lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>()); - lom.addAll(legacyOverridesModels); - } - } else if (isVanillaItemModel) { - collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModel.asString())); - } + boolean hasLegacyModel = legacyOverridesModels != null && !legacyOverridesModels.isEmpty(); + boolean hasModernModel = modernModel != null; - // 使用了item-model组件,且不是原版物品的 - if (itemModel != null && !isVanillaItemModel) { - if (isModernFormatRequired() && modernModel != null) { - AbstractItemManager.this.modernItemModels1_21_4.put(itemModel, new ModernItemModel( - modernModel, - ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), - ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") - )); - } - if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { - TreeSet lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModel, k -> new TreeSet<>()); - lom.addAll(legacyOverridesModels); - } - } - } else { - // 原版物品的item model覆写 - if (isModernFormatRequired()) { - AbstractItemManager.this.modernItemModels1_21_4.put(id, new ModernItemModel( + // 自定义物品的model处理 + // 这个item-model是否存在,且是原版item-model + boolean isVanillaItemModel = itemModel != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModel); + // 使用了自定义模型值 + if (customModelData != 0) { + // 如果用户主动设置了item-model且为原版物品,则使用item-model为基础模型,否则使用其视觉材质对应的item-model + Key finalBaseModel = isVanillaItemModel ? itemModel : clientBoundMaterial; + // 添加新版item model + if (isModernFormatRequired() && hasModernModel) { + TreeMap map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>()); + map.put(customModelData, new ModernItemModel( modernModel, ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") )); } + // 添加旧版 overrides + if (needsLegacyCompatibility() && hasLegacyModel) { + TreeSet lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>()); + lom.addAll(legacyOverridesModels); + } + } else if (isVanillaItemModel) { + collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModel.asString())); + } + + // 使用了item-model组件,且不是原版物品的 + if (itemModel != null && !isVanillaItemModel) { + if (isModernFormatRequired() && hasModernModel) { + AbstractItemManager.this.modernItemModels1_21_4.put(itemModel, new ModernItemModel( + modernModel, + ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"), + ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap") + )); + } + if (needsItemModelCompatibility() && needsLegacyCompatibility() && hasLegacyModel) { + TreeSet lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModel, k -> new TreeSet<>()); + lom.addAll(legacyOverridesModels); + } } // 抛出异常 diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java index 63913d3e5..a4fe866f6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java @@ -223,20 +223,46 @@ public class IdAllocator { } public static class IdConflictException extends RuntimeException { + private final String previousOwner; + private final int id; + public IdConflictException(String previousOwner, int id) { super("ID " + id + " is already occupied by: " + previousOwner); + this.previousOwner = previousOwner; + this.id = id; } - } - public static class IdOutOfRangeException extends RuntimeException { - public IdOutOfRangeException(String name, int id, int min, int max) { - super("ID " + id + " for '" + name + "' is out of range. Valid range: " + min + "-" + max); + public String previousOwner() { + return previousOwner; + } + + public int id() { + return id; } } public static class IdExhaustedException extends RuntimeException { + private final String name; + private final int min; + private final int max; + public IdExhaustedException(String name, int min, int max) { super("No available auto ID for '" + name + "'. All IDs in range " + min + "-" + max + " are occupied."); + this.name = name; + this.min = min; + this.max = max; + } + + public String name() { + return name; + } + + public int min() { + return min; + } + + public int max() { + return max; } } } \ No newline at end of file From 32b678de4ba45ee31fa596656743e10072b9663e Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 04:18:05 +0800 Subject: [PATCH 203/226] =?UTF-8?q?=E5=86=85=E9=83=A8id=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 47 ++- .../bukkit/block/BukkitCustomBlock.java | 128 +------ .../item/behavior/BlockItemBehavior.java | 17 +- .../behavior/DoubleHighBlockItemBehavior.java | 10 +- .../item/behavior/WallBlockItemBehavior.java | 7 +- .../DebugAppearanceStateUsageCommand.java | 16 - .../feature/DebugRealStateUsageCommand.java | 16 - .../resources/internal/configuration/gui.yml | 13 + .../src/main/resources/translations/de.yml | 7 +- .../src/main/resources/translations/en.yml | 12 +- .../src/main/resources/translations/es.yml | 7 +- .../src/main/resources/translations/ru_ru.yml | 7 +- .../src/main/resources/translations/tr.yml | 7 +- .../src/main/resources/translations/zh_cn.yml | 9 +- .../core/block/AbstractBlockManager.java | 341 +++++++++++++----- .../core/block/AbstractCustomBlock.java | 90 +---- .../craftengine/core/block/BlockSounds.java | 1 - .../core/block/BlockStateHolder.java | 8 +- .../core/block/BlockStateVariant.java | 25 -- .../core/block/BlockStateVariantProvider.java | 30 +- .../craftengine/core/block/CustomBlock.java | 19 - .../craftengine/core/block/EmptyBlock.java | 28 +- .../core/block/ImmutableBlockState.java | 4 +- .../core/block/InactiveCustomBlock.java | 6 +- .../core/block/parser/BlockNbtParser.java | 17 +- .../core/item/AbstractItemManager.java | 19 +- .../core/item/behavior/ItemBehaviors.java | 3 - .../core/pack/PendingConfigSection.java | 9 + .../core/pack/cache/IdAllocator.java | 27 +- .../craftengine/core/registry/Holder.java | 3 +- .../core/util/ResourceConfigUtils.java | 7 + .../DefaultSectionSerializer.java | 2 +- 32 files changed, 480 insertions(+), 462 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/PendingConfigSection.java 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 5fc635efe..413fc8cb3 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 @@ -1,7 +1,7 @@ package net.momirealms.craftengine.bukkit.block; import com.google.common.collect.ImmutableList; -import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.momirealms.craftengine.bukkit.block.behavior.UnsafeCompositeBlockBehavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator; @@ -16,15 +16,18 @@ import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.bukkit.util.TagUtils; import net.momirealms.craftengine.core.block.*; +import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; import net.momirealms.craftengine.core.block.parser.BlockStateParser; +import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.logger.Debugger; -import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; -import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; @@ -61,7 +64,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks()); this.plugin = plugin; this.registerServerSideCustomBlocks(Config.serverSideBlocks()); - this.registerEmptyBlock(); + EmptyBlock.initialize(); instance = this; } @@ -114,6 +117,21 @@ public final class BukkitBlockManager extends AbstractBlockManager { super.delayedLoad(); } + @Override + public BlockBehavior createBlockBehavior(CustomBlock customBlock, List> behaviorConfig) { + if (behaviorConfig == null || behaviorConfig.isEmpty()) { + return new EmptyBlockBehavior(); + } else if (behaviorConfig.size() == 1) { + return BlockBehaviors.fromMap(customBlock, behaviorConfig.getFirst()); + } else { + List behaviors = new ArrayList<>(); + for (Map config : behaviorConfig) { + behaviors.add((AbstractBlockBehavior) BlockBehaviors.fromMap(customBlock, config)); + } + return new UnsafeCompositeBlockBehavior(customBlock, behaviors); + } + } + @Override protected void resendTags() { // if there's no change @@ -258,17 +276,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { BlockRegistryMirror.init(states, new BukkitBlockStateWrapper(MBlocks.STONE$defaultState, BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState))); } - private void registerEmptyBlock() { - Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).registerForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), Key.withDefaultNamespace("empty"))); - EmptyBlock emptyBlock = new EmptyBlock(Key.withDefaultNamespace("empty"), holder); - holder.bindValue(emptyBlock); - } - - @Override - protected CustomBlock.Builder platformBuilder(Key id) { - return BukkitCustomBlock.builder(id); - } - // 注册服务端侧的真实方块 private void registerServerSideCustomBlocks(int count) { // 这个会影响全局调色盘 @@ -379,6 +386,14 @@ public final class BukkitBlockManager extends AbstractBlockManager { return this.vanillaBlockStateCount; } + @Override + protected CustomBlock createCustomBlock(@NotNull Holder.Reference holder, + @NotNull BlockStateVariantProvider variantProvider, + @NotNull Map>> events, + @Nullable LootTable lootTable) { + return new BukkitCustomBlock(holder, variantProvider, events, lootTable); + } + public boolean isOpenableBlockSoundRemoved(Object blockOwner) { return false; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java index b831a0cde..360e121ad 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java @@ -1,139 +1,27 @@ package net.momirealms.craftengine.bukkit.block; -import net.momirealms.craftengine.bukkit.block.behavior.UnsafeCompositeBlockBehavior; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.KeyUtils; -import net.momirealms.craftengine.bukkit.util.SoundUtils; -import net.momirealms.craftengine.core.block.*; -import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; -import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; -import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.block.AbstractCustomBlock; +import net.momirealms.craftengine.core.block.BlockStateVariantProvider; +import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; -import net.momirealms.craftengine.core.registry.WritableRegistry; -import net.momirealms.craftengine.core.util.*; -import org.bukkit.Bukkit; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.List; +import java.util.Map; public final class BukkitCustomBlock extends AbstractCustomBlock { - private BukkitCustomBlock( - @NotNull Key id, + public BukkitCustomBlock( @NotNull Holder.Reference holder, - @NotNull Map> properties, - @NotNull Map appearances, - @NotNull Map variantMapper, - @NotNull BlockSettings settings, + @NotNull BlockStateVariantProvider variantProvider, @NotNull Map>> events, - @Nullable List> behavior, @Nullable LootTable lootTable ) { - super(id, holder, properties, appearances, variantMapper, settings, events, behavior, lootTable); - } - - @Override - protected BlockBehavior setupBehavior(List> behaviorConfig) { - if (behaviorConfig == null || behaviorConfig.isEmpty()) { - return new EmptyBlockBehavior(); - } else if (behaviorConfig.size() == 1) { - return BlockBehaviors.fromMap(this, behaviorConfig.getFirst()); - } else { - List behaviors = new ArrayList<>(); - for (Map config : behaviorConfig) { - behaviors.add((AbstractBlockBehavior) BlockBehaviors.fromMap(this, config)); - } - return new UnsafeCompositeBlockBehavior(this, behaviors); - } - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public LootTable lootTable() { - return (LootTable) super.lootTable(); - } - - public static Builder builder(Key id) { - return new BuilderImpl(id); - } - - public static class BuilderImpl implements Builder { - protected final Key id; - protected Map> properties; - protected Map appearances; - protected Map variantMapper; - protected BlockSettings settings; - protected List> behavior; - protected LootTable lootTable; - protected Map>> events; - - public BuilderImpl(Key id) { - this.id = id; - } - - @Override - public Builder events(Map>> events) { - this.events = events; - return this; - } - - @Override - public Builder appearances(Map appearances) { - this.appearances = appearances; - return this; - } - - @Override - public Builder behavior(List> behavior) { - this.behavior = behavior; - return this; - } - - @Override - public Builder lootTable(LootTable lootTable) { - this.lootTable = lootTable; - return this; - } - - @Override - public Builder properties(Map> properties) { - this.properties = properties; - return this; - } - - @Override - public Builder settings(BlockSettings settings) { - this.settings = settings; - return this; - } - - @Override - public Builder variantMapper(Map variantMapper) { - this.variantMapper = variantMapper; - return this; - } - - @Override - public @NotNull CustomBlock build() { - // create or get block holder - Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).getOrRegisterForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), this.id)); - return new BukkitCustomBlock(this.id, holder, this.properties, this.appearances, this.variantMapper, this.settings, this.events, this.behavior, this.lootTable); - } + super(holder, variantProvider, events, lootTable); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index a71c46f64..f8cb7dfcb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; @@ -232,6 +233,15 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { return this.blockId; } + static void addPendingSection(Pack pack, Path path, String node, Key key, Map map) { + if (map.containsKey(key.toString())) { + // 防呆 + BukkitBlockManager.instance().blockParser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false))); + } else { + BukkitBlockManager.instance().blockParser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map, false))); + } + } + public static class Factory implements ItemBehaviorFactory { @Override public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { @@ -240,12 +250,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { throw new LocalizedResourceConfigException("warning.config.item.behavior.block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for block_item behavior")); } if (id instanceof Map map) { - if (map.containsKey(key.toString())) { - // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); - } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); - } + addPendingSection(pack, path, node, key, map); return new BlockItemBehavior(key); } else { return new BlockItemBehavior(Key.of(id.toString())); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java index 403d8eea3..7ad5a1bed 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -43,15 +44,10 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior { public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { Object id = arguments.get("block"); if (id == null) { - throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block", new IllegalArgumentException("Missing required parameter 'block' for double_high_block_item behavior")); + throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block"); } if (id instanceof Map map) { - if (map.containsKey(key.toString())) { - // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); - } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); - } + addPendingSection(pack, path, node, key, map); return new DoubleHighBlockItemBehavior(key); } else { return new DoubleHighBlockItemBehavior(Key.of(id.toString())); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java index 8f8232d4f..6e46af6cd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java @@ -41,12 +41,7 @@ public class WallBlockItemBehavior extends BlockItemBehavior { throw new LocalizedResourceConfigException("warning.config.item.behavior.wall_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for wall_block_item behavior")); } if (id instanceof Map map) { - if (map.containsKey(key.toString())) { - // 防呆 - BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); - } else { - BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); - } + addPendingSection(pack, path, node, key, map); return new WallBlockItemBehavior(key); } else { return new WallBlockItemBehavior(Key.of(id.toString())); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java index ce6029653..2192b12d4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java @@ -1,26 +1,10 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.Command; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.context.CommandInput; -import org.incendo.cloud.parser.standard.StringParser; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.suggestion.SuggestionProvider; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java index 04b7d0ce2..d30863b8c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java @@ -1,26 +1,10 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; -import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.Command; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.context.CommandInput; -import org.incendo.cloud.parser.standard.StringParser; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.suggestion.SuggestionProvider; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; public class DebugRealStateUsageCommand extends BukkitCommandFeature { diff --git a/common-files/src/main/resources/resources/internal/configuration/gui.yml b/common-files/src/main/resources/resources/internal/configuration/gui.yml index 42c53dee6..344d37d55 100644 --- a/common-files/src/main/resources/resources/internal/configuration/gui.yml +++ b/common-files/src/main/resources/resources/internal/configuration/gui.yml @@ -74,6 +74,7 @@ images: templates: internal:icon/2d: material: arrow + custom-model-data: ${model_data} data: item-name: ${name} lore: ${lore} @@ -92,6 +93,7 @@ items: internal:next_page_0: template: internal:icon/2d arguments: + model_data: 1000 texture: next_page_0 name: <#FAFAD2> lore: @@ -99,6 +101,7 @@ items: internal:next_page_1: template: internal:icon/2d arguments: + model_data: 1001 texture: next_page_1 name: <#808080> lore: @@ -106,6 +109,7 @@ items: internal:previous_page_0: template: internal:icon/2d arguments: + model_data: 1002 texture: previous_page_0 name: <#FAFAD2> lore: @@ -113,6 +117,7 @@ items: internal:previous_page_1: template: internal:icon/2d arguments: + model_data: 1003 texture: previous_page_1 name: <#808080> lore: @@ -120,29 +125,34 @@ items: internal:return: template: internal:icon/2d arguments: + model_data: 1004 texture: return name: <#DAA520> lore: null internal:next_recipe_0: material: arrow + custom-model-data: 1000 data: item-name: <#FAFAD2> lore: - <#F5F5F5>/ internal:next_recipe_1: material: arrow + custom-model-data: 1001 data: item-name: <#808080> lore: - <#696969>/ internal:previous_recipe_0: material: arrow + custom-model-data: 1002 data: item-name: <#FAFAD2> lore: - <#F5F5F5>/ internal:previous_recipe_1: material: arrow + custom-model-data: 1003 data: item-name: <#808080> lore: @@ -150,6 +160,7 @@ items: internal:get_item: template: internal:icon/2d arguments: + model_data: 1005 texture: get_item name: <#DAA520> lore: @@ -158,6 +169,7 @@ items: internal:cooking_info: template: internal:icon/2d arguments: + model_data: 1006 texture: cooking_info name: <#FF8C00> lore: @@ -166,6 +178,7 @@ items: internal:exit: template: internal:icon/2d arguments: + model_data: 1007 texture: exit name: <#DAA520> lore: null \ No newline at end of file diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 4e28bf879..1513a684b 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -184,7 +184,7 @@ warning.config.item.invalid_material: "Problem in Datei gefunden warning.config.item.invalid_custom_model_data: "Problem in Datei gefunden - Das Item '' verwendet eine negative Custom-Model-Data '', was ungültig ist." warning.config.item.bad_custom_model_data: "Problem in Datei gefunden - Das Item '' verwendet eine Custom-Model-Data '', die zu groß ist. Es wird empfohlen, einen Wert unter 16.777.216 zu verwenden." warning.config.item.item_model.conflict: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige 'item-model'-Option, da dieses Item-Model bereits von einem Vanilla-Item belegt ist." -warning.config.item.custom_model_data_conflict: "Problem in Datei gefunden - Das Item '' verwendet eine Custom-Model-Data '', die bereits von Item '' belegt ist." +warning.config.item.custom_model_data.conflict: "Problem in Datei gefunden - Das Item '' verwendet eine Custom-Model-Data '', die bereits von Item '' belegt ist." warning.config.item.invalid_component: "Problem in Datei gefunden - Das Item '' verwendet einen nicht existierenden Component-Typ ''." warning.config.item.missing_model_id: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'custom-model-data'- oder 'item-model'-Argument." warning.config.item.missing_model: "Problem in Datei gefunden - Beim Item '' fehlt der erforderliche 'model'-Abschnitt für die Unterstützung von Resource Packs ab 1.21.4+." @@ -252,18 +252,15 @@ warning.config.block.state.property.missing_type: "Problem in Datei Problem in Datei gefunden - Der Block '' verwendet das ungültige Typ-Argument '' für die Property ''." warning.config.block.state.property.integer.invalid_range: "Problem in Datei gefunden - Der Block '' verwendet das ungültige 'range'-Argument '' für die Integer-Property ''. Korrekte Syntax: 1~2." warning.config.block.state.property.invalid_format: "Problem in Datei gefunden - Der Block '' verwendet ein ungültiges Block-State-Property-Format ''." -warning.config.block.state.missing_real_id: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'id'-Argument für 'state'. 'id' ist die serverseitige Block-ID, die für jeden Block-State-Typ eindeutig ist. Wenn du einen serverseitigen Block mit 'note_block' und ID 30 erstellst, wäre die echte Block-ID 'craftengine:note_block_30'." warning.config.block.state.missing_state: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'state'-Argument für 'state'." warning.config.block.state.missing_properties: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'properties'-Abschnitt für 'states'." warning.config.block.state.missing_appearances: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'appearances'-Abschnitt für 'states'." warning.config.block.state.missing_variants: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'variants'-Abschnitt für 'states'." -warning.config.block.state.variant.missing_appearance: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'appearance'-Argument für die Variante ''." warning.config.block.state.variant.invalid_appearance: "Problem in Datei gefunden - Der Block '' hat einen Fehler, dass die Variante '' eine nicht existierende Appearance '' verwendet." warning.config.block.state.invalid_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen ungültigen Vanilla-Block-State ''." warning.config.block.state.unavailable_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen nicht verfügbaren Vanilla-Block-State ''. Bitte gib diesen State in der mappings.yml frei." warning.config.block.state.invalid_vanilla_id: "Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Block-State '', der den verfügbaren Slot-Bereich '0~' überschreitet." -warning.config.block.state.conflict: "Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Block-State '', der bereits von '' belegt ist." -warning.config.block.state.bind_failed: "Problem in Datei gefunden - Der Block '' konnte den echten Block-State für '' nicht binden, da der State bereits von '' belegt ist." +warning.config.block.state.id.conflict: "Problem in Datei gefunden - Der Block '' konnte den echten Block-State für '' nicht binden, da der State bereits von '' belegt ist." warning.config.block.state.model.missing_path: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'path'-Option für 'model'." warning.config.block.state.model.invalid_path: "Problem in Datei gefunden - Der Block '' hat ein 'path'-Argument '', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.block.state.model.conflict: "Problem in Datei gefunden - Der Block '' versucht, das Model '' an den Block-State '' zu binden, der bereits an das Model '' gebunden ist." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index eaf5c8740..652e823a8 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -192,8 +192,8 @@ warning.config.item.invalid_material: "Issue found in file - The warning.config.item.invalid_custom_model_data: "Issue found in file - The item '' is using a negative custom model data '' which is invalid." warning.config.item.bad_custom_model_data: "Issue found in file - The item '' is using a custom model data '' that is too large. It's recommended to use a value lower than 16,777,216." warning.config.item.item_model.conflict: "Issue found in file - The item '' is using an invalid 'item-model' option because this item model has been occupied by a vanilla item." -warning.config.item.custom_model_data_conflict: "Issue found in file - The item '' is using a custom model data '' that has been occupied by item ''." -warning.config.item.custom_model_data_exhausted: "Issue found in file - Cannot allocate custom model data for item '' as the custom model data has already been exhausted." +warning.config.item.custom_model_data.conflict: "Issue found in file - The item '' is using a custom model data '' that has been occupied by item ''." +warning.config.item.custom_model_data.exhausted: "Issue found in file - Cannot allocate custom model data for item '' as the custom model data has already been exhausted." warning.config.item.invalid_component: "Issue found in file - The item '' is using a non-existing component type ''." warning.config.item.missing_model_id: "Issue found in file - The item '' is missing the required 'custom-model-data' or 'item-model' argument." warning.config.item.missing_model: "Issue found in file - The item '' is missing the required 'model' section for 1.21.4+ resource pack support." @@ -264,7 +264,6 @@ warning.config.block.state.property.missing_type: "Issue found in file < warning.config.block.state.property.invalid_type: "Issue found in file - The block '' is using the invalid type argument '' for property ''." warning.config.block.state.property.integer.invalid_range: "Issue found in file - The block '' is using the invalid 'range' argument '' for integer property ''. Correct syntax: 1~2." warning.config.block.state.property.invalid_format: "Issue found in file - The block '' is using an invalid block state property format ''." -warning.config.block.state.missing_real_id: "Issue found in file - The block '' is missing the required 'id' argument for 'state'. 'id' is the serverside block id which is unique for each type of block state. If you create a serverside side block with 'note_block' and id 30, then the real block id would be 'craftengine:note_block_30'." warning.config.block.state.missing_state: "Issue found in file - The block '' is missing the required 'state' argument for 'state'." warning.config.block.state.missing_properties: "Issue found in file - The block '' is missing the required 'properties' section for 'states'." warning.config.block.state.missing_appearances: "Issue found in file - The block '' is missing the required 'appearances' section for 'states'." @@ -274,14 +273,13 @@ warning.config.block.state.entity_renderer.item_display.missing_item: "I warning.config.block.state.entity_renderer.text_display.missing_text: "Issue found in file - The block '' is missing the required 'text' argument for 'text_display' entity renderer." warning.config.block.state.entity_renderer.better_model.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'better_model' entity renderer." warning.config.block.state.entity_renderer.model_engine.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'model_engine' entity renderer." -warning.config.block.state.variant.missing_appearance: "Issue found in file - The block '' is missing the required 'appearance' argument for variant ''." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in mappings.yml." warning.config.block.state.invalid_vanilla_id: "Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." -warning.config.block.state.conflict: "Issue found in file - The block '' is using a vanilla block state '' that has been occupied by ''." -warning.config.block.state.bind_failed: "Issue found in file - The block '' failed to bind real block state '' for '' as the state has been occupied by ''." -warning.config.block.state.invalid_real_id: "Issue found in file - The block '' is using a real block state '' that exceeds the available slot range '0~'. Consider adding more serverside blocks in 'config.yml' if the slots are used up." +warning.config.block.state.invalid_id: "Issue found in file - The block state ID range () used by block '' is outside the valid range of 0 to . Please add more server-side blocks in 'config.yml' if the current slots are exhausted." +warning.config.block.state.id.conflict: "Issue found in file - The block '' failed to bind real block state '' for '' as the state has been occupied by ''." +warning.config.block.state.id.exhausted: "Issue found in file - Cannot allocate enough real block state for block ''. Please add more server-side blocks in 'config.yml' and restart if the current slots are exhausted." warning.config.block.state.model.missing_path: "Issue found in file - The block '' is missing the required 'path' option for 'model'." warning.config.block.state.model.invalid_path: "Issue found in file - The block '' has a 'path' argument '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.block.state.model.conflict: "Issue found in file - The block '' is trying to bind model '' to block state '' which has already been bound to model ''" diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index 0429bbb86..d65bc899a 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -121,7 +121,7 @@ warning.config.item.settings.unknown: "Problema encontrado en el archivo warning.config.item.missing_material: "Problema encontrado en el archivo - El objeto '' carece del argumento requerido 'material'." warning.config.item.invalid_material: "Problema encontrado en el archivo - El objeto '' está usando un tipo de material inválido ''." warning.config.item.bad_custom_model_data: "Problema encontrado en el archivo - El objeto '' está usando un dato de modelo personalizado demasiado grande ''. Se recomienda usar un valor menor a 16.777.216." -warning.config.item.custom_model_data_conflict: "Problema encontrado en el archivo - El objeto '' está usando un dato de modelo personalizado '' que está ocupado por el objeto ''." +warning.config.item.custom_model_data.conflict: "Problema encontrado en el archivo - El objeto '' está usando un dato de modelo personalizado '' que está ocupado por el objeto ''." warning.config.item.missing_model_id: "Problema encontrado en el archivo - El objeto '' carece del argumento requerido 'custom-model-data' o 'item-model'." warning.config.item.behavior.missing_type: "Problema encontrado en el archivo - El objeto '' carece del argumento requerido 'type' para el comportamiento del objeto." warning.config.item.behavior.invalid_type: "Problema encontrado en el archivo - El objeto '' está usando un tipo de comportamiento de objeto inválido ''." @@ -175,18 +175,15 @@ warning.config.block.state.property.missing_type: "Problema encontrado e warning.config.block.state.property.invalid_type: "Problema encontrado en el archivo - El bloque '' está usando un argumento 'type' inválido '' para la propiedad ''." warning.config.block.state.property.integer.invalid_range: "Problema encontrado en el archivo - El bloque '' está usando un argumento 'range' inválido '' para la propiedad entero ''. Sintaxis correcta: 1~2." warning.config.block.state.property.invalid_format: "Problema encontrado en el archivo - El bloque '' está usando un formato de propiedad de estado de bloque inválido ''." -warning.config.block.state.missing_real_id: "Problema encontrado en el archivo - El bloque '' carece del argumento requerido 'id' para 'state'. 'id' es el id de bloque del lado del servidor que es único para cada tipo de estado de bloque. Si creas un bloque del lado del servidor con 'note_block' e id 30, el id de bloque real será 'craftengine:note_block_30'." warning.config.block.state.missing_state: "Problema encontrado en el archivo - El bloque '' carece del argumento requerido 'state' para 'state'." warning.config.block.state.missing_properties: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'properties' para 'states'." warning.config.block.state.missing_appearances: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'appearances' para 'states'." warning.config.block.state.missing_variants: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'variants' para 'states'." -warning.config.block.state.variant.missing_appearance: "Problema encontrado en el archivo - El bloque '' carece del argumento requerido 'appearance' para la variante ''." warning.config.block.state.variant.invalid_appearance: "Problema encontrado en el archivo - Hay un error en el bloque '' donde la variante '' está usando una apariencia inexistente ''." warning.config.block.state.invalid_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla inválido ''." warning.config.block.state.unavailable_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla no disponible ''. Por favor libera este estado en el archivo mappings.yml." warning.config.block.state.invalid_vanilla_id: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla '' que excede el rango de slots disponible '0~'." -warning.config.block.state.conflict: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla '' que está ocupado por ''." -warning.config.block.state.bind_failed: "Problema encontrado en el archivo - El bloque '' falló al vincular el estado de bloque real para '' porque está ocupado por el estado ''." +warning.config.block.state.id.conflict: "Problema encontrado en el archivo - El bloque '' falló al vincular el estado de bloque real para '' porque está ocupado por el estado ''." warning.config.block.state.model.missing_path: "Problema encontrado en el archivo - El bloque '' carece de la opción requerida 'path' para 'model'." warning.config.block.state.model.invalid_path: "Problema encontrado en el archivo - El bloque '' tiene un argumento 'path' '' que contiene caracteres prohibidos. Por favor lee https://minecraft.wiki/w/Resource_location#Legal_characters" warning.config.block.settings.unknown: "Problema encontrado en el archivo - El bloque '' está usando un tipo de configuración desconocido ''." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index dcb624e7a..be8170d1a 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -161,7 +161,7 @@ warning.config.item.missing_material: "Проблема найдена warning.config.item.invalid_material: "Проблема найдена в файле - Предмет '' использует недопустимый тип материала ''." warning.config.item.invalid_custom_model_data: "Проблема найдена в файле - Предмет '' использует отрицательные данные пользовательской модели '', что недействительно." warning.config.item.bad_custom_model_data: "Проблема найдена в файле - Предмет '' использует пользовательскую модель данных '', которая имеет слишком большое значение. Рекомендуется использовать значение ниже 16,777,216." -warning.config.item.custom_model_data_conflict: "Проблема найдена в файле - Предмет '' использует пользовательскую модель данных '', которая занята элементом ''." +warning.config.item.custom_model_data.conflict: "Проблема найдена в файле - Предмет '' использует пользовательскую модель данных '', которая занята элементом ''." warning.config.item.invalid_component: "Проблема найдена в файле - Предмет '' использует несуществующий тип компонента ''." warning.config.item.missing_model_id: "Проблема найдена в файле - В предмете '' отсутствует необходимый 'custom-model-data' или 'item-model' аргумент." warning.config.item.missing_model: "Проблема найдена в файле - В предмете '' отсутствует необходимый 'model' раздел для поддержки пакета ресурсов 1.21.4+." @@ -225,18 +225,15 @@ warning.config.block.state.property.missing_type: "Проблема на warning.config.block.state.property.invalid_type: "Проблема найдена в файле - Блок '' использует недопустимый аргумент типа '' для свойства ''." warning.config.block.state.property.integer.invalid_range: "Проблема найдена в файле - Блок '' использует недействительный 'range' аргумент '' для integer свойства ''. Правильный синтаксис: 1~2." warning.config.block.state.property.invalid_format: "Проблема найдена в файле - Блок '' имеет недействительный формат свойства состояния блока ''." -warning.config.block.state.missing_real_id: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'id' аргумент для 'state'. 'id' это идентификатор блока на стороне сервера, который уникален для каждого типа состояния блока. Если вы создаете блок на стороне сервера с 'note_block' и идентификатор 30, тогда реальный идентификатор блока будет 'craftengine:note_block_30'." warning.config.block.state.missing_state: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'state' аргумент для 'state'." warning.config.block.state.missing_properties: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'properties' раздел для 'states'." warning.config.block.state.missing_appearances: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'appearances' раздел для 'states'." warning.config.block.state.missing_variants: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'variants' раздел для 'states'." -warning.config.block.state.variant.missing_appearance: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'appearance' аргумент для варианта ''." warning.config.block.state.variant.invalid_appearance: "Проблема найдена в файле - Блок '' имеет ошибку, что вариант '' использует несуществующий внешний вид ''." warning.config.block.state.invalid_vanilla: "Проблема найдена в файле - Блок '' имеет недействительное состояние ванильного блока ''." warning.config.block.state.unavailable_vanilla: "Проблема найдена в файле - Блок '' использует недоступное состояние ванильного блока ''. Пожалуйста, освободите это состояние в mappings.yml." warning.config.block.state.invalid_vanilla_id: "Проблема найдена в файле - Блок '' использует состояние ванильного блока '', что превышает доступный диапазон слотов '0~'." -warning.config.block.state.conflict: "Проблема найдена в файле - Блок '' использует состояние ванильного блока '' которое занято ''." -warning.config.block.state.bind_failed: "Проблема найдена в файле - Блоку '' не удалось привязать реальное состояние блока для '', так как состояние занято ''." +warning.config.block.state.id.conflict: "Проблема найдена в файле - Блоку '' не удалось привязать реальное состояние блока для '', так как состояние занято ''." warning.config.block.state.model.missing_path: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'path' опция для 'model'." warning.config.block.state.model.invalid_path: "Проблема найдена в файле - Блок '' имеет 'path' аргумент '' содержит недопустимые символы. Пожалуйста, прочтите https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.block.settings.unknown: "Проблема найдена в файле - Блок '' использует неизвестный тип настройки ''." diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index e5b79c186..d283b67c1 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -120,7 +120,7 @@ warning.config.item.settings.unknown: " dosyasında sorun bulundu warning.config.item.missing_material: " dosyasında sorun bulundu - '' eşyası gerekli 'material' argümanı eksik." warning.config.item.invalid_material: " dosyasında sorun bulundu - '' eşyası geçersiz bir malzeme türü '' kullanıyor." warning.config.item.bad_custom_model_data: " dosyasında sorun bulundu - '' eşyası çok büyük bir özel model verisi '' kullanıyor. 16.777.216'dan düşük bir değer kullanmanız önerilir." -warning.config.item.custom_model_data_conflict: " dosyasında sorun bulundu - '' eşyası, '' eşyası tarafından işgal edilmiş bir özel model verisi '' kullanıyor." +warning.config.item.custom_model_data.conflict: " dosyasında sorun bulundu - '' eşyası, '' eşyası tarafından işgal edilmiş bir özel model verisi '' kullanıyor." warning.config.item.missing_model_id: " dosyasında sorun bulundu - '' eşyası gerekli 'custom-model-data' veya 'item-model' argümanı eksik." warning.config.item.behavior.missing_type: " dosyasında sorun bulundu - '' eşyası, eşya davranışı için gerekli 'type' argümanı eksik." warning.config.item.behavior.invalid_type: " dosyasında sorun bulundu - '' eşyası geçersiz bir eşya davranış türü '' kullanıyor." @@ -173,18 +173,15 @@ warning.config.block.state.property.missing_type: " dosyasında s warning.config.block.state.property.invalid_type: " dosyasında sorun bulundu - '' bloğu, '' özelliği için geçersiz bir 'type' argümanı '' kullanıyor." warning.config.block.state.property.integer.invalid_range: " dosyasında sorun bulundu - '' bloğu, '' tamsayı özelliği için geçersiz bir 'range' argümanı '' kullanıyor. Doğru sözdizimi: 1~2." warning.config.block.state.property.invalid_format: " dosyasında sorun bulundu - '' bloğu, geçersiz bir blok durum özelliği formatı '' kullanıyor." -warning.config.block.state.missing_real_id: " dosyasında sorun bulundu - '' bloğu, 'state' için gerekli 'id' argümanı eksik. 'id', her blok durumu türü için benzersiz olan sunucu tarafı blok kimliğidir. 'note_block' ve id 30 ile bir sunucu tarafı blok oluşturursanız, gerçek blok kimliği 'craftengine:note_block_30' olur." warning.config.block.state.missing_state: " dosyasında sorun bulundu - '' bloğu, 'state' için gerekli 'state' argümanı eksik." warning.config.block.state.missing_properties: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'properties' bölümü eksik." warning.config.block.state.missing_appearances: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'appearances' bölümü eksik." warning.config.block.state.missing_variants: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'variants' bölümü eksik." -warning.config.block.state.variant.missing_appearance: " dosyasında sorun bulundu - '' bloğu, '' varyantı için gerekli 'appearance' argümanı eksik." warning.config.block.state.variant.invalid_appearance: " dosyasında sorun bulundu - '' bloğunda, '' varyantının var olmayan bir görünüm '' kullandığı bir hata var." warning.config.block.state.invalid_vanilla: " dosyasında sorun bulundu - '' bloğu geçersiz bir vanilya blok durumu '' kullanıyor." warning.config.block.state.unavailable_vanilla: " dosyasında sorun bulundu - '' bloğu kullanılamayan bir vanilya blok durumu '' kullanıyor. Lütfen bu durumu mappings.yml dosyasında serbest bırakın." warning.config.block.state.invalid_vanilla_id: " dosyasında sorun bulundu - '' bloğu, mevcut yuva aralığı '0~' aşan bir vanilya blok durumu '' kullanıyor." -warning.config.block.state.conflict: " dosyasında sorun bulundu - '' bloğu, '' tarafından işgal edilmiş bir vanilya blok durumu '' kullanıyor." -warning.config.block.state.bind_failed: " dosyasında sorun bulundu - '' bloğu, durum '' tarafından işgal edildiği için '' için gerçek blok durumu bağlamada başarısız oldu." +warning.config.block.state.id.conflict: " dosyasında sorun bulundu - '' bloğu, durum '' tarafından işgal edildiği için '' için gerçek blok durumu bağlamada başarısız oldu." warning.config.block.state.model.missing_path: " dosyasında sorun bulundu - '' bloğu, 'model' için gerekli 'path' seçeneği eksik." warning.config.block.state.model.invalid_path: " dosyasında sorun bulundu - '' bloğunun, yasak karakterler içeren bir 'path' argümanı '' var. Lütfen https://minecraft.wiki/w/Resource_location#Legal_characters sayfasını okuyun." warning.config.block.settings.unknown: " dosyasında sorun bulundu - '' bloğu bilinmeyen bir ayar türü '' kullanıyor." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 56720fb27..04bea5c58 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -192,7 +192,7 @@ warning.config.item.invalid_material: "在文件 发现问题 - warning.config.item.invalid_custom_model_data: "在文件 发现问题 - 物品 '' 使用了无效的负数模型值 ''" warning.config.item.bad_custom_model_data: "在文件 发现问题 - 物品 '' 使用的自定义模型数据 '' 数值过大 建议使用小于 16,777,216 的值" warning.config.item.item_model.conflict: "在文件 发现问题 - 物品 '' 使用了无效的 'item-model' 选项. 这个 item-model 已经存在对应的原版物品" -warning.config.item.custom_model_data_conflict: "在文件 发现问题 - 物品 '' 使用的自定义模型数据 '' 已被物品 '' 占用" +warning.config.item.custom_model_data.conflict: "在文件 发现问题 - 物品 '' 使用的自定义模型数据 '' 已被物品 '' 占用" warning.config.item.invalid_component: "在文件 发现问题 - 物品 '' 使用了未知的数据组件 ''" warning.config.item.missing_model_id: "在文件 发现问题 - 物品 '' 缺少必需的 'custom-model-data' 或 'item-model' 参数" warning.config.item.missing_model: "在文件 中发现问题 - 物品 '' 缺少支持 1.21.4+ 资源包必需的 'model' 配置项" @@ -260,19 +260,16 @@ warning.config.block.state.property.missing_type: "在文件 发 warning.config.block.state.property.invalid_type: "在文件 发现问题 - 方块 '' 的属性 '' 使用了无效的类型参数 ''" warning.config.block.state.property.integer.invalid_range: "在文件 发现问题 - 方块 '' 的整数属性 '' 使用了无效的范围参数 '' 正确语法: 1~2" warning.config.block.state.property.invalid_format: "在文件 发现问题 - 方块 '' 使用了无效的方块状态属性格式 ''" -warning.config.block.state.missing_real_id: "在文件 发现问题 - 方块 '' 的 'state' 缺少必需的 'id' 参数 该 ID 是服务端方块 ID 用于唯一标识每种方块状态类型" warning.config.block.state.missing_state: "在文件 发现问题 - 方块 '' 的 'state' 缺少必需的 'state' 参数" warning.config.block.state.missing_properties: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'properties' 段落" warning.config.block.state.missing_appearances: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'appearances' 段落" warning.config.block.state.missing_variants: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'variants' 段落" -warning.config.block.state.variant.missing_appearance: "在文件 发现问题 - 方块 '' 的变体 '' 缺少必需的 'appearance' 参数" warning.config.block.state.variant.invalid_appearance: "在文件 发现问题 - 方块 '' 的变体 '' 使用了不存在的 appearance ''" warning.config.block.state.invalid_vanilla: "在文件 发现问题 - 方块 '' 使用了无效的原版方块状态 ''" warning.config.block.state.unavailable_vanilla: "在文件 发现问题 - 方块 '' 使用了不可用的原版方块状态 '' 请在 mappings.yml 中释放该状态" warning.config.block.state.invalid_vanilla_id: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 超出可用槽位范围 '0~'" -warning.config.block.state.conflict: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 已被 '' 占用" -warning.config.block.state.bind_failed: "在文件 发现问题 - 方块 '' 无法为 '' 绑定真实方块状态 '' 因该状态已被 '' 占用" -warning.config.block.state.invalid_real_id: "在文件 发现问题 - 方块 '' 使用的真实方块状态 '' 超出可用槽位范围 '0~' 如果槽位已用尽 请在 config.yml 中添加更多服务端侧方块" +warning.config.block.state.id.conflict: "在文件 发现问题 - 方块 '' 无法为 '' 绑定真实方块状态 '' 因该状态已被 '' 占用" +warning.config.block.state.invalid_id: "在文件 发现问题 - 方块 '' 使用的真实方块状态 '' 超出可用槽位范围 '0~' 如果槽位已用尽 请在 config.yml 中添加更多服务端侧方块" warning.config.block.state.model.missing_path: "在文件 发现问题 - 方块 '' 的 'model' 缺少必需的 'path' 选项" warning.config.block.state.model.invalid_path: "在文件 发现问题 - 方块 '' 的 'path' 参数 '' 包含非法字符 请参考 https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" warning.config.block.state.model.conflict: "在文件 发现问题 - 方块 '' 正尝试将模型 '' 绑定到方块状态 '' 上, 但是此状态已绑定了另一个模型 ''" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 9cb662404..bd39b0a59 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -1,32 +1,54 @@ package net.momirealms.craftengine.core.block; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigs; +import net.momirealms.craftengine.core.block.parser.BlockNbtParser; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.item.AbstractItemManager; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.pack.ResourceLocation; +import net.momirealms.craftengine.core.pack.cache.IdAllocator; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.*; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; +import net.momirealms.craftengine.core.plugin.config.SectionConfigParser; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.logger.Debugger; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.*; +import net.momirealms.sparrow.nbt.CompoundTag; import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.Predicate; public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager { @@ -50,6 +72,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final Map soundReplacements = new HashMap<>(512, 0.5f); // 用于note_block:0这样格式的自动分配 protected final Map> blockStateArranger = new HashMap<>(); + // 根据registry id找note_block:x中的x值 + protected final Map reversedBlockStateArranger = new HashMap<>(); // 全方块状态映射文件,用于网络包映射 protected final int[] blockStateMappings; // 原版方块状态数量 @@ -103,6 +127,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.byId.clear(); this.soundReplacements.clear(); this.blockStateArranger.clear(); + this.reversedBlockStateArranger.clear(); this.appearanceToRealState.clear(); Arrays.fill(this.blockStateMappings, -1); Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); @@ -125,20 +150,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Optional.ofNullable(this.byId.get(id)); } - protected void addBlockInternal(Key id, CustomBlock customBlock) { - ExceptionCollector exceptionCollector = new ExceptionCollector<>(); + protected void addBlockInternal(CustomBlock customBlock) { // 绑定外观状态等 for (ImmutableBlockState state : customBlock.variantProvider().states()) { int internalId = state.customBlockState().registryId(); int appearanceId = state.vanillaBlockState().registryId(); int index = internalId - this.vanillaBlockStateCount; - ImmutableBlockState previous = this.immutableBlockStates[index]; - // todo 应当提前判断位置 - if (previous != null && !previous.isEmpty()) { - exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.bind_failed", - state.toString(), previous.toString(), getBlockOwnerId(previous.customBlockState()).toString())); - continue; - } this.immutableBlockStates[index] = state; this.blockStateMappings[internalId] = appearanceId; this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); @@ -147,8 +164,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.modBlockStateOverrides.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(appearanceId)); } } - this.byId.put(id, customBlock); - exceptionCollector.throwIfPresent(); + this.byId.put(customBlock.id(), customBlock); } @Override @@ -210,17 +226,22 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Optional.ofNullable(this.appearanceToRealState.get(appearanceStateId)).orElse(List.of()); } + public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List> behaviorConfig); + protected abstract void resendTags(); protected abstract boolean isVanillaBlock(Key id); protected abstract Key getBlockOwnerId(int id); - protected abstract CustomBlock.Builder platformBuilder(Key id); - protected abstract void setVanillaBlockTags(Key id, List tags); - public abstract int vanillaBlockStateCount(); + protected abstract int vanillaBlockStateCount(); + + protected abstract CustomBlock createCustomBlock(@NotNull Holder.Reference holder, + @NotNull BlockStateVariantProvider variantProvider, + @NotNull Map>> events, + @Nullable LootTable lootTable); public class BlockStateMappingParser implements SectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"}; @@ -261,7 +282,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem continue; } AbstractBlockManager.this.blockStateMappings[beforeState.registryId()] = afterState.registryId(); - AbstractBlockManager.this.blockStateArranger.computeIfAbsent(getBlockOwnerId(beforeState), k -> new ArrayList<>()).add(afterState); + List blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(getBlockOwnerId(beforeState), k -> new ArrayList<>()); + blockStateWrappers.add(beforeState); + AbstractBlockManager.this.reversedBlockStateArranger.put(beforeState.registryId(), blockStateWrappers.size() - 1); } exceptionCollector.throwIfPresent(); } @@ -269,6 +292,63 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem public class BlockParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; + private final IdAllocator internalIdAllocator; + private final Map appearanceIdAllocators = new HashMap<>(); + private final List pendingConfigSections = new ArrayList<>(); + + public BlockParser() { + this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json")); + } + + public void addPendingConfigSection(PendingConfigSection section) { + this.pendingConfigSections.add(section); + } + + @Override + public void postProcess() { + this.internalIdAllocator.processPendingAllocations(); + try { + this.internalIdAllocator.saveToCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block state allocation", e); + } + } + + @Override + public void preProcess() { + this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1); + try { + this.internalIdAllocator.loadFromCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block state allocation cache", e); + } + for (PendingConfigSection section : this.pendingConfigSections) { + ResourceConfigUtils.runCatching( + section.path(), + section.node(), + () -> parseSection(section.pack(), section.path(), section.node(), section.id(), section.config()), + () -> GsonHelper.get().toJson(section.config()) + ); + } + this.pendingConfigSections.clear(); + } + + @Nullable + public IdAllocator getOrCreateAppearanceIdAllocator(Key type) { + if (!AbstractBlockManager.this.blockStateArranger.containsKey(type)) { + return null; + } + return this.appearanceIdAllocators.computeIfAbsent(type, k -> { + IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("visual-block-states").resolve(k.value() + ".json")); + newAllocator.reset(0, AbstractBlockManager.this.blockStateArranger.get(type).size()); + try { + newAllocator.loadFromCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states cache for block " + k.asString(), e); + } + return newAllocator; + }); + } @Override public String[] sectionId() { @@ -289,7 +369,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (AbstractBlockManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.block.duplicate"); } - parseCustomBlock(id, section); + parseCustomBlock(path, node, id, section); } } @@ -304,86 +384,171 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - private void parseCustomBlock(Key id, Map section) { - // 获取方块设置 + private void parseCustomBlock(Path path, String node, Key id, Map section) { + // 获取共享方块设置 BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true)); - // 读取基础外观配置 - Map> properties; - Map appearances; - Map variants; // 读取states区域 - Map stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow( - ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true); + Map stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true); boolean singleState = !stateSection.containsKey("properties"); - // 单方块状态 - if (singleState) { - int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); - // 获取原版外观的注册表id - BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state")); - Optional[]> blockEntityRenderer = parseBlockEntityRender(stateSection.get("entity-renderer")); - // 为原版外观赋予外观模型并检查模型冲突 - this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models")); - // 设置参数 - properties = Map.of(); - appearances = Map.of("", new BlockStateAppearance(appearanceState, blockEntityRenderer)); - variants = Map.of("", new BlockStateVariant("", settings, getInternalBlockState(internalId))); - } - // 多方块状态 - else { - properties = parseBlockProperties(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), "properties")); - appearances = parseBlockAppearances(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances")); - variants = parseBlockVariants( - ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"), - appearances::containsKey, settings - ); - } + // 读取方块的property,通过property决定 + Map> properties = singleState ? Map.of() : parseBlockProperties(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), "properties")); + // 注册方块容器 + Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).getOrRegisterForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), id)); - addBlockInternal(id, platformBuilder(id) - .appearances(appearances) - .variantMapper(variants) - .properties(properties) - .settings(settings) - .lootTable(LootTable.fromMap(ResourceConfigUtils.getAsMapOrNull(section.get("loot"), "loot"))) - .behavior(MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors"))) - .events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))) - .build()); - } + // 根据properties生成variant provider + BlockStateVariantProvider variantProvider = new BlockStateVariantProvider(holder, (owner, propertyMap) -> { + ImmutableBlockState blockState = new ImmutableBlockState(owner, propertyMap); + blockState.setSettings(settings); + return blockState; + }, properties); - private Map parseBlockVariants(Map variantsSection, - Predicate appearanceValidator, - BlockSettings parentSettings) { - Map variants = new HashMap<>(); - for (Map.Entry entry : variantsSection.entrySet()) { - Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - String variantNBT = entry.getKey(); - String appearance = ResourceConfigUtils.requireNonEmptyStringOrThrow(variantSection.get("appearance"), "warning.config.block.state.variant.missing_appearance"); - if (!appearanceValidator.test(appearance)) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearance); + ImmutableList states = variantProvider.states(); + List> internalIdAllocators = new ArrayList<>(states.size()); + + // 如果用户指定了起始id + if (stateSection.containsKey("id")) { + int startingId = ResourceConfigUtils.getAsInt(stateSection.get("id"), "id"); + int endingId = startingId + states.size() - 1; + if (startingId < 0 || endingId >= Config.serverSideBlocks()) { + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_id", startingId + "~" + endingId, String.valueOf(Config.serverSideBlocks() - 1)); + } + // 先检测范围冲突 + List> conflicts = this.internalIdAllocator.getFixedIdsBetween(startingId, endingId); + if (!conflicts.isEmpty()) { + ExceptionCollector exceptionCollector = new ExceptionCollector<>(); + for (Pair conflict : conflicts) { + int internalId = conflict.right(); + int index = internalId - startingId; + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.id.conflict", states.get(index).toString(), conflict.left(), BlockManager.createCustomBlockKey(internalId).toString())); + } + exceptionCollector.throwIfPresent(); + } + // 强行分配id + for (ImmutableBlockState blockState : states) { + String blockStateId = blockState.toString(); + internalIdAllocators.add(this.internalIdAllocator.assignFixedId(blockStateId, startingId++)); + } + } + // 未指定,则使用自动分配 + else { + for (ImmutableBlockState blockState : states) { + String blockStateId = blockState.toString(); + internalIdAllocators.add(this.internalIdAllocator.requestAutoId(blockStateId)); } - BlockStateWrapper internalBlockState = getInternalBlockState(ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id")); - Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); - variants.put(variantNBT, new BlockStateVariant(appearance, anotherSetting == null ? parentSettings : BlockSettings.ofFullCopy(parentSettings, anotherSetting), internalBlockState)); } - return variants; - } - private BlockStateWrapper getInternalBlockState(int internalId) { - if (internalId >= Config.serverSideBlocks()) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", BlockManager.createCustomBlockKey(internalId).asString(), String.valueOf(Config.serverSideBlocks() - 1)); - } - return BlockRegistryMirror.byId(internalId + vanillaBlockStateCount()); - } + CompletableFutures.allOf(internalIdAllocators).thenRun(() -> ResourceConfigUtils.runCatching(path, node, () -> { + for (int i = 0; i < internalIdAllocators.size(); i++) { + CompletableFuture future = internalIdAllocators.get(i); + try { + int internalId = future.get(); + states.get(i).setCustomBlockState(BlockRegistryMirror.byId(internalId + AbstractBlockManager.this.vanillaBlockStateCount)); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + // 这里不会有conflict了,因为之前已经判断过了 + if (cause instanceof IdAllocator.IdExhaustedException) { + throw new LocalizedResourceConfigException("warning.config.block.state.id.exhausted"); + } else { + Debugger.BLOCK.warn(() -> "Unknown error while allocating internal block state id.", cause); + return; + } + } catch (InterruptedException e) { + AbstractBlockManager.this.plugin.logger().warn("Interrupted while parsing allocating internal block state", e); + return; + } + } - private Map parseBlockAppearances(Map appearancesSection) { - Map appearances = new HashMap<>(); - for (Map.Entry entry : appearancesSection.entrySet()) { - Map appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - BlockStateWrapper appearanceId = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow( - appearanceSection.get("state"), "warning.config.block.state.missing_state")); - this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(appearanceSection, "model", "models")); - appearances.put(entry.getKey(), new BlockStateAppearance(appearanceId, parseBlockEntityRender(appearanceSection.get("entity-renderer")))); - } - return appearances; + // 创建自定义方块 + AbstractCustomBlock customBlock = (AbstractCustomBlock) createCustomBlock( + holder, + variantProvider, + EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")), + LootTable.fromMap(ResourceConfigUtils.getAsMapOrNull(section.get("loot"), "loot")) + ); + BlockBehavior blockBehavior = createBlockBehavior(customBlock, MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors"))); + customBlock.setBehavior(blockBehavior); + holder.bindValue(customBlock); + + // 单状态 + if (singleState) { + BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state")); + this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models")); + ImmutableBlockState onlyState = states.getFirst(); + // 为唯一的状态绑定外观 + onlyState.setVanillaBlockState(appearanceState); + parseBlockEntityRender(stateSection.get("entity-renderer")).ifPresent(onlyState::setConstantRenderers); + } else { + BlockStateWrapper anyAppearanceState = null; + Map appearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"); + // 也不能为空 + if (appearancesSection.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances"); + } + Map appearances = Maps.newHashMap(); + // 先解析所有的外观 + for (Map.Entry entry : appearancesSection.entrySet()) { + Map appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); + // 解析对应的视觉方块 + BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(appearanceSection.get("state"), "warning.config.block.state.missing_state")); + this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(appearanceSection, "model", "models")); + appearances.put(entry.getKey(), new BlockStateAppearance(appearanceState, parseBlockEntityRender(appearanceSection.get("entity-renderer")))); + if (anyAppearanceState == null) { + anyAppearanceState = appearanceState; + } + } + // 解析变体 + Map variantsSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"); + for (Map.Entry entry : variantsSection.entrySet()) { + Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); + String variantNBT = entry.getKey(); + // 先解析nbt,找到需要修改的方块状态 + CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT); + if (tag == null) { + throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT); + } + List possibleStates = variantProvider.getPossibleStates(tag); + Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); + if (anotherSetting != null) { + for (ImmutableBlockState possibleState : possibleStates) { + possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting)); + } + } + String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance")); + if (appearanceName != null) { + BlockStateAppearance appearance = appearances.get(appearanceName); + if (appearance == null) { + throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName); + } + for (ImmutableBlockState possibleState : possibleStates) { + possibleState.setVanillaBlockState(appearance.blockState()); + appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); + } + } + } + // 为没有外观的方块状态填充 + for (ImmutableBlockState blockState : states) { + if (blockState.vanillaBlockState() == null) { + blockState.setVanillaBlockState(anyAppearanceState); + } + } + } + + // 获取方块实体行为 + EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior(); + boolean isEntityBlock = entityBlockBehavior != null; + + // 绑定行为 + for (ImmutableBlockState blockState : states) { + blockState.setBehavior(blockBehavior); + if (isEntityBlock) { + blockState.setBlockEntityType(entityBlockBehavior.blockEntityType()); + } + } + + // 添加方块 + addBlockInternal(customBlock); + + }, () -> GsonHelper.get().toJson(section))); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index d62c103d8..298de8ccd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -1,9 +1,6 @@ package net.momirealms.craftengine.core.block; -import com.google.common.collect.ImmutableMap; import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; -import net.momirealms.craftengine.core.block.parser.BlockNbtParser; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.loot.LootTable; @@ -11,7 +8,6 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; import net.momirealms.sparrow.nbt.CompoundTag; @@ -23,79 +19,31 @@ import java.util.*; import java.util.function.BiFunction; public abstract class AbstractCustomBlock implements CustomBlock { - protected final Holder holder; - protected final Key id; + protected final Holder.Reference holder; protected final BlockStateVariantProvider variantProvider; - protected final Map> properties; - protected final BlockBehavior behavior; protected final BiFunction placementFunction; protected final ImmutableBlockState defaultState; protected final Map>> events; @Nullable protected final LootTable lootTable; + protected BlockBehavior behavior = EmptyBlockBehavior.INSTANCE; protected AbstractCustomBlock( - @NotNull Key id, @NotNull Holder.Reference holder, - @NotNull Map> properties, - @NotNull Map appearances, - @NotNull Map variantMapper, - @NotNull BlockSettings settings, + @NotNull BlockStateVariantProvider variantProvider, @NotNull Map>> events, - @Nullable List> behaviorConfig, @Nullable LootTable lootTable ) { - holder.bindValue(this); this.holder = holder; - this.id = id; this.lootTable = lootTable; - this.properties = ImmutableMap.copyOf(properties); this.events = events; - this.variantProvider = new BlockStateVariantProvider(holder, ImmutableBlockState::new, properties); + this.variantProvider = variantProvider; this.defaultState = this.variantProvider.getDefaultState(); - this.behavior = setupBehavior(behaviorConfig); List> placements = new ArrayList<>(4); - for (Map.Entry> propertyEntry : this.properties.entrySet()) { + for (Map.Entry> propertyEntry : this.variantProvider.properties().entrySet()) { placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue())); } this.placementFunction = composite(placements); - EntityBlockBehavior entityBlockBehavior = this.behavior.getEntityBehavior(); - boolean isEntityBlock = entityBlockBehavior != null; - - for (Map.Entry entry : variantMapper.entrySet()) { - String nbtString = entry.getKey(); - CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); - if (tag == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); - } - List possibleStates = this.getPossibleStates(tag); - if (possibleStates.size() != 1) { - throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); - } - BlockStateVariant blockStateVariant = entry.getValue(); - BlockStateAppearance blockStateAppearance = appearances.get(blockStateVariant.appearance()); - // Late init states - ImmutableBlockState state = possibleStates.getFirst(); - state.setSettings(blockStateVariant.settings()); - state.setVanillaBlockState(blockStateAppearance.blockState()); - state.setCustomBlockState(blockStateVariant.blockState()); - blockStateAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); - } - - // double check if there's any invalid state - for (ImmutableBlockState state : this.variantProvider().states()) { - state.setBehavior(this.behavior); - if (state.settings() == null) { - state.setSettings(settings); - } - if (isEntityBlock) { - state.setBlockEntityType(entityBlockBehavior.blockEntityType()); - } - } - } - - protected BlockBehavior setupBehavior(List> behaviorConfig) { - return EmptyBlockBehavior.INSTANCE; } private static BiFunction composite(List> placements) { @@ -137,28 +85,16 @@ public abstract class AbstractCustomBlock implements CustomBlock { @NotNull @Override public final Key id() { - return this.id; + return this.holder.key().location(); + } + + public void setBehavior(@Nullable BlockBehavior behavior) { + this.behavior = behavior; } @Override public List getPossibleStates(CompoundTag nbt) { - List tempStates = new ArrayList<>(); - tempStates.add(defaultState()); - for (Property property : this.variantProvider.getDefaultState().getProperties()) { - Tag value = nbt.get(property.name()); - if (value != null) { - tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value))); - } else { - List newStates = new ArrayList<>(); - for (ImmutableBlockState state : tempStates) { - for (Object possibleValue : property.possibleValues()) { - newStates.add(ImmutableBlockState.with(state, property, possibleValue)); - } - } - tempStates = newStates; - } - } - return tempStates; + return this.variantProvider.getPossibleStates(nbt); } @Override @@ -179,12 +115,12 @@ public abstract class AbstractCustomBlock implements CustomBlock { @Override public @Nullable Property getProperty(String name) { - return this.properties.get(name); + return this.variantProvider.getProperty(name); } @Override public @NotNull Collection> properties() { - return this.properties.values(); + return this.variantProvider.properties().values(); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java index d393d4b9d..2997c5dfa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSounds.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.sound.SoundData; -import net.momirealms.craftengine.core.util.Key; import java.util.Map; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java index 445f9c518..eaa50da56 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java @@ -9,11 +9,11 @@ import java.util.*; import java.util.stream.Collectors; public class BlockStateHolder { - protected final Holder owner; + protected final Holder.Reference owner; private final Reference2ObjectArrayMap, Comparable> propertyMap; private Map, ImmutableBlockState[]> withMap; - public BlockStateHolder(Holder owner, Reference2ObjectArrayMap, Comparable> propertyMap) { + public BlockStateHolder(Holder.Reference owner, Reference2ObjectArrayMap, Comparable> propertyMap) { this.owner = owner; this.propertyMap = new Reference2ObjectArrayMap<>(propertyMap); } @@ -39,9 +39,9 @@ public class BlockStateHolder { @Override public String toString() { if (this.propertyMap.isEmpty()) { - return this.owner.value().id().toString(); + return this.owner.key().location().toString(); } - return this.owner.value().id() + "[" + getPropertiesAsString() + "]"; + return this.owner.key().location() + "[" + getPropertiesAsString() + "]"; } public String getPropertiesAsString() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java deleted file mode 100644 index 8a27403c8..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariant.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.momirealms.craftengine.core.block; - -public class BlockStateVariant { - private final String appearance; - private final BlockSettings settings; - private final BlockStateWrapper blockState; - - public BlockStateVariant(String appearance, BlockSettings settings, BlockStateWrapper blockState) { - this.appearance = appearance; - this.settings = settings; - this.blockState = blockState; - } - - public String appearance() { - return appearance; - } - - public BlockSettings settings() { - return settings; - } - - public BlockStateWrapper blockState() { - return blockState; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariantProvider.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariantProvider.java index 8b131563f..75c6c9080 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariantProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateVariantProvider.java @@ -8,6 +8,8 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Pair; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,7 +25,7 @@ public final class BlockStateVariantProvider { private final ImmutableList states; private final Holder owner; - public BlockStateVariantProvider(Holder owner, Factory, ImmutableBlockState> factory, Map> propertiesMap) { + public BlockStateVariantProvider(Holder.Reference owner, Factory, ImmutableBlockState> factory, Map> propertiesMap) { this.owner = owner; this.properties = ImmutableSortedMap.copyOf(propertiesMap); @@ -59,6 +61,27 @@ public final class BlockStateVariantProvider { this.states = ImmutableList.copyOf(list); } + public List getPossibleStates(CompoundTag nbt) { + List tempStates = new ArrayList<>(); + ImmutableBlockState defaultState = getDefaultState(); + tempStates.add(defaultState); + for (Property property : defaultState.getProperties()) { + Tag value = nbt.get(property.name()); + if (value != null) { + tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value))); + } else { + List newStates = new ArrayList<>(); + for (ImmutableBlockState state : tempStates) { + for (Object possibleValue : property.possibleValues()) { + newStates.add(ImmutableBlockState.with(state, property, possibleValue)); + } + } + tempStates = newStates; + } + } + return tempStates; + } + public interface Factory { S create(O owner, Reference2ObjectArrayMap, Comparable> propertyMap); } @@ -80,6 +103,11 @@ public final class BlockStateVariantProvider { return this.owner; } + @NotNull + public ImmutableSortedMap> properties() { + return this.properties; + } + @Nullable public Property getProperty(String name) { return this.properties.get(name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java index 744d0f4c9..8f71a3707 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java @@ -40,23 +40,4 @@ public interface CustomBlock { ImmutableBlockState getStateForPlacement(BlockPlaceContext context); void setPlacedBy(BlockPlaceContext context, ImmutableBlockState state); - - interface Builder { - - Builder events(Map>> events); - - Builder appearances(Map appearances); - - Builder behavior(List> behavior); - - Builder lootTable(LootTable lootTable); - - Builder properties(Map> properties); - - Builder settings(BlockSettings settings); - - Builder variantMapper(Map variantMapper); - - @NotNull CustomBlock build(); - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java index a6aa4e0f6..a095e3dc4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java @@ -1,18 +1,32 @@ package net.momirealms.craftengine.core.block; +import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; -import java.util.List; import java.util.Map; public final class EmptyBlock extends AbstractCustomBlock { - public static EmptyBlock INSTANCE; - public static ImmutableBlockState STATE; + public static final EmptyBlock INSTANCE; + public static final ImmutableBlockState STATE; - public EmptyBlock(Key id, Holder.Reference holder) { - super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null); - INSTANCE = this; - STATE = defaultState(); + static { + Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK) + .registerForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), Key.withDefaultNamespace("empty"))); + INSTANCE = new EmptyBlock(holder); + holder.bindValue(INSTANCE); + STATE = INSTANCE.defaultState(); + STATE.setSettings(BlockSettings.of()); + STATE.setBehavior(EmptyBlockBehavior.INSTANCE); + } + + private EmptyBlock(Holder.Reference holder) { + super(holder, new BlockStateVariantProvider(holder, ImmutableBlockState::new, Map.of()), Map.of(), null); + } + + public static void initialize() { } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 18e6460d2..8a68b9527 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -35,7 +35,7 @@ public final class ImmutableBlockState extends BlockStateHolder { private BlockEntityElementConfig[] renderers; ImmutableBlockState( - Holder owner, + Holder.Reference owner, Reference2ObjectArrayMap, Comparable> propertyMap ) { super(owner, propertyMap); @@ -129,7 +129,7 @@ public final class ImmutableBlockState extends BlockStateHolder { public CompoundTag toNbtToSave(CompoundTag properties) { CompoundTag tag = new CompoundTag(); tag.put("properties", properties); - tag.put("id", NBT.createString(this.owner.value().id().asString())); + tag.put("id", NBT.createString(this.owner.key().location().asString())); return tag; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java index 3c16dcae3..abd181734 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java @@ -2,18 +2,16 @@ package net.momirealms.craftengine.core.block; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import net.momirealms.craftengine.core.registry.Holder; -import net.momirealms.craftengine.core.util.Key; import net.momirealms.sparrow.nbt.CompoundTag; import java.util.HashMap; -import java.util.List; import java.util.Map; public final class InactiveCustomBlock extends AbstractCustomBlock { private final Map cachedData = new HashMap<>(); - public InactiveCustomBlock(Key id, Holder.Reference holder) { - super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null); + public InactiveCustomBlock(Holder.Reference holder) { + super(holder, new BlockStateVariantProvider(holder, ImmutableBlockState::new, Map.of()), Map.of(), null); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockNbtParser.java b/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockNbtParser.java index 151709a49..176b0d177 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockNbtParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockNbtParser.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.block.parser; +import net.momirealms.craftengine.core.block.BlockStateVariantProvider; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.util.StringReader; @@ -7,11 +8,13 @@ import net.momirealms.sparrow.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.function.Function; + public final class BlockNbtParser { private BlockNbtParser() {} @Nullable - public static CompoundTag deserialize(@NotNull CustomBlock block, @NotNull String data) { + public static CompoundTag deserialize(@NotNull Function> propertyProvider, @NotNull String data) { StringReader reader = StringReader.simple(data); CompoundTag properties = new CompoundTag(); while (reader.canRead()) { @@ -24,7 +27,7 @@ public final class BlockNbtParser { if (propertyValue.isEmpty()) { return null; } - Property property = block.getProperty(propertyName); + Property property = propertyProvider.apply(propertyName); if (property != null) { property.createOptionalTag(propertyValue).ifPresent(tag -> { properties.put(propertyName, tag); @@ -38,4 +41,14 @@ public final class BlockNbtParser { } return properties; } + + @Nullable + public static CompoundTag deserialize(@NotNull CustomBlock block, @NotNull String data) { + return deserialize(block::getProperty, data); + } + + @Nullable + public static CompoundTag deserialize(@NotNull BlockStateVariantProvider variantProvider, @NotNull String data) { + return deserialize(variantProvider::getProperty, data); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 0dee19193..c887cb246 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -28,6 +28,7 @@ import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.util.*; import org.incendo.cloud.suggestion.Suggestion; @@ -424,17 +425,27 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } // 当模型值完成分配的时候 - customModelDataFuture.whenComplete((customModelData, throwable) -> ResourceConfigUtils.runCatching(path, node, () -> { - + customModelDataFuture.whenComplete((cmd, throwable) -> ResourceConfigUtils.runCatching(path, node, () -> { + int customModelData; if (throwable != null) { // 检测custom model data 冲突 if (throwable instanceof IdAllocator.IdConflictException exception) { - throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(exception.id()), exception.previousOwner()); + if (section.containsKey("model") || section.containsKey("legacy-model")) { + throw new LocalizedResourceConfigException("warning.config.item.custom_model_data.conflict", String.valueOf(exception.id()), exception.previousOwner()); + } + customModelData = exception.id(); } // custom model data 已被用尽,不太可能 else if (throwable instanceof IdAllocator.IdExhaustedException) { - throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_exhausted"); + throw new LocalizedResourceConfigException("warning.config.item.custom_model_data.exhausted"); } + // 未知错误 + else { + Debugger.ITEM.warn(() -> "Unknown error while allocating custom model data.", throwable); + return; + } + } else { + customModelData = cmd; } // item model diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java index 21399de3d..f27f4effd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java @@ -6,13 +6,10 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Registries; import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceKey; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.Map; public class ItemBehaviors { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PendingConfigSection.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PendingConfigSection.java new file mode 100644 index 000000000..db74096e4 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PendingConfigSection.java @@ -0,0 +1,9 @@ +package net.momirealms.craftengine.core.pack; + +import net.momirealms.craftengine.core.util.Key; + +import java.nio.file.Path; +import java.util.Map; + +public record PendingConfigSection(Pack pack, Path path, String node, Key id, Map config) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java index a4fe866f6..5977120db 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java @@ -7,6 +7,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import net.momirealms.craftengine.core.util.FileUtils; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.Pair; import java.io.IOException; import java.nio.file.Files; @@ -57,6 +58,9 @@ public class IdAllocator { continue; } allocateId(id, future); + } else { + // 避免其他条目分配到过时的值上 + this.occupiedIdSet.set(entry.getValue()); } } @@ -120,6 +124,18 @@ public class IdAllocator { return CompletableFuture.completedFuture(id); } + public List> getFixedIdsBetween(int minId, int maxId) { + BiMap inverse = this.forcedIdMap.inverse(); + List> result = new ArrayList<>(); + for (int i = minId; i <= maxId; i++) { + String s = inverse.get(i); + if (s != null) { + result.add(Pair.of(s, i)); + } + } + return result; + } + /** * 请求自动分配ID * @param name 名称 @@ -205,8 +221,6 @@ public class IdAllocator { * 保存缓存到文件 */ public void saveToCache() throws IOException { - FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent()); - // 创建按ID排序的TreeMap Map sortedById = new TreeMap<>(); for (Map.Entry entry : this.cachedIdMap.entrySet()) { @@ -219,7 +233,14 @@ public class IdAllocator { sortedJsonObject.addProperty(entry.getValue(), entry.getKey()); } - GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath); + if (sortedJsonObject.isEmpty()) { + if (Files.exists(this.cacheFilePath)) { + Files.delete(this.cacheFilePath); + } + } else { + FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent()); + GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath); + } } public static class IdConflictException extends RuntimeException { diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java index b1e319931..c746856fa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.registry; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceKey; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -89,7 +90,7 @@ public interface Holder { } @Override - public String toString() { + public @NotNull String toString() { return "Direct{" + this.value + "}"; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index b1a135387..ee688e805 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -23,6 +23,13 @@ public final class ResourceConfigUtils { return raw != null ? function.apply(raw) : defaultValue; } + public static String getAsString(@Nullable Object raw) { + if (raw == null) { + return null; + } + return raw.toString(); + } + public static > E getAsEnum(Object o, Class clazz, E defaultValue) { if (o == null) { return defaultValue; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java index 36d420260..1f0ee3319 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java @@ -76,7 +76,7 @@ public final class DefaultSectionSerializer { Holder owner = BuiltInRegistries.BLOCK.get(key).orElseGet(() -> { Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).registerForHolder( ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), key)); - InactiveCustomBlock inactiveBlock = new InactiveCustomBlock(key, holder); + InactiveCustomBlock inactiveBlock = new InactiveCustomBlock(holder); holder.bindValue(inactiveBlock); return holder; }); From bb997cf5ba9319fec470edd1a55c04e5bc2e163e Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 04:30:51 +0800 Subject: [PATCH 204/226] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/behavior/WallBlockItemBehavior.java | 2 - .../default/configuration/templates.yml | 440 +++--------------- 2 files changed, 72 insertions(+), 370 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java index 6e46af6cd..94b5254f5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.item.behavior; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; @@ -9,7 +8,6 @@ import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import java.nio.file.Path; import java.util.Map; diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 4403a7a15..044ccbf3a 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -925,121 +925,17 @@ templates#block_states: model: path: ${model_path} variants: - distance=1,persistent=false,waterlogged=false: - appearance: default - distance=2,persistent=false,waterlogged=false: - appearance: default - distance=3,persistent=false,waterlogged=false: - appearance: default - distance=4,persistent=false,waterlogged=false: - appearance: default - distance=5,persistent=false,waterlogged=false: - appearance: default - distance=6,persistent=false,waterlogged=false: - appearance: default - distance=7,persistent=false,waterlogged=false: + waterlogged=false: appearance: default + waterlogged=true: + appearance: waterlogged + settings: + resistance: 1200.0 + burnable: false + fluid-state: water + distance=7: settings: is-randomly-ticking: true - distance=1,persistent=true,waterlogged=false: - appearance: default - distance=2,persistent=true,waterlogged=false: - appearance: default - distance=3,persistent=true,waterlogged=false: - appearance: default - distance=4,persistent=true,waterlogged=false: - appearance: default - distance=5,persistent=true,waterlogged=false: - appearance: default - distance=6,persistent=true,waterlogged=false: - appearance: default - distance=7,persistent=true,waterlogged=false: - appearance: default - distance=1,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=2,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=3,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=4,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=5,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=6,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=7,persistent=false,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - is-randomly-ticking: true - fluid-state: water - distance=1,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=2,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=3,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=4,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=5,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=6,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - distance=7,persistent=true,waterlogged=true: - appearance: waterlogged - settings: - resistance: 1200.0 - burnable: false - fluid-state: water # trapdoor block default:block_state/trapdoor: properties: @@ -1223,260 +1119,132 @@ templates#block_states: x: 180 y: 90 variants: - facing=east,half=bottom,open=false,powered=false,waterlogged=false: + facing=east,half=bottom,open=false,waterlogged=false: appearance: facing=east,half=bottom,open=false,waterlogged=false - facing=east,half=bottom,open=false,powered=true,waterlogged=false: - appearance: facing=east,half=bottom,open=false,waterlogged=false - facing=east,half=bottom,open=true,powered=false,waterlogged=false: + facing=east,half=bottom,open=true,waterlogged=false: appearance: facing=east,half=bottom,open=true,waterlogged=false - facing=east,half=bottom,open=true,powered=true,waterlogged=false: - appearance: facing=east,half=bottom,open=true,waterlogged=false - facing=east,half=top,open=false,powered=false,waterlogged=false: + facing=east,half=top,open=false,waterlogged=false: appearance: facing=east,half=top,open=false,waterlogged=false - facing=east,half=top,open=false,powered=true,waterlogged=false: - appearance: facing=east,half=top,open=false,waterlogged=false - facing=east,half=top,open=true,powered=false,waterlogged=false: + facing=east,half=top,open=true,waterlogged=false: appearance: facing=east,half=top,open=true,waterlogged=false - facing=east,half=top,open=true,powered=true,waterlogged=false: - appearance: facing=east,half=top,open=true,waterlogged=false - facing=north,half=bottom,open=false,powered=false,waterlogged=false: + facing=north,half=bottom,open=false,waterlogged=false: appearance: facing=north,half=bottom,open=false,waterlogged=false - facing=north,half=bottom,open=false,powered=true,waterlogged=false: - appearance: facing=north,half=bottom,open=false,waterlogged=false - facing=north,half=bottom,open=true,powered=false,waterlogged=false: + facing=north,half=bottom,open=true,waterlogged=false: appearance: facing=north,half=bottom,open=true,waterlogged=false - facing=north,half=bottom,open=true,powered=true,waterlogged=false: - appearance: facing=north,half=bottom,open=true,waterlogged=false - facing=north,half=top,open=false,powered=false,waterlogged=false: + facing=north,half=top,open=false,waterlogged=false: appearance: facing=north,half=top,open=false,waterlogged=false - facing=north,half=top,open=false,powered=true,waterlogged=false: - appearance: facing=north,half=top,open=false,waterlogged=false - facing=north,half=top,open=true,powered=false,waterlogged=false: + facing=north,half=top,open=true,waterlogged=false: appearance: facing=north,half=top,open=true,waterlogged=false - facing=north,half=top,open=true,powered=true,waterlogged=false: - appearance: facing=north,half=top,open=true,waterlogged=false - facing=south,half=bottom,open=false,powered=false,waterlogged=false: + facing=south,half=bottom,open=false,waterlogged=false: appearance: facing=south,half=bottom,open=false,waterlogged=false - facing=south,half=bottom,open=false,powered=true,waterlogged=false: - appearance: facing=south,half=bottom,open=false,waterlogged=false - facing=south,half=bottom,open=true,powered=false,waterlogged=false: + facing=south,half=bottom,open=true,waterlogged=false: appearance: facing=south,half=bottom,open=true,waterlogged=false - facing=south,half=bottom,open=true,powered=true,waterlogged=false: - appearance: facing=south,half=bottom,open=true,waterlogged=false - facing=south,half=top,open=false,powered=false,waterlogged=false: + facing=south,half=top,open=false,waterlogged=false: appearance: facing=south,half=top,open=false,waterlogged=false - facing=south,half=top,open=false,powered=true,waterlogged=false: - appearance: facing=south,half=top,open=false,waterlogged=false - facing=south,half=top,open=true,powered=false,waterlogged=false: + facing=south,half=top,open=true,waterlogged=false: appearance: facing=south,half=top,open=true,waterlogged=false - facing=south,half=top,open=true,powered=true,waterlogged=false: - appearance: facing=south,half=top,open=true,waterlogged=false - facing=west,half=bottom,open=false,powered=false,waterlogged=false: + facing=west,half=bottom,open=false,waterlogged=false: appearance: facing=west,half=bottom,open=false,waterlogged=false - facing=west,half=bottom,open=false,powered=true,waterlogged=false: - appearance: facing=west,half=bottom,open=false,waterlogged=false - facing=west,half=bottom,open=true,powered=false,waterlogged=false: + facing=west,half=bottom,open=true,waterlogged=false: appearance: facing=west,half=bottom,open=true,waterlogged=false - facing=west,half=bottom,open=true,powered=true,waterlogged=false: - appearance: facing=west,half=bottom,open=true,waterlogged=false - facing=west,half=top,open=false,powered=false,waterlogged=false: + facing=west,half=top,open=false,waterlogged=false: appearance: facing=west,half=top,open=false,waterlogged=false - facing=west,half=top,open=false,powered=true,waterlogged=false: - appearance: facing=west,half=top,open=false,waterlogged=false - facing=west,half=top,open=true,powered=false,waterlogged=false: + facing=west,half=top,open=true,waterlogged=false: appearance: facing=west,half=top,open=true,waterlogged=false - facing=west,half=top,open=true,powered=true,waterlogged=false: - appearance: facing=west,half=top,open=true,waterlogged=false - facing=east,half=bottom,open=false,powered=false,waterlogged=true: + facing=east,half=bottom,open=false,waterlogged=true: appearance: facing=east,half=bottom,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=east,half=bottom,open=false,powered=true,waterlogged=true: - appearance: facing=east,half=bottom,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=east,half=bottom,open=true,powered=false,waterlogged=true: + facing=east,half=bottom,open=true,waterlogged=true: appearance: facing=east,half=bottom,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=east,half=bottom,open=true,powered=true,waterlogged=true: - appearance: facing=east,half=bottom,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=east,half=top,open=false,powered=false,waterlogged=true: + facing=east,half=top,open=false,waterlogged=true: appearance: facing=east,half=top,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=east,half=top,open=false,powered=true,waterlogged=true: - appearance: facing=east,half=top,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=east,half=top,open=true,powered=false,waterlogged=true: + facing=east,half=top,open=true,waterlogged=true: appearance: facing=east,half=top,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=east,half=top,open=true,powered=true,waterlogged=true: - appearance: facing=east,half=top,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,half=bottom,open=false,powered=false,waterlogged=true: + facing=north,half=bottom,open=false,waterlogged=true: appearance: facing=north,half=bottom,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=north,half=bottom,open=false,powered=true,waterlogged=true: - appearance: facing=north,half=bottom,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,half=bottom,open=true,powered=false,waterlogged=true: + facing=north,half=bottom,open=true,waterlogged=true: appearance: facing=north,half=bottom,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=north,half=bottom,open=true,powered=true,waterlogged=true: - appearance: facing=north,half=bottom,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,half=top,open=false,powered=false,waterlogged=true: + facing=north,half=top,open=false,waterlogged=true: appearance: facing=north,half=top,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=north,half=top,open=false,powered=true,waterlogged=true: - appearance: facing=north,half=top,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=north,half=top,open=true,powered=false,waterlogged=true: + facing=north,half=top,open=true,waterlogged=true: appearance: facing=north,half=top,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=north,half=top,open=true,powered=true,waterlogged=true: - appearance: facing=north,half=top,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,half=bottom,open=false,powered=false,waterlogged=true: + facing=south,half=bottom,open=false,waterlogged=true: appearance: facing=south,half=bottom,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=south,half=bottom,open=false,powered=true,waterlogged=true: - appearance: facing=south,half=bottom,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,half=bottom,open=true,powered=false,waterlogged=true: + facing=south,half=bottom,open=true,waterlogged=true: appearance: facing=south,half=bottom,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=south,half=bottom,open=true,powered=true,waterlogged=true: - appearance: facing=south,half=bottom,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,half=top,open=false,powered=false,waterlogged=true: + facing=south,half=top,open=false,waterlogged=true: appearance: facing=south,half=top,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=south,half=top,open=false,powered=true,waterlogged=true: - appearance: facing=south,half=top,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=south,half=top,open=true,powered=false,waterlogged=true: + facing=south,half=top,open=true,waterlogged=true: appearance: facing=south,half=top,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=south,half=top,open=true,powered=true,waterlogged=true: - appearance: facing=south,half=top,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,half=bottom,open=false,powered=false,waterlogged=true: + facing=west,half=bottom,open=false,waterlogged=true: appearance: facing=west,half=bottom,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=west,half=bottom,open=false,powered=true,waterlogged=true: - appearance: facing=west,half=bottom,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,half=bottom,open=true,powered=false,waterlogged=true: + facing=west,half=bottom,open=true,waterlogged=true: appearance: facing=west,half=bottom,open=true,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=west,half=bottom,open=true,powered=true,waterlogged=true: - appearance: facing=west,half=bottom,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,half=top,open=false,powered=false,waterlogged=true: + facing=west,half=top,open=false,waterlogged=true: appearance: facing=west,half=top,open=false,waterlogged=true settings: resistance: 1200.0 burnable: false fluid-state: water - facing=west,half=top,open=false,powered=true,waterlogged=true: - appearance: facing=west,half=top,open=false,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water - facing=west,half=top,open=true,powered=false,waterlogged=true: + facing=west,half=top,open=true,waterlogged=true: appearance: facing=west,half=top,open=true,waterlogged=true settings: fluid-state: water - facing=west,half=top,open=true,powered=true,waterlogged=true: - appearance: facing=west,half=top,open=true,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water # door block default:block_state/door: properties: @@ -1654,133 +1422,69 @@ templates#block_states: path: ${model_top_right_open_path} y: 90 variants: - facing=east,half=lower,hinge=left,open=false,powered=true: + facing=east,half=lower,hinge=left,open=false: appearance: facing=east,half=lower,hinge=left,open=false - facing=east,half=lower,hinge=left,open=false,powered=false: - appearance: facing=east,half=lower,hinge=left,open=false - facing=east,half=lower,hinge=right,open=false,powered=true: + facing=east,half=lower,hinge=right,open=false: appearance: facing=east,half=lower,hinge=right,open=false - facing=east,half=lower,hinge=right,open=false,powered=false: - appearance: facing=east,half=lower,hinge=right,open=false - facing=east,half=upper,hinge=left,open=false,powered=true: + facing=east,half=upper,hinge=left,open=false: appearance: facing=east,half=upper,hinge=left,open=false - facing=east,half=upper,hinge=left,open=false,powered=false: - appearance: facing=east,half=upper,hinge=left,open=false - facing=east,half=upper,hinge=right,open=false,powered=true: + facing=east,half=upper,hinge=right,open=false: appearance: facing=east,half=upper,hinge=right,open=false - facing=east,half=upper,hinge=right,open=false,powered=false: - appearance: facing=east,half=upper,hinge=right,open=false - facing=north,half=lower,hinge=left,open=false,powered=true: + facing=north,half=lower,hinge=left,open=false: appearance: facing=north,half=lower,hinge=left,open=false - facing=north,half=lower,hinge=left,open=false,powered=false: - appearance: facing=north,half=lower,hinge=left,open=false - facing=north,half=lower,hinge=right,open=false,powered=true: + facing=north,half=lower,hinge=right,open=false: appearance: facing=north,half=lower,hinge=right,open=false - facing=north,half=lower,hinge=right,open=false,powered=false: - appearance: facing=north,half=lower,hinge=right,open=false - facing=north,half=upper,hinge=left,open=false,powered=true: + facing=north,half=upper,hinge=left,open=false: appearance: facing=north,half=upper,hinge=left,open=false - facing=north,half=upper,hinge=left,open=false,powered=false: - appearance: facing=north,half=upper,hinge=left,open=false - facing=north,half=upper,hinge=right,open=false,powered=true: + facing=north,half=upper,hinge=right,open=false: appearance: facing=north,half=upper,hinge=right,open=false - facing=north,half=upper,hinge=right,open=false,powered=false: - appearance: facing=north,half=upper,hinge=right,open=false - facing=south,half=lower,hinge=left,open=false,powered=true: + facing=south,half=lower,hinge=left,open=false: appearance: facing=south,half=lower,hinge=left,open=false - facing=south,half=lower,hinge=left,open=false,powered=false: - appearance: facing=south,half=lower,hinge=left,open=false - facing=south,half=lower,hinge=right,open=false,powered=true: + facing=south,half=lower,hinge=right,open=false: appearance: facing=south,half=lower,hinge=right,open=false - facing=south,half=lower,hinge=right,open=false,powered=false: - appearance: facing=south,half=lower,hinge=right,open=false - facing=south,half=upper,hinge=left,open=false,powered=true: + facing=south,half=upper,hinge=left,open=false: appearance: facing=south,half=upper,hinge=left,open=false - facing=south,half=upper,hinge=left,open=false,powered=false: - appearance: facing=south,half=upper,hinge=left,open=false - facing=south,half=upper,hinge=right,open=false,powered=true: + facing=south,half=upper,hinge=right,open=false: appearance: facing=south,half=upper,hinge=right,open=false - facing=south,half=upper,hinge=right,open=false,powered=false: - appearance: facing=south,half=upper,hinge=right,open=false - facing=west,half=lower,hinge=left,open=false,powered=true: + facing=west,half=lower,hinge=left,open=false: appearance: facing=west,half=lower,hinge=left,open=false - facing=west,half=lower,hinge=left,open=false,powered=false: - appearance: facing=west,half=lower,hinge=left,open=false - facing=west,half=lower,hinge=right,open=false,powered=true: + facing=west,half=lower,hinge=right,open=false: appearance: facing=west,half=lower,hinge=right,open=false - facing=west,half=lower,hinge=right,open=false,powered=false: - appearance: facing=west,half=lower,hinge=right,open=false - facing=west,half=upper,hinge=left,open=false,powered=true: + facing=west,half=upper,hinge=left,open=false: appearance: facing=west,half=upper,hinge=left,open=false - facing=west,half=upper,hinge=left,open=false,powered=false: - appearance: facing=west,half=upper,hinge=left,open=false - facing=west,half=upper,hinge=right,open=false,powered=true: + facing=west,half=upper,hinge=right,open=false: appearance: facing=west,half=upper,hinge=right,open=false - facing=west,half=upper,hinge=right,open=false,powered=false: - appearance: facing=west,half=upper,hinge=right,open=false - facing=east,half=lower,hinge=left,open=true,powered=true: + facing=east,half=lower,hinge=left,open=true: appearance: facing=east,half=lower,hinge=left,open=true - facing=east,half=lower,hinge=left,open=true,powered=false: - appearance: facing=east,half=lower,hinge=left,open=true - facing=east,half=lower,hinge=right,open=true,powered=true: + facing=east,half=lower,hinge=right,open=true: appearance: facing=east,half=lower,hinge=right,open=true - facing=east,half=lower,hinge=right,open=true,powered=false: - appearance: facing=east,half=lower,hinge=right,open=true - facing=east,half=upper,hinge=left,open=true,powered=true: + facing=east,half=upper,hinge=left,open=true: appearance: facing=east,half=upper,hinge=left,open=true - facing=east,half=upper,hinge=left,open=true,powered=false: - appearance: facing=east,half=upper,hinge=left,open=true - facing=east,half=upper,hinge=right,open=true,powered=true: + facing=east,half=upper,hinge=right,open=true: appearance: facing=east,half=upper,hinge=right,open=true - facing=east,half=upper,hinge=right,open=true,powered=false: - appearance: facing=east,half=upper,hinge=right,open=true - facing=north,half=lower,hinge=left,open=true,powered=true: + facing=north,half=lower,hinge=left,open=true: appearance: facing=north,half=lower,hinge=left,open=true - facing=north,half=lower,hinge=left,open=true,powered=false: - appearance: facing=north,half=lower,hinge=left,open=true - facing=north,half=lower,hinge=right,open=true,powered=true: + facing=north,half=lower,hinge=right,open=true: appearance: facing=north,half=lower,hinge=right,open=true - facing=north,half=lower,hinge=right,open=true,powered=false: - appearance: facing=north,half=lower,hinge=right,open=true - facing=north,half=upper,hinge=left,open=true,powered=true: + facing=north,half=upper,hinge=left,open=true: appearance: facing=north,half=upper,hinge=left,open=true - facing=north,half=upper,hinge=left,open=true,powered=false: - appearance: facing=north,half=upper,hinge=left,open=true - facing=north,half=upper,hinge=right,open=true,powered=true: + facing=north,half=upper,hinge=right,open=true: appearance: facing=north,half=upper,hinge=right,open=true - facing=north,half=upper,hinge=right,open=true,powered=false: - appearance: facing=north,half=upper,hinge=right,open=true - facing=south,half=lower,hinge=left,open=true,powered=true: + facing=south,half=lower,hinge=left,open=true: appearance: facing=south,half=lower,hinge=left,open=true - facing=south,half=lower,hinge=left,open=true,powered=false: - appearance: facing=south,half=lower,hinge=left,open=true - facing=south,half=lower,hinge=right,open=true,powered=true: + facing=south,half=lower,hinge=right,open=true: appearance: facing=south,half=lower,hinge=right,open=true - facing=south,half=lower,hinge=right,open=true,powered=false: - appearance: facing=south,half=lower,hinge=right,open=true - facing=south,half=upper,hinge=left,open=true,powered=true: + facing=south,half=upper,hinge=left,open=true: appearance: facing=south,half=upper,hinge=left,open=true - facing=south,half=upper,hinge=left,open=true,powered=false: - appearance: facing=south,half=upper,hinge=left,open=true - facing=south,half=upper,hinge=right,open=true,powered=true: + facing=south,half=upper,hinge=right,open=true: appearance: facing=south,half=upper,hinge=right,open=true - facing=south,half=upper,hinge=right,open=true,powered=false: - appearance: facing=south,half=upper,hinge=right,open=true - facing=west,half=lower,hinge=left,open=true,powered=true: + facing=west,half=lower,hinge=left,open=true: appearance: facing=west,half=lower,hinge=left,open=true - facing=west,half=lower,hinge=left,open=true,powered=false: - appearance: facing=west,half=lower,hinge=left,open=true - facing=west,half=lower,hinge=right,open=true,powered=true: + facing=west,half=lower,hinge=right,open=true: appearance: facing=west,half=lower,hinge=right,open=true - facing=west,half=lower,hinge=right,open=true,powered=false: - appearance: facing=west,half=lower,hinge=right,open=true - facing=west,half=upper,hinge=left,open=true,powered=true: + facing=west,half=upper,hinge=left,open=true: appearance: facing=west,half=upper,hinge=left,open=true - facing=west,half=upper,hinge=left,open=true,powered=false: - appearance: facing=west,half=upper,hinge=left,open=true - facing=west,half=upper,hinge=right,open=true,powered=true: - appearance: facing=west,half=upper,hinge=right,open=true - facing=west,half=upper,hinge=right,open=true,powered=false: + facing=west,half=upper,hinge=right,open=true: appearance: facing=west,half=upper,hinge=right,open=true # fence gate block default:block_state/fence_gate: From 7300f0e278bb5ceaa8dd19b01dc0d2ebf61102ff Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 05:30:26 +0800 Subject: [PATCH 205/226] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E6=96=B9=E5=9D=97?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 93 +++++++++++++++++-- .../reflection/minecraft/CoreReflections.java | 20 ++++ .../src/main/resources/translations/de.yml | 1 - .../src/main/resources/translations/en.yml | 1 - .../src/main/resources/translations/es.yml | 1 - .../src/main/resources/translations/ru_ru.yml | 1 - .../src/main/resources/translations/tr.yml | 1 - .../src/main/resources/translations/zh_cn.yml | 1 - .../core/block/AbstractBlockManager.java | 10 +- .../core/pack/LoadingSequence.java | 6 +- 10 files changed, 113 insertions(+), 22 deletions(-) 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 413fc8cb3..48d1fa1b1 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 @@ -6,15 +6,9 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.KeyUtils; -import net.momirealms.craftengine.bukkit.util.RegistryUtils; -import net.momirealms.craftengine.bukkit.util.TagUtils; +import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; @@ -59,6 +53,8 @@ public final class BukkitBlockManager extends AbstractBlockManager { private final Set replacedBlockSounds = new HashSet<>(); // 用于缓存string形式的方块状态到原版方块状态 private final Map blockStateCache = new HashMap<>(1024); + // 用于临时存储可燃烧自定义方块的列表 + private final List burnableBlocks = new ArrayList<>(); public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks()); @@ -95,6 +91,11 @@ public final class BukkitBlockManager extends AbstractBlockManager { Arrays.fill(this.blockStateMappings, -1); this.previousClientBoundTags = this.clientBoundTags; this.clientBoundTags = new HashMap<>(); + for (DelegatingBlock block : this.burnableBlocks) { + this.igniteOdds.remove(block); + this.burnOdds.remove(block); + } + this.burnableBlocks.clear(); if (EmptyBlock.STATE != null) Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); for (DelegatingBlock block : this.customBlocks) { @@ -252,6 +253,82 @@ public final class BukkitBlockManager extends AbstractBlockManager { } } + @Override + protected void applyPlatformSettings(ImmutableBlockState state) { + DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject(); + nmsState.setBlockState(state); + Object nmsVisualState = state.vanillaBlockState().literalObject(); + + BlockSettings settings = state.settings(); + try { + CoreReflections.field$BlockStateBase$lightEmission.set(nmsState, settings.luminance()); + CoreReflections.field$BlockStateBase$burnable.set(nmsState, settings.burnable()); + CoreReflections.field$BlockStateBase$hardness.set(nmsState, settings.hardness()); + CoreReflections.field$BlockStateBase$replaceable.set(nmsState, settings.replaceable()); + Object mcMapColor = CoreReflections.method$MapColor$byId.invoke(null, settings.mapColor().id); + CoreReflections.field$BlockStateBase$mapColor.set(nmsState, mcMapColor); + CoreReflections.field$BlockStateBase$instrument.set(nmsState, CoreReflections.instance$NoteBlockInstrument$values[settings.instrument().ordinal()]); + CoreReflections.field$BlockStateBase$pushReaction.set(nmsState, CoreReflections.instance$PushReaction$values[settings.pushReaction().ordinal()]); + boolean canOcclude = settings.canOcclude() == Tristate.UNDEFINED ? BlockStateUtils.isOcclude(nmsVisualState) : settings.canOcclude().asBoolean(); + CoreReflections.field$BlockStateBase$canOcclude.set(nmsState, canOcclude); + boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(nmsVisualState) : settings.useShapeForLightOcclusion().asBoolean(); + CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion); + CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); + CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); + CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, settings.isViewBlocking() == Tristate.UNDEFINED ? settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE : (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE)); + + DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState); + ObjectHolder shapeHolder = nmsBlock.shapeDelegate(); + shapeHolder.bindValue(new BukkitBlockShape(nmsVisualState, Optional.ofNullable(state.settings().supportShapeBlockState()).map(it -> Objects.requireNonNull(createVanillaBlockState(it), "Illegal block state: " + it).literalObject()).orElse(null))); + ObjectHolder behaviorHolder = nmsBlock.behaviorDelegate(); + behaviorHolder.bindValue(state.behavior()); + + CoreReflections.field$BlockBehaviour$explosionResistance.set(nmsBlock, settings.resistance()); + CoreReflections.field$BlockBehaviour$friction.set(nmsBlock, settings.friction()); + CoreReflections.field$BlockBehaviour$speedFactor.set(nmsBlock, settings.speedFactor()); + CoreReflections.field$BlockBehaviour$jumpFactor.set(nmsBlock, settings.jumpFactor()); + CoreReflections.field$BlockBehaviour$soundType.set(nmsBlock, SoundUtils.toSoundType(settings.sounds())); + + CoreReflections.method$BlockStateBase$initCache.invoke(nmsState); + boolean isConditionallyFullOpaque = canOcclude & useShapeForLightOcclusion; + if (!VersionHelper.isOrAbove1_21_2()) { + CoreReflections.field$BlockStateBase$isConditionallyFullOpaque.set(nmsState, isConditionallyFullOpaque); + } + + if (VersionHelper.isOrAbove1_21_2()) { + int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$lightBlock.getInt(nmsVisualState); + CoreReflections.field$BlockStateBase$lightBlock.set(nmsState, blockLight); + boolean propagatesSkylightDown = settings.propagatesSkylightDown() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$propagatesSkylightDown.getBoolean(nmsVisualState) : settings.propagatesSkylightDown().asBoolean(); + CoreReflections.field$BlockStateBase$propagatesSkylightDown.set(nmsState, propagatesSkylightDown); + } else { + Object cache = CoreReflections.field$BlockStateBase$cache.get(nmsState); + int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$Cache$lightBlock.getInt(CoreReflections.field$BlockStateBase$cache.get(nmsVisualState)); + CoreReflections.field$BlockStateBase$Cache$lightBlock.set(cache, blockLight); + boolean propagatesSkylightDown = settings.propagatesSkylightDown() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.getBoolean(CoreReflections.field$BlockStateBase$cache.get(nmsVisualState)) : settings.propagatesSkylightDown().asBoolean(); + CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.set(cache, propagatesSkylightDown); + if (!isConditionallyFullOpaque) { + CoreReflections.field$BlockStateBase$opacityIfCached.set(nmsState, blockLight); + } + } + + CoreReflections.field$BlockStateBase$fluidState.set(nmsState, settings.fluidState() ? MFluids.WATER$defaultState : MFluids.EMPTY$defaultState); + CoreReflections.field$BlockStateBase$isRandomlyTicking.set(nmsState, settings.isRandomlyTicking()); + Object holder = BukkitCraftEngine.instance().blockManager().getMinecraftBlockHolder(state.customBlockState().registryId()); + Set tags = new HashSet<>(); + for (Key tag : settings.tags()) { + tags.add(CoreReflections.method$TagKey$create.invoke(null, MRegistries.BLOCK, KeyUtils.toResourceLocation(tag))); + } + CoreReflections.field$Holder$Reference$tags.set(holder, tags); + if (settings.burnable()) { + this.igniteOdds.put(nmsBlock, settings.burnChance()); + this.burnOdds.put(nmsBlock, settings.fireSpreadChance()); + this.burnableBlocks.add(nmsBlock); + } + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e); + } + } + private BlockSounds toBlockSounds(Object soundType) throws ReflectiveOperationException { return new BlockSounds( toSoundData(CoreReflections.field$SoundType$breakSound.get(soundType), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 0bbb93176..0d4bdd343 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -1235,6 +1235,16 @@ public final class CoreReflections { ReflectionUtils.getMethod(clazz$PushReaction, new String[] { "values" }) ); + public static final Object[] instance$PushReaction$values; + + static { + try { + instance$PushReaction$values = (Object[]) method$PushReaction$values.invoke(null); + } catch (ReflectiveOperationException e) { + throw new ReflectionInitException("Failed to init PushReaction", e); + } + } + public static final Class clazz$NoteBlockInstrument = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( "world.level.block.state.properties.BlockPropertyInstrument", @@ -1246,6 +1256,16 @@ public final class CoreReflections { ReflectionUtils.getMethod(clazz$NoteBlockInstrument, new String[] { "values" }) ); + public static final Object[] instance$NoteBlockInstrument$values; + + static { + try { + instance$NoteBlockInstrument$values = (Object[]) method$NoteBlockInstrument$values.invoke(null); + } catch (ReflectiveOperationException e) { + throw new ReflectionInitException("Failed to init NoteBlockInstrument", e); + } + } + public static final Class clazz$BlockStateBase = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( "world.level.block.state.BlockBase$BlockData", diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 1513a684b..a02740112 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -255,7 +255,6 @@ warning.config.block.state.property.invalid_format: "Problem in Datei Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'state'-Argument für 'state'." warning.config.block.state.missing_properties: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'properties'-Abschnitt für 'states'." warning.config.block.state.missing_appearances: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'appearances'-Abschnitt für 'states'." -warning.config.block.state.missing_variants: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'variants'-Abschnitt für 'states'." warning.config.block.state.variant.invalid_appearance: "Problem in Datei gefunden - Der Block '' hat einen Fehler, dass die Variante '' eine nicht existierende Appearance '' verwendet." warning.config.block.state.invalid_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen ungültigen Vanilla-Block-State ''." warning.config.block.state.unavailable_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen nicht verfügbaren Vanilla-Block-State ''. Bitte gib diesen State in der mappings.yml frei." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 652e823a8..e15c5f89d 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -267,7 +267,6 @@ warning.config.block.state.property.invalid_format: "Issue found in file warning.config.block.state.missing_state: "Issue found in file - The block '' is missing the required 'state' argument for 'state'." warning.config.block.state.missing_properties: "Issue found in file - The block '' is missing the required 'properties' section for 'states'." warning.config.block.state.missing_appearances: "Issue found in file - The block '' is missing the required 'appearances' section for 'states'." -warning.config.block.state.missing_variants: "Issue found in file - The block '' is missing the required 'variants' section for 'states'." warning.config.block.state.entity_renderer.invalid_type: "Issue found in file - The block '' is using an invalid entity renderer type ''." warning.config.block.state.entity_renderer.item_display.missing_item: "Issue found in file - The block '' is missing the required 'item' argument for 'item_display' entity renderer." warning.config.block.state.entity_renderer.text_display.missing_text: "Issue found in file - The block '' is missing the required 'text' argument for 'text_display' entity renderer." diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index d65bc899a..eaa30b979 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -178,7 +178,6 @@ warning.config.block.state.property.invalid_format: "Problema encontrado warning.config.block.state.missing_state: "Problema encontrado en el archivo - El bloque '' carece del argumento requerido 'state' para 'state'." warning.config.block.state.missing_properties: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'properties' para 'states'." warning.config.block.state.missing_appearances: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'appearances' para 'states'." -warning.config.block.state.missing_variants: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'variants' para 'states'." warning.config.block.state.variant.invalid_appearance: "Problema encontrado en el archivo - Hay un error en el bloque '' donde la variante '' está usando una apariencia inexistente ''." warning.config.block.state.invalid_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla inválido ''." warning.config.block.state.unavailable_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla no disponible ''. Por favor libera este estado en el archivo mappings.yml." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index be8170d1a..e3d30278c 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -228,7 +228,6 @@ warning.config.block.state.property.invalid_format: "Проблема н warning.config.block.state.missing_state: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'state' аргумент для 'state'." warning.config.block.state.missing_properties: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'properties' раздел для 'states'." warning.config.block.state.missing_appearances: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'appearances' раздел для 'states'." -warning.config.block.state.missing_variants: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'variants' раздел для 'states'." warning.config.block.state.variant.invalid_appearance: "Проблема найдена в файле - Блок '' имеет ошибку, что вариант '' использует несуществующий внешний вид ''." warning.config.block.state.invalid_vanilla: "Проблема найдена в файле - Блок '' имеет недействительное состояние ванильного блока ''." warning.config.block.state.unavailable_vanilla: "Проблема найдена в файле - Блок '' использует недоступное состояние ванильного блока ''. Пожалуйста, освободите это состояние в mappings.yml." diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index d283b67c1..b62967f92 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -176,7 +176,6 @@ warning.config.block.state.property.invalid_format: " dosyasında warning.config.block.state.missing_state: " dosyasında sorun bulundu - '' bloğu, 'state' için gerekli 'state' argümanı eksik." warning.config.block.state.missing_properties: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'properties' bölümü eksik." warning.config.block.state.missing_appearances: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'appearances' bölümü eksik." -warning.config.block.state.missing_variants: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'variants' bölümü eksik." warning.config.block.state.variant.invalid_appearance: " dosyasında sorun bulundu - '' bloğunda, '' varyantının var olmayan bir görünüm '' kullandığı bir hata var." warning.config.block.state.invalid_vanilla: " dosyasında sorun bulundu - '' bloğu geçersiz bir vanilya blok durumu '' kullanıyor." warning.config.block.state.unavailable_vanilla: " dosyasında sorun bulundu - '' bloğu kullanılamayan bir vanilya blok durumu '' kullanıyor. Lütfen bu durumu mappings.yml dosyasında serbest bırakın." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 04bea5c58..9d0175d00 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -263,7 +263,6 @@ warning.config.block.state.property.invalid_format: "在文件 warning.config.block.state.missing_state: "在文件 发现问题 - 方块 '' 的 'state' 缺少必需的 'state' 参数" warning.config.block.state.missing_properties: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'properties' 段落" warning.config.block.state.missing_appearances: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'appearances' 段落" -warning.config.block.state.missing_variants: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'variants' 段落" warning.config.block.state.variant.invalid_appearance: "在文件 发现问题 - 方块 '' 的变体 '' 使用了不存在的 appearance ''" warning.config.block.state.invalid_vanilla: "在文件 发现问题 - 方块 '' 使用了无效的原版方块状态 ''" warning.config.block.state.unavailable_vanilla: "在文件 发现问题 - 方块 '' 使用了不可用的原版方块状态 '' 请在 mappings.yml 中释放该状态" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index bd39b0a59..c1a8fd25a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -6,7 +6,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; @@ -14,7 +13,6 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.parser.BlockNbtParser; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.item.AbstractItemManager; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; @@ -49,7 +47,6 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.function.Predicate; public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager { protected final BlockParser blockParser; @@ -150,7 +147,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Optional.ofNullable(this.byId.get(id)); } - protected void addBlockInternal(CustomBlock customBlock) { + protected void addCustomBlock(CustomBlock customBlock) { // 绑定外观状态等 for (ImmutableBlockState state : customBlock.variantProvider().states()) { int internalId = state.customBlockState().registryId(); @@ -159,6 +156,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.immutableBlockStates[index] = state; this.blockStateMappings[internalId] = appearanceId; this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); + this.applyPlatformSettings(state); // generate mod assets if (Config.generateModAssets()) { this.modBlockStateOverrides.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(appearanceId)); @@ -167,6 +165,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.byId.put(customBlock.id(), customBlock); } + protected abstract void applyPlatformSettings(ImmutableBlockState state); + @Override public ConfigParser[] parsers() { return new ConfigParser[]{this.blockParser, this.blockStateMappingParser}; @@ -546,7 +546,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } // 添加方块 - addBlockInternal(customBlock); + addCustomBlock(customBlock); }, () -> GsonHelper.get().toJson(section))); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java index 1868cd5fe..71368dae1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java @@ -8,9 +8,9 @@ public final class LoadingSequence { public static final int GLOBAL_VAR = 10; public static final int LANG = 20; public static final int TRANSLATION = 30; - public static final int BLOCK = 40; - public static final int EQUIPMENT = 50; - public static final int ITEM = 60; + public static final int EQUIPMENT = 40; + public static final int ITEM = 50; + public static final int BLOCK = 60; public static final int FURNITURE = 70; public static final int IMAGE = 80; public static final int RECIPE = 90; From 2aa75f717810c7833f589e4da21990bade5529c2 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 18:59:14 +0800 Subject: [PATCH 206/226] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=A3=B0=E9=9F=B3par?= =?UTF-8?q?t=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BlockEventListener.java | 64 ++++++--------- .../bukkit/block/BukkitBlockManager.java | 81 ++++++++++++++++--- .../plugin/network/BukkitNetworkManager.java | 44 ++++------ .../core/block/AbstractBlockManager.java | 63 ++++++++------- .../craftengine/core/util/GsonHelper.java | 20 ++--- gradle.properties | 2 +- 6 files changed, 152 insertions(+), 122 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index e72055d3c..0a6cf5176 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -73,18 +73,14 @@ public final class BlockEventListener implements Listener { // send sound if the placed block's sounds are removed if (Config.enableSoundSystem()) { Block block = event.getBlock(); - Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData()); - if (blockState != MBlocks.AIR$defaultState) { - Object ownerBlock = BlockStateUtils.getBlockOwner(blockState); - if (this.manager.isBlockSoundRemoved(ownerBlock)) { + Object blockState = BlockStateUtils.getBlockState(block); + if (blockState != MBlocks.AIR$defaultState && BlockStateUtils.isVanillaBlock(blockState)) { + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$placeSound(soundType); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + if (this.manager.isPlaceSoundMissing(soundId)) { if (player.getInventory().getItemInMainHand().getType() != Material.DEBUG_STICK) { - try { - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); - Object placeSound = CoreReflections.field$SoundType$placeSound.get(soundType); - player.playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to get sound type", e); - } + player.playSound(block.getLocation().add(0.5, 0.5, 0.5), soundId.toString(), SoundCategory.BLOCKS, 1f, 0.8f); } return; } @@ -92,23 +88,19 @@ public final class BlockEventListener implements Listener { } // resend sound if the clicked block is interactable on client side if (serverPlayer.shouldResendSound()) { - try { - Block block = event.getBlock(); - Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData()); - Object ownerBlock = BlockStateUtils.getBlockOwner(blockState); - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); - Object placeSound = CoreReflections.field$SoundType$placeSound.get(soundType); - player.playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to get sound type", e); - } + Block block = event.getBlock(); + Object blockState = BlockStateUtils.getBlockState(block); + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$placeSound(soundType); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + player.playSound(block.getLocation().add(0.5, 0.5, 0.5), soundId.toString(), SoundCategory.BLOCKS, 1f, 0.8f); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onPlayerBreak(BlockBreakEvent event) { org.bukkit.block.Block block = event.getBlock(); - Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData()); + Object blockState = BlockStateUtils.getBlockState(block); int stateId = BlockStateUtils.blockStateToId(blockState); Player player = event.getPlayer(); Location location = block.getLocation(); @@ -194,15 +186,11 @@ public final class BlockEventListener implements Listener { } // sound system if (Config.enableSoundSystem()) { - Object ownerBlock = BlockStateUtils.getBlockOwner(blockState); - if (this.manager.isBlockSoundRemoved(ownerBlock)) { - try { - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); - Object breakSound = CoreReflections.field$SoundType$breakSound.get(soundType); - player.playSound(block.getLocation().add(0.5, 0.5, 0.5), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to get sound type", e); - } + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + if (this.manager.isBreakSoundMissing(soundId)) { + player.playSound(block.getLocation().add(0.5, 0.5, 0.5), soundId.toString(), SoundCategory.BLOCKS, 1f, 0.8f); } } } @@ -259,15 +247,11 @@ public final class BlockEventListener implements Listener { } player.playSound(location, state.settings().sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.settings().sounds().stepSound().volume().get(), state.settings().sounds().stepSound().pitch().get()); } else if (Config.enableSoundSystem()) { - Object ownerBlock = BlockStateUtils.getBlockOwner(blockState); - if (this.manager.isBlockSoundRemoved(ownerBlock)) { - try { - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); - Object stepSound = CoreReflections.field$SoundType$stepSound.get(soundType); - player.playSound(player.getLocation(), FastNMS.INSTANCE.field$SoundEvent$location(stepSound).toString(), SoundCategory.BLOCKS, 0.15f, 1f); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to get sound type", e); - } + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$stepSound(soundType); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + if (this.manager.isStepSoundMissing(soundId)) { + player.playSound(player.getLocation(), soundId.toString(), SoundCategory.BLOCKS, 0.15f, 1f); } } } 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 48d1fa1b1..01f3ab547 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 @@ -1,6 +1,8 @@ package net.momirealms.craftengine.bukkit.block; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import net.momirealms.craftengine.bukkit.block.behavior.UnsafeCompositeBlockBehavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; @@ -31,6 +33,7 @@ import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Field; import java.util.*; public final class BukkitBlockManager extends AbstractBlockManager { @@ -40,7 +43,11 @@ public final class BukkitBlockManager extends AbstractBlockManager { private static BukkitBlockManager instance; private final BukkitCraftEngine plugin; // 事件监听器 - private BlockEventListener blockEventListener; + private final BlockEventListener blockEventListener; + // 用于缓存string形式的方块状态到原版方块状态 + private final Map blockStateCache = new HashMap<>(1024); + // 用于临时存储可燃烧自定义方块的列表 + private final List burnableBlocks = new ArrayList<>(); // 可燃烧的方块 private Map igniteOdds; private Map burnOdds; @@ -50,15 +57,15 @@ public final class BukkitBlockManager extends AbstractBlockManager { // 缓存的原版方块tag包 private Object cachedUpdateTagsPacket; // 被移除声音的原版方块 - private final Set replacedBlockSounds = new HashSet<>(); - // 用于缓存string形式的方块状态到原版方块状态 - private final Map blockStateCache = new HashMap<>(1024); - // 用于临时存储可燃烧自定义方块的列表 - private final List burnableBlocks = new ArrayList<>(); + private Set missingPlaceSounds = Set.of(); + private Set missingBreakSounds = Set.of(); + private Set missingHitSounds = Set.of(); + private Set missingStepSounds = Set.of(); public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks()); this.plugin = plugin; + this.blockEventListener = new BlockEventListener(plugin, this); this.registerServerSideCustomBlocks(Config.serverSideBlocks()); EmptyBlock.initialize(); instance = this; @@ -71,7 +78,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.initVanillaBlockSettings(); this.deceiveBukkitRegistry(); this.markVanillaNoteBlocks(); - this.blockEventListener = new BlockEventListener(plugin, this); Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限 } @@ -411,8 +417,20 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.clientBoundTags.put(FastNMS.INSTANCE.method$IdMap$getId(MBuiltInRegistries.BLOCK, block).orElseThrow(() -> new IllegalStateException("Block " + id + " not found")), tags); } - public boolean isBlockSoundRemoved(Object block) { - return this.replacedBlockSounds.contains(block); + public boolean isPlaceSoundMissing(Object sound) { + return this.missingPlaceSounds.contains(sound); + } + + public boolean isBreakSoundMissing(Object sound) { + return this.missingBreakSounds.contains(sound); + } + + public boolean isHitSoundMissing(Object sound) { + return this.missingHitSounds.contains(sound); + } + + public boolean isStepSoundMissing(Object sound) { + return this.missingStepSounds.contains(sound); } private void unfreezeRegistry() { @@ -463,6 +481,51 @@ public final class BukkitBlockManager extends AbstractBlockManager { return this.vanillaBlockStateCount; } + @SuppressWarnings("DuplicatedCode") + @Override + protected void processSounds() { + Set affectedBlockSoundTypes = new HashSet<>(); + for (BlockStateWrapper vanillaBlockState : super.tempVisualBlocksInUse) { + affectedBlockSoundTypes.add(FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(vanillaBlockState.literalObject())); + } + + Set placeSounds = new HashSet<>(); + Set breakSounds = new HashSet<>(); + Set stepSounds = new HashSet<>(); + Set hitSounds = new HashSet<>(); + + for (Object soundType : affectedBlockSoundTypes) { + placeSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$placeSound(soundType))); + breakSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$breakSound(soundType))); + stepSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$stepSound(soundType))); + hitSounds.add(FastNMS.INSTANCE.field$SoundEvent$location(FastNMS.INSTANCE.field$SoundType$hitSound(soundType))); + } + + ImmutableMap.Builder soundReplacementBuilder = ImmutableMap.builder(); + for (Object soundId : placeSounds) { + Key previousId = KeyUtils.resourceLocationToKey(soundId); + soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value())); + } + for (Object soundId : breakSounds) { + Key previousId = KeyUtils.resourceLocationToKey(soundId); + soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value())); + } + for (Object soundId : stepSounds) { + Key previousId = KeyUtils.resourceLocationToKey(soundId); + soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value())); + } + for (Object soundId : hitSounds) { + Key previousId = KeyUtils.resourceLocationToKey(soundId); + soundReplacementBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value())); + } + + this.missingPlaceSounds = placeSounds; + this.missingBreakSounds = breakSounds; + this.missingHitSounds = hitSounds; + this.missingStepSounds = stepSounds; + this.soundReplacements = soundReplacementBuilder.buildKeepingLast(); + } + @Override protected CustomBlock createCustomBlock(@NotNull Holder.Reference holder, @NotNull BlockStateVariantProvider variantProvider, diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 3403b6124..733013ca6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -1037,8 +1037,10 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // not a custom block if (BlockStateUtils.isVanillaBlock(stateId)) { if (Config.enableSoundSystem()) { - Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(blockState); - if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) { + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$hitSound(soundType); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + if (BukkitBlockManager.instance().isHitSoundMissing(soundId)) { player.startMiningBlock(pos, blockState, null); return; } @@ -2293,47 +2295,31 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes int state = buf.readInt(); boolean global = buf.readBoolean(); int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; + Object blockState = BlockStateUtils.idToBlockState(state); + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); + Object rawSoundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); if (BlockStateUtils.isVanillaBlock(state)) { - Object blockState = BlockStateUtils.idToBlockState(state); - Object block = BlockStateUtils.getBlockOwner(blockState); - if (BukkitBlockManager.instance().isBlockSoundRemoved(block)) { - Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); - Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); - Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); - Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); + if (BukkitBlockManager.instance().isBreakSoundMissing(rawSoundId)) { + Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(KeyUtils.resourceLocationToKey(rawSoundId)); if (mappedSoundId != null) { - Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty()); - Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( - mappedBreakSoundHolder, + FastNMS.INSTANCE.method$Holder$direct(FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(mappedSoundId), Optional.empty())), CoreReflections.instance$SoundSource$BLOCKS, - blockPos.x() + 0.5, - blockPos.y() + 0.5, - blockPos.z() + 0.5, - 1f, - 0.8F, + blockPos.x() + 0.5, blockPos.y() + 0.5, blockPos.z() + 0.5, 1f, 0.8F, RandomUtils.generateRandomLong() ); user.sendPacket(packet, true); } } } else { - Object blockState = BlockStateUtils.idToBlockState(state); - Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); - Object breakSound = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); - Key soundId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString()); + Key soundId = KeyUtils.resourceLocationToKey(rawSoundId); Key mappedSoundId = BukkitBlockManager.instance().replaceSoundIfExist(soundId); Object finalSoundId = KeyUtils.toResourceLocation(mappedSoundId == null ? soundId : mappedSoundId); - Object mappedBreakSound = FastNMS.INSTANCE.constructor$SoundEvent(finalSoundId, Optional.empty()); - Object mappedBreakSoundHolder = FastNMS.INSTANCE.method$Holder$direct(mappedBreakSound); Object packet = FastNMS.INSTANCE.constructor$ClientboundSoundPacket( - mappedBreakSoundHolder, + FastNMS.INSTANCE.method$Holder$direct(FastNMS.INSTANCE.constructor$SoundEvent(finalSoundId, Optional.empty())), CoreReflections.instance$SoundSource$BLOCKS, - blockPos.x() + 0.5, - blockPos.y() + 0.5, - blockPos.z() + 0.5, - 1f, - 0.8F, + blockPos.x() + 0.5, blockPos.y() + 0.5, blockPos.z() + 0.5, 1f, 0.8F, RandomUtils.generateRandomLong() ); user.sendPacket(packet, true); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index c1a8fd25a..6687d6731 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -14,10 +14,7 @@ import net.momirealms.craftengine.core.block.parser.BlockNbtParser; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.pack.LoadingSequence; -import net.momirealms.craftengine.core.pack.Pack; -import net.momirealms.craftengine.core.pack.PendingConfigSection; -import net.momirealms.craftengine.core.pack.ResourceLocation; +import net.momirealms.craftengine.core.pack.*; import net.momirealms.craftengine.core.pack.cache.IdAllocator; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; @@ -65,8 +62,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final Map modBlockStateOverrides = new HashMap<>(); // 根据外观查找真实状态,用于debug指令 protected final Map> appearanceToRealState = new Int2ObjectOpenHashMap<>(); - // 声音映射表,和使用了哪些视觉方块有关 - protected final Map soundReplacements = new HashMap<>(512, 0.5f); // 用于note_block:0这样格式的自动分配 protected final Map> blockStateArranger = new HashMap<>(); // 根据registry id找note_block:x中的x值 @@ -83,6 +78,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final ImmutableBlockState[] immutableBlockStates; // 原版方块的属性缓存 protected final BlockSettings[] vanillaBlockSettings; + // 临时存储哪些视觉方块被使用了 + protected final Set tempVisualBlocksInUse = new HashSet<>(); + // 声音映射表,和使用了哪些视觉方块有关 + protected Map soundReplacements = Map.of(); protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) { super(plugin); @@ -122,7 +121,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.blockStateOverrides.clear(); this.modBlockStateOverrides.clear(); this.byId.clear(); - this.soundReplacements.clear(); this.blockStateArranger.clear(); this.reversedBlockStateArranger.clear(); this.appearanceToRealState.clear(); @@ -133,8 +131,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem @Override public void delayedLoad() { this.initSuggestions(); - this.clearCache(); this.resendTags(); + this.processSounds(); + this.clearCache(); } @Override @@ -147,24 +146,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Optional.ofNullable(this.byId.get(id)); } - protected void addCustomBlock(CustomBlock customBlock) { - // 绑定外观状态等 - for (ImmutableBlockState state : customBlock.variantProvider().states()) { - int internalId = state.customBlockState().registryId(); - int appearanceId = state.vanillaBlockState().registryId(); - int index = internalId - this.vanillaBlockStateCount; - this.immutableBlockStates[index] = state; - this.blockStateMappings[internalId] = appearanceId; - this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); - this.applyPlatformSettings(state); - // generate mod assets - if (Config.generateModAssets()) { - this.modBlockStateOverrides.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(appearanceId)); - } - } - this.byId.put(customBlock.id(), customBlock); - } - protected abstract void applyPlatformSettings(ImmutableBlockState state); @Override @@ -203,6 +184,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected void clearCache() { this.tempVanillaBlockStateModels.clear(); + this.tempVisualBlocksInUse.clear(); } protected void initSuggestions() { @@ -238,6 +220,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected abstract int vanillaBlockStateCount(); + protected abstract void processSounds(); + protected abstract CustomBlock createCustomBlock(@NotNull Holder.Reference holder, @NotNull BlockStateVariantProvider variantProvider, @NotNull Map>> events, @@ -538,15 +522,34 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem boolean isEntityBlock = entityBlockBehavior != null; // 绑定行为 - for (ImmutableBlockState blockState : states) { - blockState.setBehavior(blockBehavior); + for (ImmutableBlockState state : states) { if (isEntityBlock) { - blockState.setBlockEntityType(entityBlockBehavior.blockEntityType()); + state.setBlockEntityType(entityBlockBehavior.blockEntityType()); + } + state.setBehavior(blockBehavior); + int internalId = state.customBlockState().registryId(); + int appearanceId = state.vanillaBlockState().registryId(); + int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount; + AbstractBlockManager.this.immutableBlockStates[index] = state; + AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId; + AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); + AbstractBlockManager.this.tempVisualBlocksInUse.add(state.vanillaBlockState()); + AbstractBlockManager.this.applyPlatformSettings(state); + // generate mod assets + if (Config.generateModAssets()) { + AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels.get(appearanceId)) + .orElseGet(() -> { + // 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题 + // 未来需要靠mod重构彻底解决问题 + JsonObject json = new JsonObject(); + json.addProperty("model", "minecraft:block/air"); + return json; + })); } } // 添加方块 - addCustomBlock(customBlock); + AbstractBlockManager.this.byId.put(customBlock.id(), customBlock); }, () -> GsonHelper.get().toJson(section))); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java index 975fcfee0..beb6c42a1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java @@ -11,25 +11,19 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; -public class GsonHelper { - private final Gson gson; +public final class GsonHelper { + private static final Gson GSON; - public GsonHelper() { - this.gson = new GsonBuilder() + private GsonHelper() {} + + static { + GSON = new GsonBuilder() .disableHtmlEscaping() .create(); } - public Gson getGson() { - return gson; - } - public static Gson get() { - return SingletonHolder.INSTANCE.getGson(); - } - - private static class SingletonHolder { - private static final GsonHelper INSTANCE = new GsonHelper(); + return GSON; } public static void writeJsonFile(JsonElement json, Path path) throws IOException { diff --git a/gradle.properties b/gradle.properties index e56947eb0..c3f2fe021 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.96 +nms_helper_version=1.0.97 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1 From 66bbe3f6e116604e734aa59a820f5552bb065371 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 20:24:36 +0800 Subject: [PATCH 207/226] =?UTF-8?q?=E5=A3=B0=E9=9F=B3=E9=87=8D=E6=9E=84par?= =?UTF-8?q?t=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 35 ++++--- .../block/behavior/ButtonBlockBehavior.java | 32 +++---- .../behavior/DoubleHighBlockItemBehavior.java | 3 - .../item/listener/ItemEventListener.java | 26 +++++- .../plugin/user/BukkitServerPlayer.java | 5 +- .../configuration/blocks/palm_tree.yml | 2 +- .../core/block/AbstractBlockManager.java | 15 ++- .../craftengine/core/block/BlockKeys.java | 31 +++++++ .../craftengine/core/block/CustomBlock.java | 2 - .../craftengine/core/item/ItemKeys.java | 9 +- .../craftengine/core/sound/SoundSet.java | 92 +++++++++++++++++++ .../craftengine/core/sound/Sounds.java | 35 ++----- 12 files changed, 210 insertions(+), 77 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/sound/SoundSet.java 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 01f3ab547..acc7ee28e 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 @@ -2,7 +2,6 @@ package net.momirealms.craftengine.bukkit.block; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import net.momirealms.craftengine.bukkit.block.behavior.UnsafeCompositeBlockBehavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; @@ -25,6 +24,7 @@ import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.sound.SoundSet; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; import org.bukkit.Bukkit; @@ -33,7 +33,6 @@ import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Field; import java.util.*; public final class BukkitBlockManager extends AbstractBlockManager { @@ -61,6 +60,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { private Set missingBreakSounds = Set.of(); private Set missingHitSounds = Set.of(); private Set missingStepSounds = Set.of(); + private Set missingInteractSoundBlocks = Set.of(); public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks()); @@ -433,6 +433,10 @@ public final class BukkitBlockManager extends AbstractBlockManager { return this.missingStepSounds.contains(sound); } + public boolean isInteractSoundMissing(Key blockType) { + return this.missingInteractSoundBlocks.contains(blockType); + } + private void unfreezeRegistry() { try { CoreReflections.field$MappedRegistry$frozen.set(MBuiltInRegistries.BLOCK, false); @@ -485,7 +489,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override protected void processSounds() { Set affectedBlockSoundTypes = new HashSet<>(); - for (BlockStateWrapper vanillaBlockState : super.tempVisualBlocksInUse) { + for (BlockStateWrapper vanillaBlockState : super.tempVisualBlockStatesInUse) { affectedBlockSoundTypes.add(FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(vanillaBlockState.literalObject())); } @@ -523,6 +527,23 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.missingBreakSounds = breakSounds; this.missingHitSounds = hitSounds; this.missingStepSounds = stepSounds; + + Set missingInteractSoundBlocks = new HashSet<>(); + + for (SoundSet soundSet : SoundSet.getAllSoundSets()) { + for (Key block : soundSet.blocks()) { + if (super.tempVisualBlocksInUse.contains(block)) { + Key openSound = soundSet.openSound(); + soundReplacementBuilder.put(openSound, Key.of(openSound.namespace(), "replaced." + openSound.value())); + Key closeSound = soundSet.closeSound(); + soundReplacementBuilder.put(closeSound, Key.of(closeSound.namespace(), "replaced." + closeSound.value())); + missingInteractSoundBlocks.addAll(soundSet.blocks()); + break; + } + } + } + + this.missingInteractSoundBlocks = missingInteractSoundBlocks; this.soundReplacements = soundReplacementBuilder.buildKeepingLast(); } @@ -533,12 +554,4 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Nullable LootTable lootTable) { return new BukkitCustomBlock(holder, variantProvider, events, lootTable); } - - public boolean isOpenableBlockSoundRemoved(Object blockOwner) { - return false; - } - - public SoundData getRemovedOpenableBlockSound(Object blockOwner, boolean b) { - return null; - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java index afa85f737..956830c86 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java @@ -134,26 +134,26 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { } private void checkPressed(Object thisBlock, Object state, Object level, Object pos) { - Object abstractArrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( + Object arrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move( FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape( state, level, pos, CoreReflections.instance$CollisionContext$empty )), pos), MEntitySelectors.NO_SPECTATORS).stream().findFirst().orElse(null) : null; - boolean flag = abstractArrow != null; + boolean on = arrow != null; ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); if (blockState == null) return; boolean poweredValue = blockState.get(this.poweredProperty); - if (flag != poweredValue) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, flag).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + if (on != poweredValue) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, on).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); updateNeighbours(thisBlock, blockState, level, pos); - playSound(null, level, pos, flag); + playSound(level, pos, on); Object gameEvent = VersionHelper.isOrAbove1_20_5() - ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE) - : flag ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE; - FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, abstractArrow, gameEvent, pos); + ? FastNMS.INSTANCE.method$Holder$direct(on ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE) + : on ? MGameEvents.BLOCK_ACTIVATE : MGameEvents.BLOCK_DEACTIVATE; + FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, arrow, gameEvent, pos); } - if (flag) { + if (on) { FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); } } @@ -177,21 +177,21 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, FastNMS.INSTANCE.method$BlockPos$relative(pos, nmsDirection), thisBlock, orientation); } - private void playSound(@Nullable Object player, Object level, Object pos, boolean hitByArrow) { - SoundData soundData = getSound(hitByArrow); + private void playSound(Object level, Object pos, boolean on) { + SoundData soundData = getSound(on); if (soundData == null) return; Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(soundData.id()), Optional.empty()); - FastNMS.INSTANCE.method$LevelAccessor$playSound(level, player, pos, sound, CoreReflections.instance$SoundSource$BLOCKS, soundData.volume().get(), soundData.pitch().get()); + FastNMS.INSTANCE.method$LevelAccessor$playSound(level, null, pos, sound, CoreReflections.instance$SoundSource$BLOCKS, soundData.volume().get(), soundData.pitch().get()); } - private SoundData getSound(boolean isOn) { - return isOn ? this.buttonClickOnSound : this.buttonClickOffSound; + private SoundData getSound(boolean on) { + return on ? this.buttonClickOnSound : this.buttonClickOffSound; } private void press(Object thisBlock, ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); - playSound(player, level, pos, true); + playSound(level, pos, true); Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvents.BLOCK_ACTIVATE) : MGameEvents.BLOCK_ACTIVATE; FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos); } @@ -203,7 +203,7 @@ public class ButtonBlockBehavior extends BukkitBlockBehavior { public BlockBehavior create(CustomBlock block, Map arguments) { BooleanProperty powered = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.button.missing_powered"); int ticksToStayPressed = ResourceConfigUtils.getAsInt(arguments.getOrDefault("ticks-to-stay-pressed", 30), "ticks-to-stay-pressed"); - boolean canButtonBeActivatedByArrows = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-button-be-activated-by-arrows", true), "can-button-be-activated-by-arrows"); + boolean canButtonBeActivatedByArrows = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-activated-by-arrows", true), "can-be-activated-by-arrows"); Map sounds = (Map) arguments.get("sounds"); SoundData buttonClickOnSound = null; SoundData buttonClickOffSound = null; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java index 7ad5a1bed..d33b45da1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/DoubleHighBlockItemBehavior.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.item.behavior; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; @@ -9,10 +8,8 @@ import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; import net.momirealms.craftengine.core.pack.Pack; -import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import org.bukkit.Location; import org.bukkit.block.BlockState; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index ce1500a38..2dc58f76d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -29,7 +29,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.sound.SoundSet; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.BlockHitResult; @@ -41,6 +41,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Openable; +import org.bukkit.block.data.Powerable; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -219,14 +220,29 @@ public class ItemEventListener implements Listener { } } else { if (Config.enableSoundSystem() && hitResult != null) { - Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(blockState); - if (this.plugin.blockManager().isOpenableBlockSoundRemoved(blockOwner)) { + Key blockOwner = BlockStateUtils.getBlockOwnerIdFromState(blockState); + if (this.plugin.blockManager().isInteractSoundMissing(blockOwner)) { boolean hasItem = player.getInventory().getItemInMainHand().getType() != Material.AIR || player.getInventory().getItemInOffHand().getType() != Material.AIR; boolean flag = player.isSneaking() && hasItem; if (!flag) { if (blockData instanceof Openable openable) { - SoundData soundData = this.plugin.blockManager().getRemovedOpenableBlockSound(blockOwner, !openable.isOpen()); - serverPlayer.playSound(soundData.id(), SoundSource.BLOCK, soundData.volume().get(), soundData.pitch().get()); + SoundSet soundSet = SoundSet.getByBlock(blockOwner); + if (soundSet != null) { + serverPlayer.playSound( + Vec3d.atCenterOf(hitResult.getBlockPos()), + openable.isOpen() ? soundSet.closeSound() : soundSet.openSound(), + SoundSource.BLOCK, + 1, RandomUtils.generateRandomFloat(0.9f, 1)); + } + } else if (blockData instanceof Powerable powerable && !powerable.isPowered()) { + SoundSet soundSet = SoundSet.getByBlock(blockOwner); + if (soundSet != null) { + serverPlayer.playSound( + Vec3d.atCenterOf(hitResult.getBlockPos()), + soundSet.openSound(), + SoundSource.BLOCK, + 1, RandomUtils.generateRandomFloat(0.9f, 1)); + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 2c4f7468f..36ed88089 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -717,9 +717,8 @@ public class BukkitServerPlayer extends Player { // send hit sound if the sound is removed if (currentTick - this.lastHitBlockTime > 3) { - Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(destroyedState); - Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(blockOwner); - Object soundEvent = CoreReflections.field$SoundType$hitSound.get(soundType); + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(destroyedState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$hitSound(soundType); Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); player.playSound(location, soundId.toString(), SoundCategory.BLOCKS, 0.5F, 0.5F); this.lastHitBlockTime = currentTick; diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 65268063a..61ccd87bc 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -689,7 +689,7 @@ blocks#button: - type: face_attached_horizontal_directional_block - type: button_block ticks-to-stay-pressed: 30 - can-button-be-activated-by-arrows: true + can-be-activated-by-arrows: true sounds: on: minecraft:block.wooden_button.click_on off: minecraft:block.wooden_button.click_off diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 6687d6731..94740912f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -14,7 +14,10 @@ import net.momirealms.craftengine.core.block.parser.BlockNbtParser; import net.momirealms.craftengine.core.block.properties.Properties; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.pack.*; +import net.momirealms.craftengine.core.pack.LoadingSequence; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.PendingConfigSection; +import net.momirealms.craftengine.core.pack.ResourceLocation; import net.momirealms.craftengine.core.pack.cache.IdAllocator; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; @@ -79,7 +82,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 原版方块的属性缓存 protected final BlockSettings[] vanillaBlockSettings; // 临时存储哪些视觉方块被使用了 - protected final Set tempVisualBlocksInUse = new HashSet<>(); + protected final Set tempVisualBlockStatesInUse = new HashSet<>(); + protected final Set tempVisualBlocksInUse = new HashSet<>(); // 声音映射表,和使用了哪些视觉方块有关 protected Map soundReplacements = Map.of(); @@ -184,6 +188,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected void clearCache() { this.tempVanillaBlockStateModels.clear(); + this.tempVisualBlockStatesInUse.clear(); this.tempVisualBlocksInUse.clear(); } @@ -528,12 +533,14 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } state.setBehavior(blockBehavior); int internalId = state.customBlockState().registryId(); - int appearanceId = state.vanillaBlockState().registryId(); + BlockStateWrapper visualState = state.vanillaBlockState(); + int appearanceId = visualState.registryId(); int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount; AbstractBlockManager.this.immutableBlockStates[index] = state; AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId; AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); - AbstractBlockManager.this.tempVisualBlocksInUse.add(state.vanillaBlockState()); + AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState); + AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState)); AbstractBlockManager.this.applyPlatformSettings(state); // generate mod assets if (Config.generateModAssets()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java index c6b9cd591..ce9cf8079 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java @@ -2,6 +2,8 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.util.Key; +import java.util.List; + public final class BlockKeys { private BlockKeys() {} @@ -243,4 +245,33 @@ public final class BlockKeys { public static final Key BAMBOO_WALL_HANGING_SIGN = Key.of("minecraft:bamboo_wall_hanging_sign"); public static final Key CACTUS = Key.of("minecraft:cactus"); + + public static final List WOODEN_TRAPDOORS = List.of(OAK_TRAPDOOR, SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR, + ACACIA_TRAPDOOR, PALE_OAK_TRAPDOOR, DARK_OAK_TRAPDOOR, MANGROVE_TRAPDOOR, JUNGLE_TRAPDOOR); + public static final List CHERRY_TRAPDOORS = List.of(CHERRY_TRAPDOOR); + public static final List BAMBOO_TRAPDOORS = List.of(BAMBOO_TRAPDOOR); + public static final List NETHER_TRAPDOORS = List.of(WARPED_TRAPDOOR, CRIMSON_TRAPDOOR); + public static final List COPPER_TRAPDOORS = List.of(COPPER_TRAPDOOR, EXPOSED_COPPER_TRAPDOOR, WEATHERED_COPPER_TRAPDOOR, OXIDIZED_COPPER_DOOR, + WAXED_COPPER_TRAPDOOR, WAXED_EXPOSED_COPPER_TRAPDOOR, WAXED_WEATHERED_COPPER_TRAPDOOR, WAXED_OXIDIZED_COPPER_TRAPDOOR); + + public static final List WOODEN_DOORS = List.of(OAK_DOOR, SPRUCE_DOOR, BIRCH_DOOR, + ACACIA_DOOR, PALE_OAK_DOOR, DARK_OAK_DOOR, MANGROVE_DOOR, JUNGLE_DOOR); + public static final List CHERRY_DOORS = List.of(CHERRY_DOOR); + public static final List BAMBOO_DOORS = List.of(BAMBOO_DOOR); + public static final List NETHER_DOORS = List.of(WARPED_DOOR, CRIMSON_DOOR); + public static final List COPPER_DOORS = List.of(COPPER_DOOR, EXPOSED_COPPER_DOOR, WEATHERED_COPPER_DOOR, OXIDIZED_COPPER_DOOR, + WAXED_COPPER_DOOR, WAXED_EXPOSED_COPPER_DOOR, WAXED_WEATHERED_COPPER_DOOR, WAXED_OXIDIZED_COPPER_DOOR); + + public static final List WOODEN_FENCE_GATES = List.of(OAK_FENCE_GATE, SPRUCE_FENCE_GATE, BIRCH_FENCE_GATE, + ACACIA_FENCE_GATE, PALE_OAK_FENCE_GATE, DARK_OAK_FENCE_GATE, MANGROVE_FENCE_GATE, JUNGLE_FENCE_GATE); + public static final List CHERRY_FENCE_GATES = List.of(CHERRY_FENCE_GATE); + public static final List BAMBOO_FENCE_GATES = List.of(BAMBOO_FENCE_GATE); + public static final List NETHER_FENCE_GATES = List.of(WARPED_FENCE_GATE, CRIMSON_FENCE_GATE); + + public static final List WOODEN_BUTTONS = List.of(OAK_BUTTON, SPRUCE_BUTTON, BIRCH_BUTTON, JUNGLE_BUTTON, + ACACIA_BUTTON, DARK_OAK_BUTTON, PALE_OAK_BUTTON, MANGROVE_BUTTON); + public static final List CHERRY_BUTTONS = List.of(CHERRY_BUTTON); + public static final List BAMBOO_BUTTONS = List.of(BAMBOO_BUTTON); + public static final List NETHER_BUTTONS = List.of(CRIMSON_BUTTON, WARPED_BUTTON); + public static final List STONE_BUTTONS = List.of(STONE_BUTTON, POLISHED_BLACKSTONE_BUTTON); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java index 8f71a3707..83932728d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java @@ -5,7 +5,6 @@ import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; -import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.util.Key; import net.momirealms.sparrow.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; @@ -13,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; -import java.util.Map; public interface CustomBlock { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java index cf0c172e5..33a9ac8c5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java @@ -62,11 +62,6 @@ public final class ItemKeys { public static final Key BLACK_DYE = Key.of("minecraft:black_dye"); public static final Key FIREWORK_STAR = Key.of("minecraft:firework_star"); - public static final Key[] AXES = new Key[] { - WOODEN_AXE, STONE_AXE, IRON_AXE, GOLDEN_AXE, DIAMOND_AXE, NETHERITE_AXE - }; - - public static final Key[] WATER_BUCKETS = new Key[] { - WATER_BUCKET, COD_BUCKET, SALMON_BUCKET, TROPICAL_FISH_BUCKET, TADPOLE_BUCKET, PUFFERFISH_BUCKET, AXOLOTL_BUCKET - }; + public static final Key[] AXES = new Key[] {WOODEN_AXE, STONE_AXE, IRON_AXE, GOLDEN_AXE, DIAMOND_AXE, NETHERITE_AXE}; + public static final Key[] WATER_BUCKETS = new Key[] {WATER_BUCKET, COD_BUCKET, SALMON_BUCKET, TROPICAL_FISH_BUCKET, TADPOLE_BUCKET, PUFFERFISH_BUCKET, AXOLOTL_BUCKET}; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSet.java b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSet.java new file mode 100644 index 000000000..409f9cd7b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSet.java @@ -0,0 +1,92 @@ +package net.momirealms.craftengine.core.sound; + +import net.momirealms.craftengine.core.block.BlockKeys; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public record SoundSet(List blocks, Key openSound, Key closeSound) { + // Trapdoor sound sets + public static final SoundSet WOODEN_TRAPDOOR = new SoundSet(BlockKeys.WOODEN_TRAPDOORS, Sounds.WOODEN_TRAPDOOR_OPEN, Sounds.WOODEN_TRAPDOOR_CLOSE); + public static final SoundSet NETHER_WOOD_TRAPDOOR = new SoundSet(BlockKeys.NETHER_TRAPDOORS, Sounds.NETHER_WOOD_TRAPDOOR_OPEN, Sounds.NETHER_WOOD_TRAPDOOR_CLOSE); + public static final SoundSet BAMBOO_WOOD_TRAPDOOR = new SoundSet(BlockKeys.BAMBOO_TRAPDOORS, Sounds.BAMBOO_WOOD_TRAPDOOR_OPEN, Sounds.BAMBOO_WOOD_TRAPDOOR_CLOSE); + public static final SoundSet CHERRY_WOOD_TRAPDOOR = new SoundSet(BlockKeys.CHERRY_TRAPDOORS, Sounds.CHERRY_WOOD_TRAPDOOR_OPEN, Sounds.CHERRY_WOOD_TRAPDOOR_CLOSE); + public static final SoundSet COPPER_TRAPDOOR = new SoundSet(BlockKeys.COPPER_TRAPDOORS, Sounds.COPPER_TRAPDOOR_OPEN, Sounds.COPPER_TRAPDOOR_CLOSE); + + // Door sound sets + public static final SoundSet WOODEN_DOOR = new SoundSet(BlockKeys.WOODEN_DOORS, Sounds.WOODEN_DOOR_OPEN, Sounds.WOODEN_DOOR_CLOSE); + public static final SoundSet NETHER_WOOD_DOOR = new SoundSet(BlockKeys.NETHER_DOORS, Sounds.NETHER_WOOD_DOOR_OPEN, Sounds.NETHER_WOOD_DOOR_CLOSE); + public static final SoundSet BAMBOO_WOOD_DOOR = new SoundSet(BlockKeys.BAMBOO_DOORS, Sounds.BAMBOO_WOOD_DOOR_OPEN, Sounds.BAMBOO_WOOD_DOOR_CLOSE); + public static final SoundSet CHERRY_WOOD_DOOR = new SoundSet(BlockKeys.CHERRY_DOORS, Sounds.CHERRY_WOOD_DOOR_OPEN, Sounds.CHERRY_WOOD_DOOR_CLOSE); + public static final SoundSet COPPER_DOOR = new SoundSet(BlockKeys.COPPER_DOORS, Sounds.COPPER_DOOR_OPEN, Sounds.COPPER_DOOR_CLOSE); + + // Button sound sets + public static final SoundSet WOODEN_BUTTON = new SoundSet(BlockKeys.WOODEN_BUTTONS, Sounds.WOODEN_BUTTON_CLICK_ON, Sounds.WOODEN_BUTTON_CLICK_OFF); + public static final SoundSet NETHER_WOOD_BUTTON = new SoundSet(BlockKeys.NETHER_BUTTONS, Sounds.NETHER_WOOD_BUTTON_CLICK_ON, Sounds.NETHER_WOOD_BUTTON_CLICK_OFF); + public static final SoundSet BAMBOO_WOOD_BUTTON = new SoundSet(BlockKeys.BAMBOO_BUTTONS, Sounds.BAMBOO_WOOD_BUTTON_CLICK_ON, Sounds.BAMBOO_WOOD_BUTTON_CLICK_OFF); + public static final SoundSet CHERRY_WOOD_BUTTON = new SoundSet(BlockKeys.CHERRY_BUTTONS, Sounds.CHERRY_WOOD_BUTTON_CLICK_ON, Sounds.CHERRY_WOOD_BUTTON_CLICK_OFF); + public static final SoundSet STONE_BUTTON = new SoundSet(BlockKeys.STONE_BUTTONS, Sounds.STONE_BUTTON_CLICK_ON, Sounds.STONE_BUTTON_CLICK_OFF); + + // Fence gate sound sets + public static final SoundSet WOODEN_FENCE_GATE = new SoundSet(BlockKeys.WOODEN_FENCE_GATES, Sounds.WOODEN_FENCE_GATE_OPEN, Sounds.WOODEN_FENCE_GATE_CLOSE); + public static final SoundSet NETHER_WOOD_FENCE_GATE = new SoundSet(BlockKeys.NETHER_FENCE_GATES, Sounds.NETHER_WOOD_FENCE_GATE_OPEN, Sounds.NETHER_WOOD_FENCE_GATE_CLOSE); + public static final SoundSet BAMBOO_WOOD_FENCE_GATE = new SoundSet(BlockKeys.BAMBOO_FENCE_GATES, Sounds.BAMBOO_WOOD_FENCE_GATE_OPEN, Sounds.BAMBOO_WOOD_FENCE_GATE_CLOSE); + public static final SoundSet CHERRY_WOOD_FENCE_GATE = new SoundSet(BlockKeys.CHERRY_FENCE_GATES, Sounds.CHERRY_WOOD_FENCE_GATE_OPEN, Sounds.CHERRY_WOOD_FENCE_GATE_CLOSE); + + // 获取所有声音集合的便捷方法 + public static List getAllSoundSets() { + return List.of( + // Trapdoors + WOODEN_TRAPDOOR, NETHER_WOOD_TRAPDOOR, BAMBOO_WOOD_TRAPDOOR, + CHERRY_WOOD_TRAPDOOR, COPPER_TRAPDOOR, + // Doors + WOODEN_DOOR, NETHER_WOOD_DOOR, BAMBOO_WOOD_DOOR, + CHERRY_WOOD_DOOR, COPPER_DOOR, + // Fence gates + WOODEN_FENCE_GATE, NETHER_WOOD_FENCE_GATE, + BAMBOO_WOOD_FENCE_GATE, CHERRY_WOOD_FENCE_GATE, + // Buttons + WOODEN_BUTTON, NETHER_WOOD_BUTTON, BAMBOO_WOOD_BUTTON, + CHERRY_WOOD_BUTTON, STONE_BUTTON + ); + } + + // 按类型获取声音集合的方法 + public static List getTrapdoorSoundSets() { + return List.of(WOODEN_TRAPDOOR, NETHER_WOOD_TRAPDOOR, BAMBOO_WOOD_TRAPDOOR, + CHERRY_WOOD_TRAPDOOR, COPPER_TRAPDOOR); + } + + public static List getDoorSoundSets() { + return List.of(WOODEN_DOOR, NETHER_WOOD_DOOR, BAMBOO_WOOD_DOOR, + CHERRY_WOOD_DOOR, COPPER_DOOR); + } + + public static List getFenceGateSoundSets() { + return List.of(WOODEN_FENCE_GATE, NETHER_WOOD_FENCE_GATE, + BAMBOO_WOOD_FENCE_GATE, CHERRY_WOOD_FENCE_GATE); + } + + public static List getButtonSoundSets() { + return List.of(WOODEN_BUTTON, NETHER_WOOD_BUTTON, BAMBOO_WOOD_BUTTON, + CHERRY_WOOD_BUTTON, STONE_BUTTON); + } + + private static final Map SOUND_SETS = new HashMap<>(); + + static { + for (SoundSet set : getAllSoundSets()) { + set.blocks.forEach(block -> { + SOUND_SETS.put(block, set); + }); + } + } + + @Nullable + public static SoundSet getByBlock(Key blockType) { + return SOUND_SETS.get(blockType); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/Sounds.java b/core/src/main/java/net/momirealms/craftengine/core/sound/Sounds.java index a5e6000df..252754c00 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/Sounds.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/Sounds.java @@ -1,35 +1,10 @@ package net.momirealms.craftengine.core.sound; -import net.momirealms.craftengine.core.block.BlockKeys; import net.momirealms.craftengine.core.util.Key; -import java.util.List; - public final class Sounds { private Sounds() {} - public static final List WOODEN_TRAPDOORS = List.of(BlockKeys.OAK_TRAPDOOR, BlockKeys.SPRUCE_TRAPDOOR, BlockKeys.BIRCH_TRAPDOOR, - BlockKeys.ACACIA_TRAPDOOR, BlockKeys.PALE_OAK_TRAPDOOR, BlockKeys.DARK_OAK_TRAPDOOR, BlockKeys.MANGROVE_TRAPDOOR, BlockKeys.JUNGLE_TRAPDOOR); - public static final List CHERRY_TRAPDOORS = List.of(BlockKeys.CHERRY_TRAPDOOR); - public static final List BAMBOO_TRAPDOORS = List.of(BlockKeys.BAMBOO_TRAPDOOR); - public static final List NETHER_TRAPDOORS = List.of(BlockKeys.WARPED_TRAPDOOR, BlockKeys.CRIMSON_TRAPDOOR); - public static final List COPPER_TRAPDOORS = List.of(BlockKeys.COPPER_TRAPDOOR, BlockKeys.EXPOSED_COPPER_TRAPDOOR, BlockKeys.WEATHERED_COPPER_TRAPDOOR, BlockKeys.OXIDIZED_COPPER_DOOR, - BlockKeys.WAXED_COPPER_TRAPDOOR, BlockKeys.WAXED_EXPOSED_COPPER_TRAPDOOR, BlockKeys.WAXED_WEATHERED_COPPER_TRAPDOOR, BlockKeys.WAXED_OXIDIZED_COPPER_TRAPDOOR); - - public static final List WOODEN_DOORS = List.of(BlockKeys.OAK_DOOR, BlockKeys.SPRUCE_DOOR, BlockKeys.BIRCH_DOOR, - BlockKeys.ACACIA_DOOR, BlockKeys.PALE_OAK_DOOR, BlockKeys.DARK_OAK_DOOR, BlockKeys.MANGROVE_DOOR, BlockKeys.JUNGLE_DOOR); - public static final List CHERRY_DOORS = List.of(BlockKeys.CHERRY_DOOR); - public static final List BAMBOO_DOORS = List.of(BlockKeys.BAMBOO_DOOR); - public static final List NETHER_DOORS = List.of(BlockKeys.WARPED_DOOR, BlockKeys.CRIMSON_DOOR); - public static final List COPPER_DOORS = List.of(BlockKeys.COPPER_DOOR, BlockKeys.EXPOSED_COPPER_DOOR, BlockKeys.WEATHERED_COPPER_DOOR, BlockKeys.OXIDIZED_COPPER_DOOR, - BlockKeys.WAXED_COPPER_DOOR, BlockKeys.WAXED_EXPOSED_COPPER_DOOR, BlockKeys.WAXED_WEATHERED_COPPER_DOOR, BlockKeys.WAXED_OXIDIZED_COPPER_DOOR); - - public static final List WOODEN_FENCE_GATES = List.of(BlockKeys.OAK_FENCE_GATE, BlockKeys.SPRUCE_FENCE_GATE, BlockKeys.BIRCH_FENCE_GATE, - BlockKeys.ACACIA_FENCE_GATE, BlockKeys.PALE_OAK_FENCE_GATE, BlockKeys.DARK_OAK_FENCE_GATE, BlockKeys.MANGROVE_FENCE_GATE, BlockKeys.JUNGLE_FENCE_GATE); - public static final List CHERRY_FENCE_GATES = List.of(BlockKeys.CHERRY_FENCE_GATE); - public static final List BAMBOO_FENCE_GATES = List.of(BlockKeys.BAMBOO_FENCE_GATE); - public static final List NETHER_FENCE_GATES = List.of(BlockKeys.WARPED_FENCE_GATE, BlockKeys.CRIMSON_FENCE_GATE); - public static final Key WOODEN_TRAPDOOR_OPEN = Key.of("block.wooden_trapdoor.open"); public static final Key WOODEN_TRAPDOOR_CLOSE = Key.of("block.wooden_trapdoor.close"); public static final Key WOODEN_DOOR_OPEN = Key.of("block.wooden_door.open"); @@ -58,4 +33,14 @@ public final class Sounds { public static final Key COPPER_TRAPDOOR_CLOSE = Key.of("block.copper_trapdoor.close"); public static final Key COPPER_DOOR_OPEN = Key.of("block.copper_door.open"); public static final Key COPPER_DOOR_CLOSE = Key.of("block.copper_door.close"); + public static final Key STONE_BUTTON_CLICK_OFF = Key.of("block.stone_button.click_off"); + public static final Key STONE_BUTTON_CLICK_ON = Key.of("block.stone_button.click_on"); + public static final Key CHERRY_WOOD_BUTTON_CLICK_OFF = Key.of("block.cherry_wood_button.click_off"); + public static final Key CHERRY_WOOD_BUTTON_CLICK_ON = Key.of("block.cherry_wood_button.click_on"); + public static final Key NETHER_WOOD_BUTTON_CLICK_OFF = Key.of("block.nether_wood_button.click_off"); + public static final Key NETHER_WOOD_BUTTON_CLICK_ON = Key.of("block.nether_wood_button.click_on"); + public static final Key BAMBOO_WOOD_BUTTON_CLICK_OFF = Key.of("block.bamboo_wood_button.click_off"); + public static final Key BAMBOO_WOOD_BUTTON_CLICK_ON = Key.of("block.bamboo_wood_button.click_on"); + public static final Key WOODEN_BUTTON_CLICK_OFF = Key.of("block.wooden_button.click_off"); + public static final Key WOODEN_BUTTON_CLICK_ON = Key.of("block.wooden_button.click_on"); } From 1da35c6bb5b8f8414f47562fb09272cb378637f3 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 21:19:44 +0800 Subject: [PATCH 208/226] =?UTF-8?q?=E6=B7=BB=E5=8A=A0atlas=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/config.yml | 10 ++- .../core/pack/AbstractPackManager.java | 77 +++++++++++++++---- .../core/plugin/config/Config.java | 14 +++- gradle.properties | 2 +- 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 4f14758e1..3203ee842 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -23,6 +23,7 @@ resource-pack: method-1: false method-2: false method-3: false # Enable this would increase the resource pack size by 0.67MB + # [Premium Exclusive] # Obfuscate your resource pack obfuscation: enable: false @@ -50,9 +51,13 @@ resource-pack: - "@vanilla_models" bypass-sounds: [] bypass-equipments: [] - # Validate if there are any errors in the resource pack, such as missing textures or models - validate: + # Validate if there is any error in the resource pack, such as missing textures or models + # If your resource pack is compliant with the standard, you can disable validation to improve the resource pack generation speed. + validation: enable: true + # [Premium Exclusive] + # Fix images that are not within the texture atlas. + fix-atlas: true # Define the name of the overlay folders overlay-format: "ce_overlay_{version}" # Allowed values: @@ -138,6 +143,7 @@ resource-pack: type: merge_atlas item: + # [Premium Exclusive] # Make custom-model-data and item-model clientside by default client-bound-model: true # Add a tag on custom name and lore diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 268e9bdc9..f74448582 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -866,9 +866,6 @@ public abstract class AbstractPackManager implements PackManager { } } } - case "filter", "minecraft:filter" -> { - // todo filter - } case "paletted_permutations", "minecraft:paletted_permutations" -> { JsonArray textures = sourceJson.getAsJsonArray("textures"); if (textures == null) continue; @@ -883,6 +880,9 @@ public abstract class AbstractPackManager implements PackManager { } } } + case "filter", "minecraft:filter" -> { + // todo filter + } } } } @@ -908,28 +908,32 @@ public abstract class AbstractPackManager implements PackManager { Set existingTextures = new HashSet<>(VANILLA_TEXTURES); Map directoryMapper = new HashMap<>(); processAtlas(this.vanillaAtlas, directoryMapper::put, existingTextures::add, texturesInAtlas::add); + Map allAtlas = new HashMap<>(); for (Path rootPath : rootPaths) { Path assetsPath = rootPath.resolve("assets"); if (!Files.isDirectory(assetsPath)) continue; + + Path atlasesFile = assetsPath.resolve("minecraft").resolve("atlases").resolve("blocks.json"); + if (Files.exists(atlasesFile)) { + try { + JsonObject atlasJsonObject = GsonHelper.readJsonFile(atlasesFile).getAsJsonObject(); + processAtlas(atlasJsonObject, directoryMapper::put, existingTextures::add, texturesInAtlas::add); + allAtlas.put(atlasesFile, atlasJsonObject); + } catch (IOException | JsonParseException e) { + TranslationManager.instance().log("warning.config.resource_pack.generation.malformatted_json", atlasesFile.toAbsolutePath().toString()); + } + } + List namespaces; try { namespaces = FileUtils.collectNamespaces(assetsPath); } catch (IOException e) { - plugin.logger().warn("Failed to collect namespaces for " + assetsPath.toAbsolutePath(), e); + this.plugin.logger().warn("Failed to collect namespaces for " + assetsPath.toAbsolutePath(), e); return; } - for (Path namespacePath : namespaces) { - Path atlasesFile = namespacePath.resolve("atlases").resolve("blocks.json"); - if (Files.exists(atlasesFile)) { - try { - JsonObject atlasJsonObject = GsonHelper.readJsonFile(atlasesFile).getAsJsonObject(); - processAtlas(atlasJsonObject, directoryMapper::put, existingTextures::add, texturesInAtlas::add); - } catch (IOException | JsonParseException e) { - TranslationManager.instance().log("warning.config.resource_pack.generation.malformatted_json", atlasesFile.toAbsolutePath().toString()); - } - } + for (Path namespacePath : namespaces) { Path fontPath = namespacePath.resolve("font"); if (Files.isDirectory(fontPath)) { try { @@ -1079,6 +1083,8 @@ public abstract class AbstractPackManager implements PackManager { TranslationManager.instance().log("warning.config.resource_pack.generation.missing_block_model", entry.getValue().stream().distinct().toList().toString(), modelPath); } + Set texturesToFix = new HashSet<>(); + // 验证贴图是否存在 boolean enableObf = Config.enableObfuscation() && Config.enableRandomResourceLocation(); label: for (Map.Entry> entry : imageToModels.asMap().entrySet()) { @@ -1108,7 +1114,48 @@ public abstract class AbstractPackManager implements PackManager { continue label; } } - TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString()); + if (Config.fixTextureAtlas()) { + texturesToFix.add(key); + } else { + TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString()); + } + } + } + + if (Config.fixTextureAtlas() && !texturesToFix.isEmpty()) { + List sourcesToAdd = new ArrayList<>(); + for (Key toFix : texturesToFix) { + JsonObject source = new JsonObject(); + source.addProperty("type", "single"); + source.addProperty("resource", toFix.asString()); + sourcesToAdd.add(source); + } + + Path defaultAtlas = path.resolve("assets").resolve("minecraft").resolve("atlases").resolve("blocks.json"); + if (!allAtlas.containsKey(defaultAtlas)) { + allAtlas.put(defaultAtlas, new JsonObject()); + try { + Files.createDirectories(defaultAtlas.getParent()); + } catch (IOException e) { + this.plugin.logger().warn("could not create default atlas directory", e); + } + } + + for (Map.Entry atlas : allAtlas.entrySet()) { + JsonObject right = atlas.getValue(); + JsonArray sources = right.getAsJsonArray("sources"); + if (sources == null) { + sources = new JsonArray(); + right.add("sources", sources); + } + for (JsonObject source : sourcesToAdd) { + sources.add(source); + } + try { + GsonHelper.writeJsonFile(right, atlas.getKey()); + } catch (IOException e) { + this.plugin.logger().warn("Failed to write atlas to json file", e); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 1cb3d542b..8f36b8752 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -64,7 +64,8 @@ public class Config { protected boolean resource_pack$protection$crash_tools$method_2; protected boolean resource_pack$protection$crash_tools$method_3; - protected boolean resource_pack$validate$enable; + protected boolean resource_pack$validation$enable; + protected boolean resource_pack$validation$fix_atlas; protected boolean resource_pack$exclude_core_shaders; protected boolean resource_pack$protection$obfuscation$enable; @@ -296,7 +297,7 @@ public class Config { resource_pack$protection$crash_tools$method_1 = config.getBoolean("resource-pack.protection.crash-tools.method-1", false); resource_pack$protection$crash_tools$method_2 = config.getBoolean("resource-pack.protection.crash-tools.method-2", false); resource_pack$protection$crash_tools$method_3 = config.getBoolean("resource-pack.protection.crash-tools.method-3", false); - resource_pack$protection$obfuscation$enable = config.getBoolean("resource-pack.protection.obfuscation.enable", false); + resource_pack$protection$obfuscation$enable = VersionHelper.PREMIUM && config.getBoolean("resource-pack.protection.obfuscation.enable", false); resource_pack$protection$obfuscation$seed = config.getLong("resource-pack.protection.obfuscation.seed", 0L); resource_pack$protection$obfuscation$fake_directory = config.getBoolean("resource-pack.protection.obfuscation.fake-directory", false); resource_pack$protection$obfuscation$escape_unicode = config.getBoolean("resource-pack.protection.obfuscation.escape-unicode", false); @@ -313,7 +314,8 @@ public class Config { resource_pack$protection$obfuscation$resource_location$bypass_models = config.getStringList("resource-pack.protection.obfuscation.resource-location.bypass-models"); resource_pack$protection$obfuscation$resource_location$bypass_sounds = config.getStringList("resource-pack.protection.obfuscation.resource-location.bypass-sounds"); resource_pack$protection$obfuscation$resource_location$bypass_equipments = config.getStringList("resource-pack.protection.obfuscation.resource-location.bypass-equipments"); - resource_pack$validate$enable = config.getBoolean("resource-pack.validate.enable", true); + resource_pack$validation$enable = config.getBoolean("resource-pack.validation.enable", true); + resource_pack$validation$fix_atlas = VersionHelper.PREMIUM && config.getBoolean("resource-pack.validation.fix-atlas", true); resource_pack$exclude_core_shaders = config.getBoolean("resource-pack.exclude-core-shaders", false); resource_pack$overlay_format = config.getString("resource-pack.overlay-format", "overlay_{version}"); if (!resource_pack$overlay_format.contains("{version}")) { @@ -895,7 +897,11 @@ public class Config { } public static boolean validateResourcePack() { - return instance.resource_pack$validate$enable; + return instance.resource_pack$validation$enable; + } + + public static boolean fixTextureAtlas() { + return instance.resource_pack$validation$fix_atlas; } public static boolean excludeShaders() { diff --git a/gradle.properties b/gradle.properties index c3f2fe021..94725baf0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] project_version=0.0.63.7 -config_version=46 +config_version=47 lang_version=31 project_group=net.momirealms latest_supported_version=1.21.8 From 4cca5288e4b3c513cc5459784b794a106f84d13e Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 21:31:30 +0800 Subject: [PATCH 209/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8E=9F=E7=89=88?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/behavior/FurnitureItemBehavior.java | 5 ++-- .../core/block/AbstractBlockManager.java | 2 +- .../furniture/AbstractFurnitureManager.java | 25 ++++++++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java index 82a1f8457..83b4bdef6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java @@ -20,6 +20,7 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; @@ -185,9 +186,9 @@ public class FurnitureItemBehavior extends ItemBehavior { if (id instanceof Map map) { if (map.containsKey(key.toString())) { // 防呆 - BukkitFurnitureManager.instance().parser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)); + BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false))); } else { - BukkitFurnitureManager.instance().parser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false)); + BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map, false))); } return new FurnitureItemBehavior(key); } else { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 94740912f..206feef2d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -253,7 +253,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem String after = entry.getValue().toString(); // 先解析为唯一的wrapper BlockStateWrapper beforeState = createVanillaBlockState(before); - BlockStateWrapper afterState = createVanillaBlockState(before); + BlockStateWrapper afterState = createVanillaBlockState(after); if (beforeState == null) { exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", before)); continue; diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index 0d5a714d9..d99d43e8b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -1,20 +1,25 @@ package net.momirealms.craftengine.core.entity.furniture; +import net.momirealms.craftengine.core.block.AbstractBlockManager; import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.incendo.cloud.suggestion.Suggestion; import org.joml.Vector3f; +import java.io.IOException; import java.nio.file.Path; import java.util.*; @@ -31,7 +36,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { } @Override - public IdSectionConfigParser parser() { + public FurnitureParser parser() { return this.furnitureParser; } @@ -76,6 +81,24 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { public class FurnitureParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" }; + private final List pendingConfigSections = new ArrayList<>(); + + public void addPendingConfigSection(PendingConfigSection section) { + this.pendingConfigSections.add(section); + } + + @Override + public void preProcess() { + for (PendingConfigSection section : this.pendingConfigSections) { + ResourceConfigUtils.runCatching( + section.path(), + section.node(), + () -> parseSection(section.pack(), section.path(), section.node(), section.id(), section.config()), + () -> GsonHelper.get().toJson(section.config()) + ); + } + this.pendingConfigSections.clear(); + } @Override public String[] sectionId() { From 7d24430a40a2637db8ade878d0d5ece34d96d770 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Sun, 28 Sep 2025 21:35:52 +0800 Subject: [PATCH 210/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcrafting=20ui?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/plugin/gui/BukkitGuiManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index fb01ccc33..dbf750e06 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -55,7 +55,7 @@ public class BukkitGuiManager implements GuiManager, Listener { case ANVIL -> MenuType.ANVIL.create(bukkitPlayer).open(); case LOOM -> MenuType.LOOM.create(bukkitPlayer).open(); case ENCHANTMENT -> MenuType.ENCHANTMENT.create(bukkitPlayer).open(); - case CRAFTING -> MenuType.CRAFTER_3X3.create(bukkitPlayer).open(); + case CRAFTING -> MenuType.CRAFTING.create(bukkitPlayer).open(); case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE.create(bukkitPlayer).open(); case SMITHING -> MenuType.SMITHING.create(bukkitPlayer).open(); case GRINDSTONE -> MenuType.GRINDSTONE.create(bukkitPlayer).open(); From 5c06d8af13e4af9b377f6236b00f7eaa7db5afc7 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 00:26:57 +0800 Subject: [PATCH 211/226] =?UTF-8?q?=E5=AD=97=E4=BD=93=E5=AD=97=E7=AC=A6id?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mythicmobs/MythicItemDrop.java | 4 +- .../bukkit/block/BukkitBlockManager.java | 2 +- .../behavior/SimpleStorageBlockBehavior.java | 6 +- .../furniture/hitbox/ShulkerHitBox.java | 2 +- .../item/listener/DebugStickListener.java | 3 +- .../item/listener/ItemEventListener.java | 4 +- .../bukkit/plugin/gui/BukkitGuiManager.java | 31 +- .../handler/ProjectilePacketHandler.java | 12 +- common-files/src/main/resources/config.yml | 9 +- .../resources/default/configuration/emoji.yml | 6 +- .../resources/internal/configuration/gui.yml | 12 - .../internal/configuration/offset_chars.yml | 88 +++--- .../src/main/resources/translations/de.yml | 4 +- .../src/main/resources/translations/en.yml | 8 +- .../src/main/resources/translations/es.yml | 2 +- .../src/main/resources/translations/ru_ru.yml | 4 +- .../src/main/resources/translations/tr.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 5 +- .../core/block/AbstractBlockManager.java | 2 +- .../furniture/AbstractFurnitureManager.java | 3 - .../core/font/AbstractFontManager.java | 262 +++++++++++----- .../core/item/AbstractItemManager.java | 2 +- .../craftengine/core/loot/LootPool.java | 4 +- .../entry/AbstractLootEntryContainer.java | 4 +- .../AbstractLootConditionalFunction.java | 4 +- .../core/pack/AbstractPackManager.java | 3 + .../core/plugin/config/Config.java | 28 +- .../context/condition/AllOfCondition.java | 3 +- .../context/condition/AnyOfCondition.java | 3 +- .../function/AbstractConditionalFunction.java | 3 +- .../context/function/BreakBlockFunction.java | 4 +- .../context/function/PlaceBlockFunction.java | 4 +- .../plugin/context/function/RunFunction.java | 4 +- .../number/GaussianNumberProvider.java | 4 +- .../parameter/EntityParameterProvider.java | 8 +- .../parameter/PlayerParameterProvider.java | 8 +- .../parameter/PositionParameterProvider.java | 8 +- .../context/selector/AllPlayerSelector.java | 4 +- .../plugin/network/codec/NetworkCodecs.java | 4 +- .../core/registry/ConstantBoundRegistry.java | 4 +- .../craftengine/core/util/CharacterUtils.java | 3 +- .../craftengine/core/util/Color.java | 2 +- .../craftengine/core/util/Direction.java | 8 +- .../core/util/FriendlyByteBuf.java | 8 +- .../core/util/Int2ObjectBiMap.java | 2 +- .../craftengine/core/util/MCUtils.java | 294 ------------------ .../craftengine/core/util/MiscUtils.java | 294 +++++++++++++++++- .../core/util/QuaternionUtils.java | 12 +- .../craftengine/core/world/BlockPos.java | 4 +- .../core/world/EntityHitResult.java | 8 +- .../craftengine/core/world/Vec3d.java | 4 +- .../core/world/chunk/PalettedContainer.java | 8 +- gradle.properties | 26 +- 53 files changed, 676 insertions(+), 574 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java index dbb2f00db..9258e4214 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java @@ -13,7 +13,7 @@ import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop { context = ItemBuildContext.of(player); } } - int amountInt = MCUtils.fastFloor(amount + 0.5F); + int amountInt = MiscUtils.fastFloor(amount + 0.5F); ItemStack itemStack = this.customItem.buildItemStack(context, amountInt); return adapt(itemStack).amount(amountInt); } 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 acc7ee28e..705ef4dd4 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 @@ -362,7 +362,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { // 注册服务端侧的真实方块 private void registerServerSideCustomBlocks(int count) { // 这个会影响全局调色盘 - if (MCUtils.ceilLog2(this.vanillaBlockStateCount + count) == MCUtils.ceilLog2(this.vanillaBlockStateCount)) { + if (MiscUtils.ceilLog2(this.vanillaBlockStateCount + count) == MiscUtils.ceilLog2(this.vanillaBlockStateCount)) { PalettedContainer.NEED_DOWNGRADE = false; } try { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java index 0a7933cad..07b33233f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SimpleStorageBlockBehavior.java @@ -20,7 +20,7 @@ import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.AdventureHelper; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.CEWorld; @@ -166,7 +166,7 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E } } signal /= (float) inventory.getSize(); - return MCUtils.lerpDiscrete(signal, 0, 15); + return MiscUtils.lerpDiscrete(signal, 0, 15); } } return 0; @@ -194,7 +194,7 @@ public class SimpleStorageBlockBehavior extends BukkitBlockBehavior implements E @Override public BlockBehavior create(CustomBlock block, Map arguments) { String title = arguments.getOrDefault("title", "").toString(); - int rows = MCUtils.clamp(ResourceConfigUtils.getAsInt(arguments.getOrDefault("rows", 1), "rows"), 1, 6); + int rows = MiscUtils.clamp(ResourceConfigUtils.getAsInt(arguments.getOrDefault("rows", 1), "rows"), 1, 6); Map sounds = (Map) arguments.get("sounds"); boolean hasAnalogOutputSignal = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-signal", true), "has-signal"); SoundData openSound = null; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 5f72065db..9883004cc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -171,7 +171,7 @@ public class ShulkerHitBox extends AbstractHitBox { } private static float getPhysicalPeek(float peek) { - return 0.5F - MCUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F; + return 0.5F - MiscUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F; } public boolean interactionEntity() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java index 8b8df50b4..b3f763ce1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java @@ -17,7 +17,6 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.util.MiscUtils; import org.bukkit.Material; import org.bukkit.block.Block; @@ -119,7 +118,7 @@ public class DebugStickListener implements Listener { } private static T getRelative(Iterable elements, @Nullable T current, boolean inverse) { - return inverse ? MCUtils.findPreviousInIterable(elements, current) : MCUtils.findNextInIterable(elements, current); + return inverse ? MiscUtils.findPreviousInIterable(elements, current) : MiscUtils.findNextInIterable(elements, current); } private static > String getNameHelper(ImmutableBlockState state, Property property) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index 2dc58f76d..9038391e2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -474,9 +474,9 @@ public class ItemEventListener implements Listener { if (foodData == null) return; event.setCancelled(true); int oldFoodLevel = player.getFoodLevel(); - if (foodData.nutrition() != 0) player.setFoodLevel(MCUtils.clamp(oldFoodLevel + foodData.nutrition(), 0, 20)); + if (foodData.nutrition() != 0) player.setFoodLevel(MiscUtils.clamp(oldFoodLevel + foodData.nutrition(), 0, 20)); float oldSaturation = player.getSaturation(); - if (foodData.saturation() != 0) player.setSaturation(MCUtils.clamp(oldSaturation, 0, 10)); + if (foodData.saturation() != 0) player.setSaturation(MiscUtils.clamp(oldSaturation, 0, 10)); } private boolean cancelEventIfHasInteraction(PlayerInteractEvent event, BukkitServerPlayer player, InteractionHand hand) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index dbf750e06..65feeb73d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -12,7 +12,6 @@ import net.momirealms.craftengine.bukkit.util.InventoryUtils; import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.gui.*; -import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -23,11 +22,9 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.InventoryView; -import org.bukkit.inventory.MenuType; public class BukkitGuiManager implements GuiManager, Listener { - private static final boolean useNewOpenInventory = ReflectionUtils.getDeclaredMethod(InventoryView.class, void.class, new String[]{"open"}) != null; +// private static final boolean useNewOpenInventory = ReflectionUtils.getDeclaredMethod(InventoryView.class, void.class, new String[]{"open"}) != null; private static BukkitGuiManager instance; private final BukkitCraftEngine plugin; @@ -46,21 +43,21 @@ public class BukkitGuiManager implements GuiManager, Listener { HandlerList.unregisterAll(this); } - @SuppressWarnings("UnstableApiUsage") +// @SuppressWarnings("UnstableApiUsage") @Override public void openInventory(net.momirealms.craftengine.core.entity.player.Player player, GuiType guiType) { Player bukkitPlayer = (Player) player.platformPlayer(); - if (useNewOpenInventory) { - switch (guiType) { - case ANVIL -> MenuType.ANVIL.create(bukkitPlayer).open(); - case LOOM -> MenuType.LOOM.create(bukkitPlayer).open(); - case ENCHANTMENT -> MenuType.ENCHANTMENT.create(bukkitPlayer).open(); - case CRAFTING -> MenuType.CRAFTING.create(bukkitPlayer).open(); - case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE.create(bukkitPlayer).open(); - case SMITHING -> MenuType.SMITHING.create(bukkitPlayer).open(); - case GRINDSTONE -> MenuType.GRINDSTONE.create(bukkitPlayer).open(); - } - } else { +// if (useNewOpenInventory) { +// switch (guiType) { +// case ANVIL -> MenuType.ANVIL.create(bukkitPlayer).open(); +// case LOOM -> MenuType.LOOM.create(bukkitPlayer).open(); +// case ENCHANTMENT -> MenuType.ENCHANTMENT.create(bukkitPlayer).open(); +// case CRAFTING -> MenuType.CRAFTING.create(bukkitPlayer).open(); +// case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE.create(bukkitPlayer).open(); +// case SMITHING -> MenuType.SMITHING.create(bukkitPlayer).open(); +// case GRINDSTONE -> MenuType.GRINDSTONE.create(bukkitPlayer).open(); +// } +// } else { switch (guiType) { case ANVIL -> LegacyInventoryUtils.openAnvil(bukkitPlayer); case LOOM -> LegacyInventoryUtils.openLoom(bukkitPlayer); @@ -70,7 +67,7 @@ public class BukkitGuiManager implements GuiManager, Listener { case ENCHANTMENT -> LegacyInventoryUtils.openEnchanting(bukkitPlayer); case CARTOGRAPHY -> LegacyInventoryUtils.openCartographyTable(bukkitPlayer); } - } +// } } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java index 1524c7a39..a85925165 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ProjectilePacketHandler.java @@ -16,7 +16,7 @@ import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.plugin.network.NMSPacketEvent; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.FriendlyByteBuf; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.Vec3d; import org.bukkit.inventory.ItemStack; @@ -89,8 +89,8 @@ public class ProjectilePacketHandler implements EntityPacketHandler { buf.writeDouble(y); buf.writeDouble(z); if (VersionHelper.isOrAbove1_21_9()) buf.writeLpVec3(movement); - buf.writeByte(MCUtils.packDegrees(MCUtils.clamp(-MCUtils.unpackDegrees(xRot), -90.0F, 90.0F))); - buf.writeByte(MCUtils.packDegrees(-MCUtils.unpackDegrees(yRot))); + buf.writeByte(MiscUtils.packDegrees(MiscUtils.clamp(-MiscUtils.unpackDegrees(xRot), -90.0F, 90.0F))); + buf.writeByte(MiscUtils.packDegrees(-MiscUtils.unpackDegrees(yRot))); buf.writeByte(yHeadRot); buf.writeVarInt(data); if (!VersionHelper.isOrAbove1_21_9()) buf.writeShort(xa); @@ -142,12 +142,12 @@ public class ProjectilePacketHandler implements EntityPacketHandler { short xa = FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$xa(packet); short ya = FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$ya(packet); short za = FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$za(packet); - float xRot = MCUtils.unpackDegrees(FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$xRot(packet)); - float yRot = MCUtils.unpackDegrees(FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$yRot(packet)); + float xRot = MiscUtils.unpackDegrees(FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$xRot(packet)); + float yRot = MiscUtils.unpackDegrees(FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$yRot(packet)); boolean onGround = FastNMS.INSTANCE.field$ClientboundMoveEntityPacket$onGround(packet); return FastNMS.INSTANCE.constructor$ClientboundMoveEntityPacket$PosRot( entityId, xa, ya, za, - MCUtils.packDegrees(-yRot), MCUtils.packDegrees(MCUtils.clamp(-xRot, -90.0F, 90.0F)), + MiscUtils.packDegrees(-yRot), MiscUtils.packDegrees(MiscUtils.clamp(-xRot, -90.0F, 90.0F)), onGround ); } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 3203ee842..a96c789c1 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -175,7 +175,7 @@ equipment: block: # This decides the amount of real blocks on serverside. Requires a restart to apply. - serverside-blocks: 2000 + serverside-blocks: 2025 # Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience. sound-system: enable: true @@ -232,12 +232,17 @@ image: chat: true command: true sign: true + # Decided the starting value for automatic codepoint assignment. + codepoint-starting-value: + default: 19968 + overrides: + minecraft:default: 57344 # 57344 ~ 63743 (U+E000 ~ U+F8FF) # Defines Unicode characters used for positioning # - Must match the font defined in resource packs # - Do NOT modify unless you understand text rendering mechanics offset-characters: - font: minecraft:offset_chars + font: minecraft:default -1: '\uf800' -2: '\uf801' -3: '\uf802' diff --git a/common-files/src/main/resources/resources/default/configuration/emoji.yml b/common-files/src/main/resources/resources/default/configuration/emoji.yml index 49c0ca5c6..06262afd2 100644 --- a/common-files/src/main/resources/resources/default/configuration/emoji.yml +++ b/common-files/src/main/resources/resources/default/configuration/emoji.yml @@ -122,8 +122,4 @@ images: ascent: 9 font: minecraft:emoji file: minecraft:font/image/emojis.png - chars: - - \ub000\ub001\ub002\ub003 - - \ub004\ub005\ub006\ub007 - - \ub008\ub009\ub00a\ub00b - - \ub00c\ub00d\ub00e\ub00f \ No newline at end of file + grid-size: 4,4 \ No newline at end of file diff --git a/common-files/src/main/resources/resources/internal/configuration/gui.yml b/common-files/src/main/resources/resources/internal/configuration/gui.yml index 344d37d55..87efc97c5 100644 --- a/common-files/src/main/resources/resources/internal/configuration/gui.yml +++ b/common-files/src/main/resources/resources/internal/configuration/gui.yml @@ -4,73 +4,61 @@ images: ascent: 18 font: minecraft:gui file: minecraft:font/gui/custom/item_browser.png - char: \ub000 internal:category: height: 140 ascent: 18 font: minecraft:gui file: minecraft:font/gui/custom/category.png - char: \ub001 internal:crafting_recipe: height: 142 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/crafting_recipe.png - char: \ub002 internal:cooking_recipe: height: 138 ascent: 16 font: minecraft:gui file: minecraft:font/gui/custom/cooking_recipe.png - char: \ub003 internal:smelting: height: 23 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/smelting.png - char: \ub004 internal:smoking: height: 23 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/smoking.png - char: \ub005 internal:blasting: height: 23 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/blasting.png - char: \ub006 internal:campfire: height: 23 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/campfire.png - char: \ub007 internal:stonecutting_recipe: height: 142 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/stonecutting_recipe.png - char: \ub008 internal:smithing_transform_recipe: height: 142 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/smithing_transform_recipe.png - char: \ub009 internal:brewing_recipe: height: 142 ascent: 20 font: minecraft:gui file: minecraft:font/gui/custom/brewing_recipe.png - char: \ub00a internal:no_recipe: height: 140 ascent: 18 font: minecraft:gui file: minecraft:font/gui/custom/no_recipe.png - char: \ub00b templates: internal:icon/2d: material: arrow diff --git a/common-files/src/main/resources/resources/internal/configuration/offset_chars.yml b/common-files/src/main/resources/resources/internal/configuration/offset_chars.yml index f315f2571..4857eb238 100644 --- a/common-files/src/main/resources/resources/internal/configuration/offset_chars.yml +++ b/common-files/src/main/resources/resources/internal/configuration/offset_chars.yml @@ -2,264 +2,264 @@ images: internal:neg_1: height: -3 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf800 internal:neg_2: height: -4 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf801 internal:neg_3: height: -5 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf802 internal:neg_4: height: -6 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf803 internal:neg_5: height: -7 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf804 internal:neg_6: height: -8 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf805 internal:neg_7: height: -9 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf806 internal:neg_8: height: -10 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf807 internal:neg_9: height: -11 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf808 internal:neg_10: height: -12 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf809 internal:neg_11: height: -13 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf80a internal:neg_12: height: -14 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf80b internal:neg_13: height: -15 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf80c internal:neg_14: height: -16 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf80d internal:neg_15: height: -17 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf80e internal:neg_16: height: -18 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf80f internal:neg_24: height: -26 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf810 internal:neg_32: height: -34 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf811 internal:neg_48: height: -50 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf812 internal:neg_64: height: -66 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf813 internal:neg_128: height: -130 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf814 internal:neg_256: height: -258 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf815 internal:pos_1: height: -1 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf830 internal:pos_2: height: 1 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf831 internal:pos_3: height: 2 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf832 internal:pos_4: height: 3 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf833 internal:pos_5: height: 4 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf834 internal:pos_6: height: 5 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf835 internal:pos_7: height: 6 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf836 internal:pos_8: height: 7 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf837 internal:pos_9: height: 8 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf838 internal:pos_10: height: 9 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf839 internal:pos_11: height: 10 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf83a internal:pos_12: height: 11 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf83b internal:pos_13: height: 12 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf83c internal:pos_14: height: 13 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf83d internal:pos_15: height: 14 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf83e internal:pos_16: height: 15 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf83f internal:pos_24: height: 23 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf840 internal:pos_32: height: 31 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf841 internal:pos_48: height: 47 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf842 internal:pos_64: height: 63 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf843 internal:pos_128: height: 127 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf844 internal:pos_256: height: 255 ascent: -5000 - font: minecraft:offset_chars + font: minecraft:default file: minecraft:font/offset/space_split.png char: \uf845 \ No newline at end of file diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index a02740112..3d10d877e 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -115,7 +115,7 @@ warning.config.image.missing_file: "Problem in Datei gefunden - warning.config.image.invalid_file_chars: "Problem in Datei gefunden - Das Image '' hat ein 'file'-Argument '', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.image.invalid_font_chars: "Problem in Datei gefunden - Das Image '' hat ein 'font'-Argument '', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.image.missing_char: "Problem in Datei gefunden - Beim Image '' fehlt das erforderliche 'char'-Argument." -warning.config.image.codepoint_conflict: "Problem in Datei gefunden - Das Image '' verwendet ein Zeichen '()' im Font , das bereits von einem anderen Image '' verwendet wird." +warning.config.image.codepoint.conflict: "Problem in Datei gefunden - Das Image '' verwendet ein Zeichen '()' im Font , das bereits von einem anderen Image '' verwendet wird." warning.config.image.invalid_codepoint_grid: "Problem in Datei gefunden - Image '' hat ein ungültiges 'chars' Codepoint-Grid." warning.config.image.invalid_char: "Problem in Datei gefunden - Image '' hat einen char-Parameter, der kombinierende Zeichen enthält, was zur Aufteilung des Bildes führen kann." warning.config.image.invalid_hex_value: "Problem in Datei gefunden - Das Image '' verwendet ein Unicode-Zeichen '', das kein gültiger hexadezimaler (Basis 16) Wert ist." @@ -407,7 +407,7 @@ warning.config.selector.invalid_target: "Problem in Datei gefund warning.config.resource_pack.item_model.already_exist: "Generierung des Item-Models für '' fehlgeschlagen, da die Datei '' bereits existiert." warning.config.resource_pack.model.generation.already_exist: "Generierung des Models fehlgeschlagen, da die Model-Datei '' bereits existiert." warning.config.resource_pack.generation.missing_font_texture: "Beim Font '' fehlt die erforderliche Textur: ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "Textur '' ist nicht im Atlas aufgeführt. Du musst den Texturpfad zum Atlas hinzufügen oder die 'obfuscation'-Option in der config.yml aktivieren." +warning.config.resource_pack.generation.texture_not_in_atlas: "Textur '' ist nicht im Atlas aufgeführt. Du musst den Texturpfad zum Atlas hinzufügen oder die 'obfuscation'/'fix-atlas'-Option in der config.yml aktivieren." warning.config.resource_pack.generation.missing_model_texture: "Beim Model '' fehlt die Textur ''" warning.config.resource_pack.generation.missing_item_model: "Beim Item '' fehlt die Model-Datei: ''" warning.config.resource_pack.generation.missing_block_model: "Beim Block-State '' fehlt die Model-Datei: ''" diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index e15c5f89d..a9cf1ec13 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -122,8 +122,10 @@ warning.config.image.height_ascent_conflict: "Issue found in file Issue found in file - The image '' is missing the required 'file' argument." warning.config.image.invalid_file_chars: "Issue found in file - The image '' has a 'file' argument '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.image.invalid_font_chars: "Issue found in file - The image '' has a 'font' argument '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." +warning.config.image.invalid_grid_size: "Issue found in file - The image '' is using an incorrect grid size format ''. Correct example: '3,5'" warning.config.image.missing_char: "Issue found in file - The image '' is missing the required 'char' argument." -warning.config.image.codepoint_conflict: "Issue found in file - The image '' is using a character '()' in font that has been used by another image ''." +warning.config.image.codepoint.conflict: "Issue found in file - The image '' is using a character '()' in font that has been used by another image ''." +warning.config.image.codepoint.exhausted: "Issue found in file - Cannot allocate codepoint for image '' as the codepoints have already been exhausted for font ''." warning.config.image.invalid_codepoint_grid: "Issue found in file - Image '' has an invalid 'chars' codepoint grid." warning.config.image.invalid_char: "Issue found in file - Image '' has a char parameter containing combining characters, which may result in image splitting." warning.config.image.invalid_hex_value: "Issue found in file - The image '' is using a unicode character '' that is not a valid hexadecimal (radix 16) value." @@ -193,7 +195,7 @@ warning.config.item.invalid_custom_model_data: "Issue found in file Issue found in file - The item '' is using a custom model data '' that is too large. It's recommended to use a value lower than 16,777,216." warning.config.item.item_model.conflict: "Issue found in file - The item '' is using an invalid 'item-model' option because this item model has been occupied by a vanilla item." warning.config.item.custom_model_data.conflict: "Issue found in file - The item '' is using a custom model data '' that has been occupied by item ''." -warning.config.item.custom_model_data.exhausted: "Issue found in file - Cannot allocate custom model data for item '' as the custom model data has already been exhausted." +warning.config.item.custom_model_data.exhausted: "Issue found in file - Cannot allocate custom model data for item '' as the custom model data has already been exhausted for material ''." warning.config.item.invalid_component: "Issue found in file - The item '' is using a non-existing component type ''." warning.config.item.missing_model_id: "Issue found in file - The item '' is missing the required 'custom-model-data' or 'item-model' argument." warning.config.item.missing_model: "Issue found in file - The item '' is missing the required 'model' section for 1.21.4+ resource pack support." @@ -461,7 +463,7 @@ warning.config.resource_pack.generation.missing_item_model: "Item 'Block state '' is missing model file: ''" warning.config.resource_pack.generation.missing_parent_model: "Model '' cannot find parent model: ''" warning.config.resource_pack.generation.missing_equipment_texture: "Equipment '' is missing texture ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "Texture '' is not listed in the atlas. You need to add the texture path to the atlas or enable 'obfuscation' option in config.yml." +warning.config.resource_pack.generation.texture_not_in_atlas: "Texture '' is not listed in the atlas. You need to add the texture path to the atlas or enable 'obfuscation' or 'fix-atlas' option in config.yml." warning.config.resource_pack.invalid_overlay_format: "Issue found in config.yml at 'resource-pack.overlay-format' - Invalid overlay format ''. Overlay format must contain the placeholder '{version}'." warning.config.equipment.duplicate: "Issue found in file - Duplicated equipment ''. Please check if there is the same configuration in other files." warning.config.equipment.missing_type: "Issue found in file - The equipment '' is missing the required 'type' argument." diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index eaa30b979..908d1752c 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -77,7 +77,7 @@ warning.config.image.missing_file: "Problema encontrado en el archivo Problema encontrado en el archivo - La imagen '' tiene un argumento 'file' '' que contiene caracteres prohibidos. Por favor lee https://minecraft.wiki/w/Resource_location#Legal_characters" warning.config.image.invalid_font_chars: "Problema encontrado en el archivo - La imagen '' tiene un argumento 'font' '' que contiene caracteres prohibidos. Por favor lee https://minecraft.wiki/w/Resource_location#Legal_characters" warning.config.image.missing_char: "Problema encontrado en el archivo - La imagen '' carece del argumento requerido 'char'." -warning.config.image.codepoint_conflict: "Problema encontrado en el archivo - La imagen '' está usando el carácter '()' que ya ha sido usado por otra imagen '' en la fuente ." +warning.config.image.codepoint.conflict: "Problema encontrado en el archivo - La imagen '' está usando el carácter '()' que ya ha sido usado por otra imagen '' en la fuente ." warning.config.image.invalid_codepoint_grid: "Problema encontrado en el archivo - La imagen '' tiene una cuadrícula de puntos de código 'chars' inválida." warning.config.image.file_not_found: "Problema encontrado en el archivo - Archivo PNG '' no encontrado para la imagen ''." warning.config.image.invalid_hex_value: "Problema encontrado en el archivo - La imagen '' está usando el carácter unicode '' que no es un valor hexadecimal válido." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index e3d30278c..baf7bc52c 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -113,7 +113,7 @@ warning.config.image.missing_file: "Проблема найдена в warning.config.image.invalid_file_chars: "Проблема найдена в файле - Изображение '' имеет 'file' аргумент '', который содержит недопустимые символы. Пожалуйста, прочтите https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.image.invalid_font_chars: "Проблема найдена в файле - Изображение'' имеет 'font' аргумент '', который содержит недопустимые символы. Пожалуйста, прочтите https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.image.missing_char: "Проблема найдена в файле - В изображении '' отсутствует необходимый 'char' аргумент." -warning.config.image.codepoint_conflict: "Проблема найдена в файле - Изображение '' использует символ '()' в шрифте который был использован другим изображением ''." +warning.config.image.codepoint.conflict: "Проблема найдена в файле - Изображение '' использует символ '()' в шрифте который был использован другим изображением ''." warning.config.image.invalid_codepoint_grid: "Проблема найдена в файле - Изображение '' имеет недействительную 'chars' сетку кодовых точек." warning.config.image.invalid_char: "Проблема найдена в файле - Изображение '' имеет параметр char, содержащий комбинированные символы, что может привести к разделению изображения." warning.config.image.invalid_hex_value: "Проблема найдена в файле - Изображение '' использует символ юникода '' это недопустимое шестнадцатеричное (radix 16) значение." @@ -376,7 +376,7 @@ warning.config.resource_pack.item_model.conflict.vanilla: "Не удал warning.config.resource_pack.item_model.already_exist: "Не удалось создать модель элемента для '', потому что файл '' уже существует." warning.config.resource_pack.model.generation.already_exist: "Не удалось создать модель, так как файл модели '' уже существует." warning.config.resource_pack.generation.missing_font_texture: "В шрифте '' отсутствует обязательная текстура: ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "Текстура '' не указана в атласе. Вам нужно добавить путь к текстуре в атлас или включить 'obfuscation' опцию в config.yml." +warning.config.resource_pack.generation.texture_not_in_atlas: "Текстура '' не указана в атласе. Вам нужно добавить путь к текстуре в атлас или включить 'obfuscation'/'fix-atlas' опцию в config.yml." warning.config.resource_pack.generation.missing_model_texture: "В модели '' отсутствует текстура ''" warning.config.resource_pack.generation.missing_item_model: "В предмете '' отсутствует файл модели: ''" warning.config.resource_pack.generation.missing_block_model: "В блоке '' отсутствует файл модели: ''" diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index b62967f92..20538d70e 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -77,7 +77,7 @@ warning.config.image.missing_file: " dosyasında sorun bulundu - warning.config.image.invalid_file_chars: " dosyasında sorun bulundu - '' resmi, yasak karakterler içeren '' 'file' argümanına sahip. Lütfen https://minecraft.wiki/w/Resource_location#Legal_characters sayfasını okuyun." warning.config.image.invalid_font_chars: " dosyasında sorun bulundu - '' resmi, yasak karakterler içeren '' 'font' argümanına sahip. Lütfen https://minecraft.wiki/w/Resource_location#Legal_characters sayfasını okuyun." warning.config.image.missing_char: " dosyasında sorun bulundu - '' resmi gerekli 'char' argümanı eksik." -warning.config.image.codepoint_conflict: " dosyasında sorun bulundu - '' resmi, yazı tipinde başka bir resim '' tarafından kullanılmış olan '()' karakterini kullanıyor." +warning.config.image.codepoint.conflict: " dosyasında sorun bulundu - '' resmi, yazı tipinde başka bir resim '' tarafından kullanılmış olan '()' karakterini kullanıyor." warning.config.image.invalid_codepoint_grid: " dosyasında sorun bulundu - '' resminin geçersiz bir 'chars' kod noktası ızgarası var." warning.config.image.invalid_hex_value: " dosyasında sorun bulundu - '' resmi, geçerli bir onaltılık (16 tabanlı) değer olmayan '' unicode karakterini kullanıyor." warning.config.recipe.duplicate: " dosyasında sorun bulundu - Yinelenen tarif ''. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 9d0175d00..21e1bae3b 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -122,8 +122,9 @@ warning.config.image.height_ascent_conflict: "在文件 发现 warning.config.image.missing_file: "在文件 发现问题 - 图片 '' 缺少必需的 'file' 参数" warning.config.image.invalid_file_chars: "在文件 发现问题 - 图片 '' 的 'file' 参数 '' 包含非法字符 请参考 https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" warning.config.image.invalid_font_chars: "在文件 发现问题 - 图片 '' 的 'font' 参数 '' 包含非法字符 请参考 https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" +warning.config.image.invalid_grid_size: "在文件 发现问题 - 图片 '' 使用了无效的网格尺寸 ''. 正确的格式 '3,5'" warning.config.image.missing_char: "在文件 发现问题 - 图片 '' 缺少必需的 'char' 参数" -warning.config.image.codepoint_conflict: "在文件 发现问题 - 图片 '' 在字体 中使用的字符 '()' 已被其他图片 '' 占用" +warning.config.image.codepoint.conflict: "在文件 发现问题 - 图片 '' 在字体 中使用的字符 '()' 已被其他图片 '' 占用" warning.config.image.invalid_codepoint_grid: "在文件 发现问题 - 图片 '' 的 'chars' 码位网格无效" warning.config.image.invalid_char: "在文件 发现问题 - 图片 '' 的 'char' 参数包含组合字符可能导致图片分裂" warning.config.image.invalid_hex_value: "在文件 发现问题 - 图片 '' 使用的 Unicode 字符 '' 不是有效的十六进制值" @@ -443,7 +444,7 @@ warning.config.resource_pack.generation.missing_item_model: "物品'方块状态''缺少模型文件: ''" warning.config.resource_pack.generation.missing_parent_model: "模型''找不到父级模型文件: ''" warning.config.resource_pack.generation.missing_equipment_texture: "装备 '' 缺少纹理 ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "纹理''不在图集内. 你需要将纹理路径或文件夹前缀添加到图集内,或者启用 config.yml 中的 'obfuscation' 选项" +warning.config.resource_pack.generation.texture_not_in_atlas: "纹理''不在图集内. 你需要将纹理路径或文件夹前缀添加到图集内,或者启用 config.yml 中的 'obfuscation'/'fix-atlas' 或 'fix-atlas' 选项" warning.config.resource_pack.invalid_overlay_format: "在 config.yml 的 'resource-pack.overlay-format' 处发现问题 - 无效的overlay格式 ''. Overlay格式必须包含占位符 '{version}'" warning.config.equipment.duplicate: "在文件 发现问题 - 重复的装备配置 ''。请检查其他文件中是否存在相同配置" warning.config.equipment.missing_type: "在文件 发现问题 - 装备 '' 缺少必需的 'type' 参数" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 206feef2d..13078a68a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -442,7 +442,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return; } } catch (InterruptedException e) { - AbstractBlockManager.this.plugin.logger().warn("Interrupted while parsing allocating internal block state", e); + AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating internal block state for block " + id.asString(), e); return; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index d99d43e8b..008d83b52 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.entity.furniture; -import net.momirealms.craftengine.core.block.AbstractBlockManager; import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.loot.LootTable; @@ -8,7 +7,6 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -19,7 +17,6 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.incendo.cloud.suggestion.Suggestion; import org.joml.Vector3f; -import java.io.IOException; import java.nio.file.Path; import java.util.*; diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 89721f922..37b33b51b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -2,10 +2,13 @@ package net.momirealms.craftengine.core.font; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.AbstractItemManager; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; +import net.momirealms.craftengine.core.pack.cache.IdAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.ContextHolder; @@ -24,6 +27,8 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -54,6 +59,14 @@ public abstract class AbstractFontManager implements FontManager { this.emojiParser = new EmojiParser(); } + public ImageParser imageParser() { + return imageParser; + } + + public EmojiParser emojiParser() { + return emojiParser; + } + @Override public void load() { this.offsetFont = Optional.ofNullable(plugin.config().settings().getSection("image.offset-characters")) @@ -441,6 +454,7 @@ public abstract class AbstractFontManager implements FontManager { public class ImageParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"images", "image"}; + private final Map idAllocators = new HashMap<>(); @Override public String[] sectionId() { @@ -452,9 +466,43 @@ public abstract class AbstractFontManager implements FontManager { return LoadingSequence.IMAGE; } + @Override + public void postProcess() { + for (Map.Entry entry : this.idAllocators.entrySet()) { + entry.getValue().processPendingAllocations(); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + AbstractFontManager.this.plugin.logger().warn("Error while saving codepoint allocation for font " + entry.getKey().asString(), e); + } + } + } + + @Override + public void preProcess() { + this.idAllocators.clear(); + } + + public IdAllocator getOrCreateIdAllocator(Key key) { + return this.idAllocators.computeIfAbsent(key, k -> { + IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("font").resolve(k.namespace()).resolve(k.value() + ".json")); + newAllocator.reset(Config.codepointStartingValue(k), 1114111); // utf16 + try { + newAllocator.loadFromCache(); + } catch (IOException e) { + AbstractFontManager.this.plugin.logger().warn("Error while loading chars data from cache for font " + k.asString(), e); + } + return newAllocator; + }); + } + + public Map idAllocators() { + return this.idAllocators; + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { - if (images.containsKey(id)) { + if (AbstractFontManager.this.images.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.image.duplicate"); } @@ -463,119 +511,165 @@ public abstract class AbstractFontManager implements FontManager { throw new LocalizedResourceConfigException("warning.config.image.missing_file"); } - String resourceLocation = CharacterUtils.replaceBackslashWithSlash(file.toString()); + String resourceLocation = MiscUtils.make(CharacterUtils.replaceBackslashWithSlash(file.toString()), s -> s.endsWith(".png") ? s : s + ".png"); if (!ResourceLocation.isValid(resourceLocation)) { throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", resourceLocation); } - - String fontName = section.getOrDefault("font", "minecraft:default").toString(); + String fontName = section.getOrDefault("font", pack.namespace()+ ":default").toString(); if (!ResourceLocation.isValid(fontName)) { throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", fontName); } - Key fontKey = Key.withDefaultNamespace(fontName, id.namespace()); - Font font = getOrCreateFont(fontKey); - List chars; + Key fontId = Key.withDefaultNamespace(fontName, id.namespace()); + Font font = getOrCreateFont(fontId); + + IdAllocator allocator = getOrCreateIdAllocator(fontId); + + int rows; + int columns; + List> futureCodepoints = new ArrayList<>(); Object charsObj = ResourceConfigUtils.get(section, "chars", "char"); + // 自动分配 if (charsObj == null) { - throw new LocalizedResourceConfigException("warning.config.image.missing_char"); - } - if (charsObj instanceof List list) { - chars = MiscUtils.getAsStringList(list).stream().map(it -> { - if (it.startsWith("\\u")) { - return CharacterUtils.decodeUnicodeToChars(it); - } else { - return it.toCharArray(); + Object grid = section.get("grid-size"); + if (grid != null) { + String gridString = grid.toString(); + String[] split = gridString.split(","); + if (split.length != 2) { + throw new LocalizedResourceConfigException("warning.config.image.invalid_grid_size", gridString); } - }).toList(); - if (chars.isEmpty()) { + rows = Integer.parseInt(split[0]); + columns = Integer.parseInt(split[1]); + int chars = rows * columns; + if (chars <= 0) { + throw new LocalizedResourceConfigException("warning.config.image.invalid_grid_size", gridString); + } + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + futureCodepoints.add(allocator.requestAutoId(id.asString() + ":" + i + ":" + j)); + } + } + } else { + rows = 1; + columns = 1; + futureCodepoints.add(allocator.requestAutoId(id.asString())); + } + } + // 使用了list + else if (charsObj instanceof List list) { + List charsList = MiscUtils.getAsStringList(list); + if (charsList.isEmpty() || charsList.getFirst().isEmpty()) { throw new LocalizedResourceConfigException("warning.config.image.missing_char"); } - } else { - if (charsObj instanceof Integer integer) { - chars = List.of(new char[]{(char) integer.intValue()}); + int tempColumns = -1; + rows = charsList.size(); + for (int i = 0; i < charsList.size(); i++) { + String charString = charsList.get(i); + int[] codepoints; + if (charString.startsWith("\\u")) { + codepoints = CharacterUtils.charsToCodePoints(CharacterUtils.decodeUnicodeToChars(charString)); + } else { + codepoints = CharacterUtils.charsToCodePoints(charString.toCharArray()); + } + for (int j = 0; j < codepoints.length; j++) { + futureCodepoints.add(allocator.assignFixedId(id.asString() + ":" + i + ":" + j, codepoints[i])); + } + if (tempColumns == -1) { + tempColumns = codepoints.length; + } else if (tempColumns != codepoints.length) { + throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid"); + } + } + columns = tempColumns; + } + // 使用了具体的值 + else { + if (charsObj instanceof Integer codepoint) { + futureCodepoints.add(allocator.assignFixedId(id.asString(), codepoint)); + rows = 1; + columns = 1; } else { String character = charsObj.toString(); if (character.isEmpty()) { throw new LocalizedResourceConfigException("warning.config.image.missing_char"); } - if (character.length() == 1) { - chars = List.of(character.toCharArray()); + rows = 1; + int[] codepoints; + if (character.startsWith("\\u")) { + codepoints = CharacterUtils.charsToCodePoints(CharacterUtils.decodeUnicodeToChars(character)); } else { - if (character.startsWith("\\u")) { - chars = List.of(CharacterUtils.decodeUnicodeToChars(character)); - } else { - // ??? TODO 需要测试特殊字符集 -// if (CharacterUtils.containsCombinedCharacter(character)) { -// TranslationManager.instance().log("warning.config.image.invalid_char", path.toString(), id.toString()); -// } - chars = List.of(character.toCharArray()); + codepoints = CharacterUtils.charsToCodePoints(character.toCharArray()); + } + columns = codepoints.length; + for (int i = 0; i < codepoints.length; i++) { + futureCodepoints.add(allocator.assignFixedId(id.asString() + ":0:" + i, codepoints[i])); + } + } + } + + CompletableFutures.allOf(futureCodepoints).thenRun(() -> ResourceConfigUtils.runCatching(path, node, () -> { + int[][] codepointGrid = new int[rows][columns]; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + try { + int codepoint = futureCodepoints.get(i * columns + j).get(); + codepointGrid[i][j] = codepoint; + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof IdAllocator.IdConflictException conflict) { + throw new LocalizedResourceConfigException("warning.config.image.codepoint.conflict", + fontId.toString(), + CharacterUtils.encodeCharsToUnicode(Character.toChars(conflict.id())), + new String(Character.toChars(conflict.id())), + conflict.previousOwner() + ); + } else if (cause instanceof IdAllocator.IdExhaustedException) { + throw new LocalizedResourceConfigException("warning.config.image.codepoint.exhausted", fontId.asString()); + } + } catch (InterruptedException e) { + AbstractFontManager.this.plugin.logger().warn("Interrupted while allocating codepoint for image " + id.asString(), e); + return; } } } - } - int size = -1; - int[][] codepointGrid = new int[chars.size()][]; - for (int i = 0; i < chars.size(); ++i) { - int[] codepoints = CharacterUtils.charsToCodePoints(chars.get(i)); - for (int codepoint : codepoints) { - if (font.isCodepointInUse(codepoint)) { - BitmapImage image = font.bitmapImageByCodepoint(codepoint); - throw new LocalizedResourceConfigException("warning.config.image.codepoint_conflict", - fontKey.toString(), - CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)), - new String(Character.toChars(codepoint)), - image.id().toString()); + Object heightObj = section.get("height"); + if (heightObj == null) { + Key namespacedPath = Key.of(resourceLocation); + Path targetImagePath = pack.resourcePackFolder() + .resolve("assets") + .resolve(namespacedPath.namespace()) + .resolve("textures") + .resolve(namespacedPath.value()); + if (Files.exists(targetImagePath)) { + try (InputStream in = Files.newInputStream(targetImagePath)) { + BufferedImage image = ImageIO.read(in); + heightObj = image.getHeight() / codepointGrid.length; + } catch (IOException e) { + plugin.logger().warn("Failed to load image " + targetImagePath, e); + return; + } + } else { + throw new LocalizedResourceConfigException("warning.config.image.missing_height"); } } - if (codepoints.length == 0) { - throw new LocalizedResourceConfigException("warning.config.image.missing_char"); - } - codepointGrid[i] = codepoints; - if (size == -1) size = codepoints.length; - if (size != codepoints.length) { - throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid"); - } - } - Object heightObj = section.get("height"); - if (!resourceLocation.endsWith(".png")) resourceLocation += ".png"; + int height = ResourceConfigUtils.getAsInt(heightObj, "height"); + int ascent = ResourceConfigUtils.getAsInt(section.getOrDefault("ascent", height - 1), "ascent"); + if (height < ascent) { + throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", String.valueOf(height), String.valueOf(ascent)); + } - if (heightObj == null) { - Key namespacedPath = Key.of(resourceLocation); - Path targetImagePath = pack.resourcePackFolder() - .resolve("assets") - .resolve(namespacedPath.namespace()) - .resolve("textures") - .resolve(namespacedPath.value()); - if (Files.exists(targetImagePath)) { - try (InputStream in = Files.newInputStream(targetImagePath)) { - BufferedImage image = ImageIO.read(in); - heightObj = image.getHeight() / codepointGrid.length; - } catch (IOException e) { - plugin.logger().warn("Failed to load image " + targetImagePath, e); - return; + BitmapImage bitmapImage = new BitmapImage(id, fontId, height, ascent, resourceLocation, codepointGrid); + for (int[] y : codepointGrid) { + for (int x : y) { + font.addBitmapImage(x, bitmapImage); } - } else { - throw new LocalizedResourceConfigException("warning.config.image.missing_height"); } - } - int height = ResourceConfigUtils.getAsInt(heightObj, "height"); - int ascent = ResourceConfigUtils.getAsInt(section.getOrDefault("ascent", height - 1), "ascent"); - if (height < ascent) { - throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", String.valueOf(height), String.valueOf(ascent)); - } + AbstractFontManager.this.images.put(id, bitmapImage); - BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, resourceLocation, codepointGrid); - for (int[] y : codepointGrid) { - for (int x : y) { - font.addBitmapImage(x, bitmapImage); - } - } - - images.put(id, bitmapImage); + }, () -> GsonHelper.get().toJson(section))); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index c887cb246..a4c0164c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -437,7 +437,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } // custom model data 已被用尽,不太可能 else if (throwable instanceof IdAllocator.IdExhaustedException) { - throw new LocalizedResourceConfigException("warning.config.item.custom_model_data.exhausted"); + throw new LocalizedResourceConfigException("warning.config.item.custom_model_data.exhausted", clientBoundMaterial.asString()); } // 未知错误 else { diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java index 41694006f..f07bd5d3c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java @@ -8,7 +8,7 @@ import net.momirealms.craftengine.core.loot.function.LootFunction; import net.momirealms.craftengine.core.loot.function.LootFunctions; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.MutableInt; import net.momirealms.craftengine.core.util.RandomUtils; @@ -44,7 +44,7 @@ public class LootPool { } if (this.compositeCondition.test(context)) { Consumer> consumer = LootFunction.decorate(this.compositeFunction, lootConsumer, context); - int i = this.rolls.getInt(context) + MCUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck()); + int i = this.rolls.getInt(context) + MiscUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck()); for (int j = 0; j < i; ++j) { this.addRandomItem(createFunctionApplier(consumer, context), context); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/entry/AbstractLootEntryContainer.java b/core/src/main/java/net/momirealms/craftengine/core/loot/entry/AbstractLootEntryContainer.java index 7538d3f7f..0f860d061 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/entry/AbstractLootEntryContainer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/entry/AbstractLootEntryContainer.java @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.loot.entry; import net.momirealms.craftengine.core.loot.LootContext; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import java.util.List; import java.util.function.Predicate; @@ -14,7 +14,7 @@ public abstract class AbstractLootEntryContainer implements LootEntryContaine protected AbstractLootEntryContainer(List> conditions) { this.conditions = conditions; - this.compositeCondition = MCUtils.allOf(conditions); + this.compositeCondition = MiscUtils.allOf(conditions); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/function/AbstractLootConditionalFunction.java b/core/src/main/java/net/momirealms/craftengine/core/loot/function/AbstractLootConditionalFunction.java index 89a918222..7169d2cad 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/function/AbstractLootConditionalFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/function/AbstractLootConditionalFunction.java @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.loot.function; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.loot.LootContext; import net.momirealms.craftengine.core.plugin.context.Condition; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import java.util.List; import java.util.function.Predicate; @@ -14,7 +14,7 @@ public abstract class AbstractLootConditionalFunction implements LootFunction public AbstractLootConditionalFunction(List> predicates) { this.predicates = predicates; - this.compositePredicates = MCUtils.allOf(predicates); + this.compositePredicates = MiscUtils.allOf(predicates); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index f74448582..c0b51bace 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -320,6 +320,9 @@ public abstract class AbstractPackManager implements PackManager { if (namespace.charAt(0) == '.') { continue; } + if (!ResourceLocation.isValidNamespace(namespace)) { + namespace = "minecraft"; + } Path metaFile = path.resolve("pack.yml"); String description = null; String version = null; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 8f36b8752..357414f9a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -137,6 +137,8 @@ public class Config { protected boolean image$illegal_characters_filter$anvil; protected boolean image$illegal_characters_filter$sign; protected boolean image$illegal_characters_filter$book; + protected int image$codepoint_starting_value$default; + protected Map image$codepoint_starting_value$overrides; protected boolean network$intercept_packets$system_chat; protected boolean network$intercept_packets$tab_list; @@ -257,6 +259,7 @@ public class Config { forcedLocale = TranslationManager.parseLocale(config.getString("forced-locale", "")); } + @SuppressWarnings("DuplicatedCode") public void loadFullSettings() { YamlDocument config = settings(); forcedLocale = TranslationManager.parseLocale(config.getString("forced-locale", "")); @@ -429,7 +432,7 @@ public class Config { block$extended_interaction_range = Math.max(config.getDouble("block.predict-breaking.extended-interaction-range", 0.5), 0.0); block$chunk_relighter = config.getBoolean("block.chunk-relighter", true); if (firstTime) { - block$serverside_blocks = config.getInt("block.serverside-blocks", 2000); + block$serverside_blocks = Math.min(config.getInt("block.serverside-blocks", 2000), 10_0000); if (block$serverside_blocks < 0) block$serverside_blocks = 0; } @@ -445,6 +448,22 @@ public class Config { image$illegal_characters_filter$chat = config.getBoolean("image.illegal-characters-filter.chat", true); image$illegal_characters_filter$command = config.getBoolean("image.illegal-characters-filter.command", true); image$illegal_characters_filter$sign = config.getBoolean("image.illegal-characters-filter.sign", true); + + image$codepoint_starting_value$default = config.getInt("image.codepoint-starting-value.default", 0); + Section codepointOverridesSection = config.getSection("image.codepoint-starting-value.overrides"); + if (codepointOverridesSection != null) { + Map codepointOverrides = new HashMap<>(); + for (Map.Entry entry : codepointOverridesSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof String s) { + codepointOverrides.put(Key.of(entry.getKey()), Integer.parseInt(s)); + } else if (entry.getValue() instanceof Integer i) { + codepointOverrides.put(Key.of(entry.getKey()), i); + } + } + image$codepoint_starting_value$overrides = codepointOverrides; + } else { + image$codepoint_starting_value$overrides = Map.of(); + } network$intercept_packets$system_chat = config.getBoolean("network.intercept-packets.system-chat", true); network$intercept_packets$tab_list = config.getBoolean("network.intercept-packets.tab-list", true); @@ -780,6 +799,13 @@ public class Config { return instance.item$custom_model_data_starting_value$default; } + public static int codepointStartingValue(Key font) { + if (instance.image$codepoint_starting_value$overrides.containsKey(font)) { + return instance.image$codepoint_starting_value$overrides.get(font); + } + return instance.image$codepoint_starting_value$default; + } + public static int compressionMethod() { int id = instance.chunk_system$compression_method; if (id <= 0 || id > CompressionMethod.METHOD_COUNT) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AllOfCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AllOfCondition.java index a1776b43d..761f07dc7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AllOfCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AllOfCondition.java @@ -4,7 +4,6 @@ import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -18,7 +17,7 @@ public class AllOfCondition implements Condition { protected final Predicate condition; public AllOfCondition(List> conditions) { - this.condition = MCUtils.allOf(conditions); + this.condition = MiscUtils.allOf(conditions); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AnyOfCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AnyOfCondition.java index e01568f5b..278bb9421 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AnyOfCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/AnyOfCondition.java @@ -4,7 +4,6 @@ import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -18,7 +17,7 @@ public class AnyOfCondition implements Condition { protected final Predicate condition; public AnyOfCondition(List> conditions) { - this.condition = MCUtils.anyOf(conditions); + this.condition = MiscUtils.anyOf(conditions); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java index 44d22ba1a..4c20d65a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java @@ -2,7 +2,6 @@ package net.momirealms.craftengine.core.plugin.context.function; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; -import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.util.MiscUtils; import java.util.ArrayList; @@ -16,7 +15,7 @@ public abstract class AbstractConditionalFunction implement public AbstractConditionalFunction(List> predicates) { this.predicates = predicates; - this.compositePredicates = MCUtils.allOf(predicates); + this.compositePredicates = MiscUtils.allOf(predicates); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java index 025cda880..268f23984 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import java.util.List; import java.util.Map; @@ -28,7 +28,7 @@ public class BreakBlockFunction extends AbstractConditional @Override public void runInternal(CTX ctx) { Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); - optionalPlayer.ifPresent(player -> player.breakBlock(MCUtils.fastFloor(x.getDouble(ctx)), MCUtils.fastFloor(y.getDouble(ctx)), MCUtils.fastFloor(z.getDouble(ctx)))); + optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.fastFloor(x.getDouble(ctx)), MiscUtils.fastFloor(y.getDouble(ctx)), MiscUtils.fastFloor(z.getDouble(ctx)))); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java index 15167ef06..3068766eb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java @@ -10,7 +10,7 @@ import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.LazyReference; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; @@ -40,7 +40,7 @@ public class PlaceBlockFunction extends AbstractConditional Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - world.setBlockAt(MCUtils.fastFloor(this.x.getDouble(ctx)), MCUtils.fastFloor(this.y.getDouble(ctx)), MCUtils.fastFloor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); + world.setBlockAt(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java index 801e13667..020c44fe7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; @@ -48,7 +48,7 @@ public class RunFunction extends AbstractConditionalFunctio for (Function function : functions) { function.run(ctx); } - }, delay, pos.world().platformWorld(), MCUtils.fastFloor(pos.x()) >> 4, MCUtils.fastFloor(pos.z()) >> 4); + }, delay, pos.world().platformWorld(), MiscUtils.fastFloor(pos.x()) >> 4, MiscUtils.fastFloor(pos.z()) >> 4); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java index 77de046d5..4554a46ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java @@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.plugin.context.number; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; @@ -54,7 +54,7 @@ public class GaussianNumberProvider implements NumberProvider { } attempts++; } - return MCUtils.clamp(this.mean, this.min, this.max); + return MiscUtils.clamp(this.mean, this.min, this.max); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java index 465b49809..27529a7e5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.plugin.context.parameter; import net.momirealms.craftengine.core.entity.Entity; import net.momirealms.craftengine.core.plugin.context.ChainParameterProvider; import net.momirealms.craftengine.core.plugin.context.ContextKey; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import java.util.HashMap; import java.util.Map; @@ -19,9 +19,9 @@ public class EntityParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MCUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MCUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MCUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java index 25c3df127..ee6033ccd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.context.ChainParameterProvider; import net.momirealms.craftengine.core.plugin.context.ContextKey; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import java.util.HashMap; import java.util.Map; @@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MCUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MCUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MCUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel); CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java index b7e2b26b6..c646658fb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java @@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.plugin.context.parameter; import net.momirealms.craftengine.core.plugin.context.ChainParameterProvider; import net.momirealms.craftengine.core.plugin.context.ContextKey; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.WorldPosition; import java.util.HashMap; @@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider MCUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MCUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MCUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/selector/AllPlayerSelector.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/selector/AllPlayerSelector.java index edd33b388..b8686e3db 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/selector/AllPlayerSelector.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/selector/AllPlayerSelector.java @@ -8,7 +8,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import java.util.ArrayList; import java.util.Arrays; @@ -21,7 +21,7 @@ public class AllPlayerSelector implements PlayerSelector predicate; public AllPlayerSelector(List> predicates) { - this.predicate = MCUtils.allOf(predicates); + this.predicate = MiscUtils.allOf(predicates); } public AllPlayerSelector() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java index 0589a2df5..a75ebda9d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/codec/NetworkCodecs.java @@ -6,7 +6,7 @@ import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufUtil; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; @@ -47,7 +47,7 @@ public interface NetworkCodecs { } }; - NetworkCodec ROTATION_BYTE = BYTE.map(MCUtils::unpackDegrees, MCUtils::packDegrees); + NetworkCodec ROTATION_BYTE = BYTE.map(MiscUtils::unpackDegrees, MiscUtils::packDegrees); NetworkCodec SHORT = new NetworkCodec<>() { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java b/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java index 1c24d56fc..974d1a01d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/ConstantBoundRegistry.java @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.registry; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceKey; import java.util.IdentityHashMap; @@ -11,7 +11,7 @@ import java.util.Map; import java.util.Objects; public class ConstantBoundRegistry extends AbstractMappedRegistry { - protected final Reference2IntMap toId = MCUtils.make(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); + protected final Reference2IntMap toId = MiscUtils.init(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); protected final Map> byValue; public ConstantBoundRegistry(ResourceKey> key, int expectedSize) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java index 90abef1e1..562e59b81 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java @@ -19,8 +19,7 @@ public class CharacterUtils { for (int i = 0, j = 0; j < count; i += 6, j++) { String hex = unicodeString.substring(i + 2, i + 6); try { - int codePoint = Integer.parseInt(hex, 16); - chars[j] = (char) codePoint; + chars[j] = (char) Integer.parseInt(hex, 16); } catch (NumberFormatException e) { throw new LocalizedResourceConfigException("warning.config.image.invalid_hex_value", e, hex); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java index 6884afedd..5f37d42b0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java @@ -38,7 +38,7 @@ public class Color { } public static Color fromVector3f(Vector3f vec) { - return new Color(0 << 24 /*不可省略*/ | MCUtils.fastFloor(vec.x) << 16 | MCUtils.fastFloor(vec.y) << 8 | MCUtils.fastFloor(vec.z)); + return new Color(0 << 24 /*不可省略*/ | MiscUtils.fastFloor(vec.x) << 16 | MiscUtils.fastFloor(vec.y) << 8 | MiscUtils.fastFloor(vec.z)); } public static int opaque(int color) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java b/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java index 636d84a0b..8e8e44da1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java @@ -131,10 +131,10 @@ public enum Direction { public static Direction[] orderedByNearest(AbstractEntity entity) { float f = entity.xRot() * ((float)Math.PI / 180F); float f1 = -entity.yRot() * ((float)Math.PI / 180F); - float sin = MCUtils.sin(f); - float cos = MCUtils.cos(f); - float sin1 = MCUtils.sin(f1); - float cos1 = MCUtils.cos(f1); + float sin = MiscUtils.sin(f); + float cos = MiscUtils.cos(f); + float sin1 = MiscUtils.sin(f1); + float cos1 = MiscUtils.cos(f1); boolean flag = sin1 > 0.0F; boolean flag1 = sin < 0.0F; boolean flag2 = cos1 > 0.0F; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java index 62c582ee7..b4b339108 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java @@ -585,7 +585,7 @@ public class FriendlyByteBuf extends ByteBuf { } public BitSet readFixedBitSet(int size) { - byte[] byteArray = new byte[MCUtils.positiveCeilDiv(size, 8)]; + byte[] byteArray = new byte[MiscUtils.positiveCeilDiv(size, 8)]; this.readBytes(byteArray); return BitSet.valueOf(byteArray); } @@ -595,7 +595,7 @@ public class FriendlyByteBuf extends ByteBuf { throw new EncoderException("BitSet length exceeds expected size (" + bitSet.length() + " > " + size + ")"); } byte[] byteArray = bitSet.toByteArray(); - this.writeBytes(Arrays.copyOf(byteArray, MCUtils.positiveCeilDiv(size, 8))); + this.writeBytes(Arrays.copyOf(byteArray, MiscUtils.positiveCeilDiv(size, 8))); } @SuppressWarnings("unchecked") @@ -631,11 +631,11 @@ public class FriendlyByteBuf extends ByteBuf { double d = Double.isNaN(vec3.x) ? (double) 0.0F : Math.clamp(vec3.x, -1.7179869183E10, 1.7179869183E10); double d1 = Double.isNaN(vec3.y) ? (double) 0.0F : Math.clamp(vec3.y, -1.7179869183E10, 1.7179869183E10); double d2 = Double.isNaN(vec3.z) ? (double) 0.0F : Math.clamp(vec3.z, -1.7179869183E10, 1.7179869183E10); - double max = MCUtils.absMax(d, MCUtils.absMax(d1, d2)); + double max = MiscUtils.absMax(d, MiscUtils.absMax(d1, d2)); if (max < 3.051944088384301E-5) { this.writeByte(0); } else { - long l = MCUtils.ceilLong(max); + long l = MiscUtils.ceilLong(max); boolean flag = (l & 3L) != l; long l1 = flag ? l & 3L | 4L : l; long l2 = (Math.round(((d / (double) l) * (double) 0.5F + (double) 0.5F) * (double) 32766.0F)) << 3; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java b/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java index 76cbad82e..5eeb3c669 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Int2ObjectBiMap.java @@ -121,7 +121,7 @@ public class Int2ObjectBiMap implements IndexedIterable { } private int getIdealIndex(@Nullable K value) { - return (MCUtils.idealHash(System.identityHashCode(value)) & Integer.MAX_VALUE) % this.values.length; + return (MiscUtils.idealHash(System.identityHashCode(value)) & Integer.MAX_VALUE) % this.values.length; } private int findIndex(@Nullable K value, int id) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java deleted file mode 100644 index c78fc05d1..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java +++ /dev/null @@ -1,294 +0,0 @@ -package net.momirealms.craftengine.core.util; - -import com.google.common.collect.Iterators; -import org.jetbrains.annotations.Nullable; - -import java.util.Iterator; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Predicate; - -public class MCUtils { - - private MCUtils() {} - - public static final float DEG_TO_RAD = ((float)Math.PI / 180F); - - private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; - private static final float[] SIN = make(new float[65536], (sineTable) -> { - for(int i = 0; i < sineTable.length; ++i) { - sineTable[i] = (float) Math.sin((double) i * Math.PI * 2.0 / 65536.0); - } - }); - - public static int fastFloor(double value) { - int truncated = (int) value; - return value < (double) truncated ? truncated - 1 : truncated; - } - - public static int fastFloor(float value) { - int truncated = (int) value; - return value < (double) truncated ? truncated - 1 : truncated; - } - - public static int lerpDiscrete(float delta, int start, int end) { - int i = end - start; - return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); - } - - public static int murmurHash3Mixer(int value) { - value ^= value >>> 16; - value *= -2048144789; - value ^= value >>> 13; - value *= -1028477387; - return value ^ value >>> 16; - } - - public static int ceil(double value) { - int i = (int)value; - return value > (double)i ? i + 1 : i; - } - - public static boolean isPowerOfTwo(int value) { - return value != 0 && (value & value - 1) == 0; - } - - public static int smallestEncompassingPowerOfTwo(int value) { - int i = value - 1; - i |= i >> 1; - i |= i >> 2; - i |= i >> 4; - i |= i >> 8; - i |= i >> 16; - return i + 1; - } - - public static int ceilLog2(int value) { - value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value); - return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int)((long)value * 125613361L >> 27) & 31]; - } - - public static int positiveCeilDiv(int a, int b) { - return -Math.floorDiv(-a, b); - } - - public static int idealHash(int value) { - value ^= value >>> 16; - value *= -2048144789; - value ^= value >>> 13; - value *= -1028477387; - value ^= value >>> 16; - return value; - } - - public static long getUnsignedDivisorMagic(final long divisor, final int bits) { - return ((1L << bits) - 1L) / divisor + 1L; - } - - public static T make(T object, Consumer initializer) { - initializer.accept(object); - return object; - } - - public static Predicate allOf() { - return o -> true; - } - - @SuppressWarnings("unchecked") - public static Predicate allOf(Predicate a) { - return (Predicate) a; - } - - public static Predicate allOf(Predicate a, Predicate b) { - return o -> a.test(o) && b.test(o); - } - - public static Predicate allOf(Predicate a, Predicate b, Predicate c) { - return o -> a.test(o) && b.test(o) && c.test(o); - } - - public static Predicate allOf(Predicate a, Predicate b, Predicate c, Predicate d) { - return o -> a.test(o) && b.test(o) && c.test(o) && d.test(o); - } - - public static Predicate allOf(Predicate a, Predicate b, Predicate c, Predicate d, Predicate e) { - return o -> a.test(o) && b.test(o) && c.test(o) && d.test(o) && e.test(o); - } - - @SafeVarargs - public static Predicate allOf(Predicate... predicates) { - return o -> { - for (Predicate predicate : predicates) { - if (!predicate.test(o)) { - return false; - } - } - return true; - }; - } - - public static Predicate allOf(List> predicates) { - return switch (predicates.size()) { - case 0 -> allOf(); - case 1 -> allOf((Predicate) predicates.get(0)); - case 2 -> allOf((Predicate) predicates.get(0), (Predicate) predicates.get(1)); - case 3 -> allOf((Predicate) predicates.get(0), (Predicate) predicates.get(1), (Predicate) predicates.get(2)); - case 4 -> allOf( - (Predicate) predicates.get(0), - (Predicate) predicates.get(1), - (Predicate) predicates.get(2), - (Predicate) predicates.get(3) - ); - case 5 -> allOf( - (Predicate) predicates.get(0), - (Predicate) predicates.get(1), - (Predicate) predicates.get(2), - (Predicate) predicates.get(3), - (Predicate) predicates.get(4) - ); - default -> { - @SuppressWarnings("unchecked") - Predicate[] predicates2 = predicates.toArray(Predicate[]::new); - yield allOf(predicates2); - } - }; - } - - public static Predicate anyOf() { - return o -> false; - } - - @SuppressWarnings("unchecked") - public static Predicate anyOf(Predicate a) { - return (Predicate) a; - } - - public static Predicate anyOf(Predicate a, Predicate b) { - return o -> a.test(o) || b.test(o); - } - - public static Predicate anyOf(Predicate a, Predicate b, Predicate c) { - return o -> a.test(o) || b.test(o) || c.test(o); - } - - public static Predicate anyOf(Predicate a, Predicate b, Predicate c, Predicate d) { - return o -> a.test(o) || b.test(o) || c.test(o) || d.test(o); - } - - public static Predicate anyOf(Predicate a, Predicate b, Predicate c, Predicate d, Predicate e) { - return o -> a.test(o) || b.test(o) || c.test(o) || d.test(o) || e.test(o); - } - - @SafeVarargs - public static Predicate anyOf(Predicate... predicates) { - return o -> { - for (Predicate predicate : predicates) { - if (predicate.test(o)) { - return true; - } - } - return false; - }; - } - - public static Predicate anyOf(List> predicates) { - return switch (predicates.size()) { - case 0 -> anyOf(); - case 1 -> anyOf((Predicate) predicates.get(0)); - case 2 -> anyOf((Predicate) predicates.get(0), (Predicate) predicates.get(1)); - case 3 -> anyOf((Predicate) predicates.get(0), (Predicate) predicates.get(1), (Predicate) predicates.get(2)); - case 4 -> anyOf( - (Predicate) predicates.get(0), - (Predicate) predicates.get(1), - (Predicate) predicates.get(2), - (Predicate) predicates.get(3) - ); - case 5 -> anyOf( - (Predicate) predicates.get(0), - (Predicate) predicates.get(1), - (Predicate) predicates.get(2), - (Predicate) predicates.get(3), - (Predicate) predicates.get(4) - ); - default -> { - @SuppressWarnings("unchecked") - Predicate[] predicates2 = predicates.toArray(Predicate[]::new); - yield anyOf(predicates2); - } - }; - } - - public static T findPreviousInIterable(Iterable iterable, @Nullable T object) { - Iterator iterator = iterable.iterator(); - T previous = null; - while (iterator.hasNext()) { - T current = iterator.next(); - if (current == object) { - if (previous == null) { - previous = iterator.hasNext() ? Iterators.getLast(iterator) : object; - } - break; - } - previous = current; - } - return previous; - } - - public static float sin(float value) { - return SIN[(int) (value * 10430.378F) & '\uffff']; - } - - public static float cos(float value) { - return SIN[(int)(value * 10430.378F + 16384.0F) & '\uffff']; - } - - public static float sqrt(float value) { - return (float)Math.sqrt(value); - } - - public static T findNextInIterable(Iterable iterable, @Nullable T object) { - Iterator iterator = iterable.iterator(); - T next = iterator.next(); - if (object != null) { - T current = next; - while (current != object) { - if (iterator.hasNext()) { - current = iterator.next(); - } - } - if (iterator.hasNext()) { - return iterator.next(); - } - } - return next; - } - - public static byte packDegrees(float degrees) { - return (byte) fastFloor(degrees * 256.0F / 360.0F); - } - - public static float unpackDegrees(byte degrees) { - return (float) (degrees * 360) / 256.0F; - } - - public static int clamp(int value, int min, int max) { - return Math.min(Math.max(value, min), max); - } - - public static float clamp(float value, float min, float max) { - return value < min ? min : Math.min(value, max); - } - - public static double clamp(double value, double min, double max) { - return value < min ? min : Math.min(value, max); - } - - public static double absMax(double x, double y) { - return Math.max(Math.abs(x), Math.abs(y)); - } - - public static long ceilLong(double value) { - long l = (long)value; - return value > (double)l ? l + 1L : l; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 8e5a2864a..2e28001c2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -1,10 +1,302 @@ package net.momirealms.craftengine.core.util; +import com.google.common.collect.Iterators; +import org.jetbrains.annotations.Nullable; + import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; public class MiscUtils { + private MiscUtils() { + } - private MiscUtils() {} + public static final float DEG_TO_RAD = ((float) Math.PI / 180F); + + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + private static final float[] SIN = init(new float[65536], (sineTable) -> { + for (int i = 0; i < sineTable.length; ++i) { + sineTable[i] = (float) Math.sin((double) i * Math.PI * 2.0 / 65536.0); + } + }); + + public static int fastFloor(double value) { + int truncated = (int) value; + return value < (double) truncated ? truncated - 1 : truncated; + } + + public static int fastFloor(float value) { + int truncated = (int) value; + return value < (double) truncated ? truncated - 1 : truncated; + } + + public static int lerpDiscrete(float delta, int start, int end) { + int i = end - start; + return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); + } + + public static int murmurHash3Mixer(int value) { + value ^= value >>> 16; + value *= -2048144789; + value ^= value >>> 13; + value *= -1028477387; + return value ^ value >>> 16; + } + + public static int ceil(double value) { + int i = (int) value; + return value > (double) i ? i + 1 : i; + } + + public static boolean isPowerOfTwo(int value) { + return value != 0 && (value & value - 1) == 0; + } + + public static int smallestEncompassingPowerOfTwo(int value) { + int i = value - 1; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return i + 1; + } + + public static int ceilLog2(int value) { + value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value); + return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int) ((long) value * 125613361L >> 27) & 31]; + } + + public static int positiveCeilDiv(int a, int b) { + return -Math.floorDiv(-a, b); + } + + public static int idealHash(int value) { + value ^= value >>> 16; + value *= -2048144789; + value ^= value >>> 13; + value *= -1028477387; + value ^= value >>> 16; + return value; + } + + public static long getUnsignedDivisorMagic(final long divisor, final int bits) { + return ((1L << bits) - 1L) / divisor + 1L; + } + + public static T init(T object, Consumer initializer) { + initializer.accept(object); + return object; + } + + public static T make(final T object, Function initializer) { + return initializer.apply(object); + } + + public static Predicate allOf() { + return o -> true; + } + + @SuppressWarnings("unchecked") + public static Predicate allOf(Predicate a) { + return (Predicate) a; + } + + public static Predicate allOf(Predicate a, Predicate b) { + return o -> a.test(o) && b.test(o); + } + + public static Predicate allOf(Predicate a, Predicate b, Predicate c) { + return o -> a.test(o) && b.test(o) && c.test(o); + } + + public static Predicate allOf(Predicate a, Predicate b, Predicate c, Predicate d) { + return o -> a.test(o) && b.test(o) && c.test(o) && d.test(o); + } + + public static Predicate allOf(Predicate a, Predicate b, Predicate c, Predicate d, Predicate e) { + return o -> a.test(o) && b.test(o) && c.test(o) && d.test(o) && e.test(o); + } + + @SafeVarargs + public static Predicate allOf(Predicate... predicates) { + return o -> { + for (Predicate predicate : predicates) { + if (!predicate.test(o)) { + return false; + } + } + return true; + }; + } + + public static Predicate allOf(List> predicates) { + return switch (predicates.size()) { + case 0 -> allOf(); + case 1 -> allOf((Predicate) predicates.get(0)); + case 2 -> allOf((Predicate) predicates.get(0), (Predicate) predicates.get(1)); + case 3 -> + allOf((Predicate) predicates.get(0), (Predicate) predicates.get(1), (Predicate) predicates.get(2)); + case 4 -> allOf( + (Predicate) predicates.get(0), + (Predicate) predicates.get(1), + (Predicate) predicates.get(2), + (Predicate) predicates.get(3) + ); + case 5 -> allOf( + (Predicate) predicates.get(0), + (Predicate) predicates.get(1), + (Predicate) predicates.get(2), + (Predicate) predicates.get(3), + (Predicate) predicates.get(4) + ); + default -> { + @SuppressWarnings("unchecked") + Predicate[] predicates2 = predicates.toArray(Predicate[]::new); + yield allOf(predicates2); + } + }; + } + + public static Predicate anyOf() { + return o -> false; + } + + @SuppressWarnings("unchecked") + public static Predicate anyOf(Predicate a) { + return (Predicate) a; + } + + public static Predicate anyOf(Predicate a, Predicate b) { + return o -> a.test(o) || b.test(o); + } + + public static Predicate anyOf(Predicate a, Predicate b, Predicate c) { + return o -> a.test(o) || b.test(o) || c.test(o); + } + + public static Predicate anyOf(Predicate a, Predicate b, Predicate c, Predicate d) { + return o -> a.test(o) || b.test(o) || c.test(o) || d.test(o); + } + + public static Predicate anyOf(Predicate a, Predicate b, Predicate c, Predicate d, Predicate e) { + return o -> a.test(o) || b.test(o) || c.test(o) || d.test(o) || e.test(o); + } + + @SafeVarargs + public static Predicate anyOf(Predicate... predicates) { + return o -> { + for (Predicate predicate : predicates) { + if (predicate.test(o)) { + return true; + } + } + return false; + }; + } + + public static Predicate anyOf(List> predicates) { + return switch (predicates.size()) { + case 0 -> anyOf(); + case 1 -> anyOf((Predicate) predicates.get(0)); + case 2 -> anyOf((Predicate) predicates.get(0), (Predicate) predicates.get(1)); + case 3 -> + anyOf((Predicate) predicates.get(0), (Predicate) predicates.get(1), (Predicate) predicates.get(2)); + case 4 -> anyOf( + (Predicate) predicates.get(0), + (Predicate) predicates.get(1), + (Predicate) predicates.get(2), + (Predicate) predicates.get(3) + ); + case 5 -> anyOf( + (Predicate) predicates.get(0), + (Predicate) predicates.get(1), + (Predicate) predicates.get(2), + (Predicate) predicates.get(3), + (Predicate) predicates.get(4) + ); + default -> { + @SuppressWarnings("unchecked") + Predicate[] predicates2 = predicates.toArray(Predicate[]::new); + yield anyOf(predicates2); + } + }; + } + + public static T findPreviousInIterable(Iterable iterable, @Nullable T object) { + Iterator iterator = iterable.iterator(); + T previous = null; + while (iterator.hasNext()) { + T current = iterator.next(); + if (current == object) { + if (previous == null) { + previous = iterator.hasNext() ? Iterators.getLast(iterator) : object; + } + break; + } + previous = current; + } + return previous; + } + + public static float sin(float value) { + return SIN[(int) (value * 10430.378F) & '\uffff']; + } + + public static float cos(float value) { + return SIN[(int) (value * 10430.378F + 16384.0F) & '\uffff']; + } + + public static float sqrt(float value) { + return (float) Math.sqrt(value); + } + + public static T findNextInIterable(Iterable iterable, @Nullable T object) { + Iterator iterator = iterable.iterator(); + T next = iterator.next(); + if (object != null) { + T current = next; + while (current != object) { + if (iterator.hasNext()) { + current = iterator.next(); + } + } + if (iterator.hasNext()) { + return iterator.next(); + } + } + return next; + } + + public static byte packDegrees(float degrees) { + return (byte) fastFloor(degrees * 256.0F / 360.0F); + } + + public static float unpackDegrees(byte degrees) { + return (float) (degrees * 360) / 256.0F; + } + + public static int clamp(int value, int min, int max) { + return Math.min(Math.max(value, min), max); + } + + public static float clamp(float value, float min, float max) { + return value < min ? min : Math.min(value, max); + } + + public static double clamp(double value, double min, double max) { + return value < min ? min : Math.min(value, max); + } + + public static double absMax(double x, double y) { + return Math.max(Math.abs(x), Math.abs(y)); + } + + public static long ceilLong(double value) { + long l = (long) value; + return value > (double) l ? l + 1L : l; + } @SuppressWarnings("unchecked") public static Map castToMap(Object obj, boolean allowNull) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/QuaternionUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/QuaternionUtils.java index 96c2081b7..98eaeb41d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/QuaternionUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/QuaternionUtils.java @@ -21,12 +21,12 @@ public class QuaternionUtils { } public static Quaternionf toQuaternionf(float yaw, float pitch, float roll) { - float cy = MCUtils.cos(yaw * 0.5f); - float sy = MCUtils.sin(yaw * 0.5f); - float cp = MCUtils.cos(pitch * 0.5f); - float sp = MCUtils.sin(pitch * 0.5f); - float cr = MCUtils.cos(roll * 0.5f); - float sr = MCUtils.sin(roll * 0.5f); + float cy = MiscUtils.cos(yaw * 0.5f); + float sy = MiscUtils.sin(yaw * 0.5f); + float cp = MiscUtils.cos(pitch * 0.5f); + float sp = MiscUtils.sin(pitch * 0.5f); + float cr = MiscUtils.cos(roll * 0.5f); + float sr = MiscUtils.sin(roll * 0.5f); float w = cr * cp * cy + sr * sp * sy; float x = sr * cp * cy - cr * sp * sy; float y = cr * sp * cy + sr * cp * sy; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 88fe4b680..86905697d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; public class BlockPos extends Vec3i { public static final BlockPos ZERO = new BlockPos(0, 0, 0); @@ -23,7 +23,7 @@ public class BlockPos extends Vec3i { } public static BlockPos fromVec3d(Vec3d vec) { - return new BlockPos(MCUtils.fastFloor(vec.x), MCUtils.fastFloor(vec.y), MCUtils.fastFloor(vec.z)); + return new BlockPos(MiscUtils.fastFloor(vec.x), MiscUtils.fastFloor(vec.y), MiscUtils.fastFloor(vec.z)); } public static BlockPos of(long packedPos) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java index 36c0f2e96..6944337ed 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; public class EntityHitResult { private final Direction direction; @@ -23,9 +23,9 @@ public class EntityHitResult { } private BlockPos getBlockPos() { - int x = MCUtils.fastFloor(this.position.x); - int y = MCUtils.fastFloor(this.position.y); - int z = MCUtils.fastFloor(this.position.z); + int x = MiscUtils.fastFloor(this.position.x); + int y = MiscUtils.fastFloor(this.position.y); + int z = MiscUtils.fastFloor(this.position.z); if (this.direction == Direction.UP) { if (this.position.y % 1 == 0) { y--; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java index 83ac823ba..e24e99037 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.core.world; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; public class Vec3d implements Position { public static final Vec3d ZERO = new Vec3d(0, 0, 0); @@ -15,7 +15,7 @@ public class Vec3d implements Position { } public Vec3d toCenter() { - return new Vec3d(MCUtils.fastFloor(x) + 0.5, MCUtils.fastFloor(y) + 0.5, MCUtils.fastFloor(z) + 0.5); + return new Vec3d(MiscUtils.fastFloor(x) + 0.5, MiscUtils.fastFloor(y) + 0.5, MiscUtils.fastFloor(z) + 0.5); } public Vec3d add(Vec3d vec) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java index 211addb11..9ce501846 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/PalettedContainer.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.IndexedIterable; -import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.VersionHelper; import org.jetbrains.annotations.Nullable; @@ -269,7 +269,7 @@ public class PalettedContainer implements PaletteResizeListener, ReadableC case 0 -> new DataProvider<>(SINGULAR, bits); case 1, 2, 3, 4 -> new DataProvider<>(ARRAY, 4); case 5, 6, 7, 8 -> new DataProvider<>(BI_MAP, bits); - default -> new DataProvider<>(PaletteProvider.ID_LIST, MCUtils.ceilLog2(idList.size())); + default -> new DataProvider<>(PaletteProvider.ID_LIST, MiscUtils.ceilLog2(idList.size())); }; } }; @@ -278,7 +278,7 @@ public class PalettedContainer implements PaletteResizeListener, ReadableC return switch (bits) { case 0 -> new DataProvider<>(SINGULAR, bits); case 1, 2, 3 -> new DataProvider<>(ARRAY, bits); - default -> new DataProvider<>(PaletteProvider.ID_LIST, MCUtils.ceilLog2(idList.size())); + default -> new DataProvider<>(PaletteProvider.ID_LIST, MiscUtils.ceilLog2(idList.size())); }; } }; @@ -300,7 +300,7 @@ public class PalettedContainer implements PaletteResizeListener, ReadableC public abstract DataProvider createDataProvider(IndexedIterable idList, int bits); int getBits(IndexedIterable idList, int size) { - int i = MCUtils.ceilLog2(size); + int i = MiscUtils.ceilLog2(size); DataProvider dataProvider = this.createDataProvider(idList, i); return dataProvider.factory() == ID_LIST ? i : dataProvider.bits(); } diff --git a/gradle.properties b/gradle.properties index 94725baf0..464ab90be 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.7 +project_version=0.0.63.8 config_version=47 -lang_version=31 +lang_version=32 project_group=net.momirealms latest_supported_version=1.21.8 @@ -15,8 +15,8 @@ supported_languages=en,zh_cn,zh_tw,es,tr,de,ru_ru paper_version=1.21.8 jetbrains_annotations_version=26.0.2 slf4j_version=2.0.17 -log4j_version=2.24.3 -gson_version=2.11.0 +log4j_version=2.25.2 +gson_version=2.13.2 asm_version=9.8 asm_commons_version=9.8 jar_relocator_version=1.7 @@ -29,20 +29,20 @@ cloud_paper_version=2.0.0-beta.11 cloud_minecraft_extras_version=2.0.0-beta.11 boosted_yaml_version=1.3.7 bstats_version=3.1.0 -caffeine_version=3.2.0 +caffeine_version=3.2.2 placeholder_api_version=2.11.6 vault_version=1.7 -guava_version=33.4.6-jre +guava_version=33.5.0-jre lz4_version=1.8.0 geantyref_version=1.3.16 -zstd_version=1.5.7-2 -commons_io_version=2.18.0 +zstd_version=1.5.7-4 +commons_io_version=2.20.0 commons_imaging_version=1.0.0-alpha6 -commons_lang3_version=3.17.0 +commons_lang3_version=3.19.0 sparrow_nbt_version=0.9.4 sparrow_util_version=0.51 -fastutil_version=8.5.15 -netty_version=4.1.124.Final +fastutil_version=8.5.16 +netty_version=4.1.127.Final joml_version=1.10.8 datafixerupper_version=8.0.16 mojang_brigadier_version=1.0.18 @@ -53,9 +53,9 @@ anti_grief_version=0.20 nms_helper_version=1.0.97 evalex_version=3.5.0 reactive_streams_version=1.0.4 -amazon_awssdk_version=2.33.1 +amazon_awssdk_version=2.34.5 amazon_awssdk_eventstream_version=1.0.1 -jimfs_version=1.3.0 +jimfs_version=1.3.1 authlib_version=7.0.60 concurrent_util_version=0.0.3 From c7df9ced635d885c09041f940e9f2af54efe4ef3 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 00:43:59 +0800 Subject: [PATCH 212/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- common-files/src/main/resources/config.yml | 3 ++- common-files/src/main/resources/translations/de.yml | 2 +- common-files/src/main/resources/translations/en.yml | 2 +- .../src/main/resources/translations/ru_ru.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 2 +- gradle.properties | 12 ++++++------ 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index daf38199a..26b55747f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ repositories { ``` ```kotlin dependencies { - compileOnly("net.momirealms:craft-engine-core:0.0.63") - compileOnly("net.momirealms:craft-engine-bukkit:0.0.63") + compileOnly("net.momirealms:craft-engine-core:0.0.64") + compileOnly("net.momirealms:craft-engine-bukkit:0.0.64") } ``` \ No newline at end of file diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index a96c789c1..1ba1c55a3 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -56,7 +56,8 @@ resource-pack: validation: enable: true # [Premium Exclusive] - # Fix images that are not within the texture atlas. + # Fix images that are not within the texture atlas. It is unreasonable to always rely on plugins to fix your mistakes. + # You should strive to make your resource pack more standardized after gaining some experience with resource packs. fix-atlas: true # Define the name of the overlay folders overlay-format: "ce_overlay_{version}" diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 3d10d877e..2b9ac8bc2 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -407,7 +407,7 @@ warning.config.selector.invalid_target: "Problem in Datei gefund warning.config.resource_pack.item_model.already_exist: "Generierung des Item-Models für '' fehlgeschlagen, da die Datei '' bereits existiert." warning.config.resource_pack.model.generation.already_exist: "Generierung des Models fehlgeschlagen, da die Model-Datei '' bereits existiert." warning.config.resource_pack.generation.missing_font_texture: "Beim Font '' fehlt die erforderliche Textur: ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "Textur '' ist nicht im Atlas aufgeführt. Du musst den Texturpfad zum Atlas hinzufügen oder die 'obfuscation'/'fix-atlas'-Option in der config.yml aktivieren." +warning.config.resource_pack.generation.texture_not_in_atlas: "Textur '' ist nicht im Atlas aufgeführt. Du musst den Texturpfad zum Atlas hinzufügen oder die 'fix-atlas'-Option in der config.yml aktivieren." warning.config.resource_pack.generation.missing_model_texture: "Beim Model '' fehlt die Textur ''" warning.config.resource_pack.generation.missing_item_model: "Beim Item '' fehlt die Model-Datei: ''" warning.config.resource_pack.generation.missing_block_model: "Beim Block-State '' fehlt die Model-Datei: ''" diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index a9cf1ec13..655b1c690 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -463,7 +463,7 @@ warning.config.resource_pack.generation.missing_item_model: "Item 'Block state '' is missing model file: ''" warning.config.resource_pack.generation.missing_parent_model: "Model '' cannot find parent model: ''" warning.config.resource_pack.generation.missing_equipment_texture: "Equipment '' is missing texture ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "Texture '' is not listed in the atlas. You need to add the texture path to the atlas or enable 'obfuscation' or 'fix-atlas' option in config.yml." +warning.config.resource_pack.generation.texture_not_in_atlas: "Texture '' is not listed in the atlas. You need to add the texture path to the atlas or enable 'fix-atlas' option in config.yml." warning.config.resource_pack.invalid_overlay_format: "Issue found in config.yml at 'resource-pack.overlay-format' - Invalid overlay format ''. Overlay format must contain the placeholder '{version}'." warning.config.equipment.duplicate: "Issue found in file - Duplicated equipment ''. Please check if there is the same configuration in other files." warning.config.equipment.missing_type: "Issue found in file - The equipment '' is missing the required 'type' argument." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index baf7bc52c..c3f6229fb 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -376,7 +376,7 @@ warning.config.resource_pack.item_model.conflict.vanilla: "Не удал warning.config.resource_pack.item_model.already_exist: "Не удалось создать модель элемента для '', потому что файл '' уже существует." warning.config.resource_pack.model.generation.already_exist: "Не удалось создать модель, так как файл модели '' уже существует." warning.config.resource_pack.generation.missing_font_texture: "В шрифте '' отсутствует обязательная текстура: ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "Текстура '' не указана в атласе. Вам нужно добавить путь к текстуре в атлас или включить 'obfuscation'/'fix-atlas' опцию в config.yml." +warning.config.resource_pack.generation.texture_not_in_atlas: "Текстура '' не указана в атласе. Вам нужно добавить путь к текстуре в атлас или включить 'fix-atlas' опцию в config.yml." warning.config.resource_pack.generation.missing_model_texture: "В модели '' отсутствует текстура ''" warning.config.resource_pack.generation.missing_item_model: "В предмете '' отсутствует файл модели: ''" warning.config.resource_pack.generation.missing_block_model: "В блоке '' отсутствует файл модели: ''" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 21e1bae3b..7c2b3abef 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -444,7 +444,7 @@ warning.config.resource_pack.generation.missing_item_model: "物品'方块状态''缺少模型文件: ''" warning.config.resource_pack.generation.missing_parent_model: "模型''找不到父级模型文件: ''" warning.config.resource_pack.generation.missing_equipment_texture: "装备 '' 缺少纹理 ''" -warning.config.resource_pack.generation.texture_not_in_atlas: "纹理''不在图集内. 你需要将纹理路径或文件夹前缀添加到图集内,或者启用 config.yml 中的 'obfuscation'/'fix-atlas' 或 'fix-atlas' 选项" +warning.config.resource_pack.generation.texture_not_in_atlas: "纹理''不在图集内. 你需要将纹理路径或文件夹前缀添加到图集内,或者启用 config.yml 中的 'fix-atlas' 选项" warning.config.resource_pack.invalid_overlay_format: "在 config.yml 的 'resource-pack.overlay-format' 处发现问题 - 无效的overlay格式 ''. Overlay格式必须包含占位符 '{version}'" warning.config.equipment.duplicate: "在文件 发现问题 - 重复的装备配置 ''。请检查其他文件中是否存在相同配置" warning.config.equipment.missing_type: "在文件 发现问题 - 装备 '' 缺少必需的 'type' 参数" diff --git a/gradle.properties b/gradle.properties index 464ab90be..604850d98 100644 --- a/gradle.properties +++ b/gradle.properties @@ -60,9 +60,9 @@ authlib_version=7.0.60 concurrent_util_version=0.0.3 # Proxy settings -systemProp.socks.proxyHost=127.0.0.1 -systemProp.socks.proxyPort=7890 -systemProp.http.proxyHost=127.0.0.1 -systemProp.http.proxyPort=7890 -systemProp.https.proxyHost=127.0.0.1 -systemProp.https.proxyPort=7890 \ No newline at end of file +#systemProp.socks.proxyHost=127.0.0.1 +#systemProp.socks.proxyPort=7890 +#systemProp.http.proxyHost=127.0.0.1 +#systemProp.http.proxyPort=7890 +#systemProp.https.proxyHost=127.0.0.1 +#systemProp.https.proxyPort=7890 \ No newline at end of file From ab68fa13d04995c93f197abfc2b17ddb243db123 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 01:16:44 +0800 Subject: [PATCH 213/226] =?UTF-8?q?=E5=86=8D=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/translations/de.yml | 2 +- .../src/main/resources/translations/en.yml | 3 ++- .../src/main/resources/translations/es.yml | 2 +- .../src/main/resources/translations/ru_ru.yml | 2 +- .../src/main/resources/translations/tr.yml | 2 +- .../src/main/resources/translations/zh_cn.yml | 2 +- .../core/block/AbstractBlockManager.java | 8 ++++-- .../core/block/InactiveCustomBlock.java | 2 ++ .../craftengine/core/util/CharacterUtils.java | 27 +------------------ .../DefaultBlockEntitySerializer.java | 8 +++--- .../DefaultSectionSerializer.java | 3 +-- 11 files changed, 22 insertions(+), 39 deletions(-) diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 2b9ac8bc2..b3e57e754 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -257,7 +257,7 @@ warning.config.block.state.missing_properties: "Problem in Datei warning.config.block.state.missing_appearances: "Problem in Datei gefunden - Beim Block '' fehlt der erforderliche 'appearances'-Abschnitt für 'states'." warning.config.block.state.variant.invalid_appearance: "Problem in Datei gefunden - Der Block '' hat einen Fehler, dass die Variante '' eine nicht existierende Appearance '' verwendet." warning.config.block.state.invalid_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen ungültigen Vanilla-Block-State ''." -warning.config.block.state.unavailable_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen nicht verfügbaren Vanilla-Block-State ''. Bitte gib diesen State in der mappings.yml frei." +warning.config.block.state.unavailable_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen nicht verfügbaren Vanilla-Block-State ''. Bitte gib diesen State in der block-state-mappings frei." warning.config.block.state.invalid_vanilla_id: "Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Block-State '', der den verfügbaren Slot-Bereich '0~' überschreitet." warning.config.block.state.id.conflict: "Problem in Datei gefunden - Der Block '' konnte den echten Block-State für '' nicht binden, da der State bereits von '' belegt ist." warning.config.block.state.model.missing_path: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'path'-Option für 'model'." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 655b1c690..5679ea63c 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -129,6 +129,7 @@ warning.config.image.codepoint.exhausted: "Issue found in file - warning.config.image.invalid_codepoint_grid: "Issue found in file - Image '' has an invalid 'chars' codepoint grid." warning.config.image.invalid_char: "Issue found in file - Image '' has a char parameter containing combining characters, which may result in image splitting." warning.config.image.invalid_hex_value: "Issue found in file - The image '' is using a unicode character '' that is not a valid hexadecimal (radix 16) value." +warning.config.image.invalid_unicode_string: "Issue found in file - The image '' is using an incorrect unicode string ''." warning.config.recipe.duplicate: "Issue found in file - Duplicated recipe ''. Please check if there is the same configuration in other files." warning.config.recipe.missing_type: "Issue found in file - The recipe '' is missing the required 'type' argument." warning.config.recipe.invalid_type: "Issue found in file - The recipe '' is using an invalid recipe type ''." @@ -276,7 +277,7 @@ warning.config.block.state.entity_renderer.better_model.missing_model: " warning.config.block.state.entity_renderer.model_engine.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'model_engine' entity renderer." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." -warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in mappings.yml." +warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in block-state-mappings." warning.config.block.state.invalid_vanilla_id: "Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." warning.config.block.state.invalid_id: "Issue found in file - The block state ID range () used by block '' is outside the valid range of 0 to . Please add more server-side blocks in 'config.yml' if the current slots are exhausted." warning.config.block.state.id.conflict: "Issue found in file - The block '' failed to bind real block state '' for '' as the state has been occupied by ''." diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index 908d1752c..13b0fe348 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -180,7 +180,7 @@ warning.config.block.state.missing_properties: "Problema encontrado en e warning.config.block.state.missing_appearances: "Problema encontrado en el archivo - El bloque '' carece de la sección requerida 'appearances' para 'states'." warning.config.block.state.variant.invalid_appearance: "Problema encontrado en el archivo - Hay un error en el bloque '' donde la variante '' está usando una apariencia inexistente ''." warning.config.block.state.invalid_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla inválido ''." -warning.config.block.state.unavailable_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla no disponible ''. Por favor libera este estado en el archivo mappings.yml." +warning.config.block.state.unavailable_vanilla: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla no disponible ''. Por favor libera este estado en el archivo block-state-mappings." warning.config.block.state.invalid_vanilla_id: "Problema encontrado en el archivo - El bloque '' está usando un estado de bloque vanilla '' que excede el rango de slots disponible '0~'." warning.config.block.state.id.conflict: "Problema encontrado en el archivo - El bloque '' falló al vincular el estado de bloque real para '' porque está ocupado por el estado ''." warning.config.block.state.model.missing_path: "Problema encontrado en el archivo - El bloque '' carece de la opción requerida 'path' para 'model'." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index c3f6229fb..5ae93dfe1 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -230,7 +230,7 @@ warning.config.block.state.missing_properties: "Проблема най warning.config.block.state.missing_appearances: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'appearances' раздел для 'states'." warning.config.block.state.variant.invalid_appearance: "Проблема найдена в файле - Блок '' имеет ошибку, что вариант '' использует несуществующий внешний вид ''." warning.config.block.state.invalid_vanilla: "Проблема найдена в файле - Блок '' имеет недействительное состояние ванильного блока ''." -warning.config.block.state.unavailable_vanilla: "Проблема найдена в файле - Блок '' использует недоступное состояние ванильного блока ''. Пожалуйста, освободите это состояние в mappings.yml." +warning.config.block.state.unavailable_vanilla: "Проблема найдена в файле - Блок '' использует недоступное состояние ванильного блока ''. Пожалуйста, освободите это состояние в block-state-mappings." warning.config.block.state.invalid_vanilla_id: "Проблема найдена в файле - Блок '' использует состояние ванильного блока '', что превышает доступный диапазон слотов '0~'." warning.config.block.state.id.conflict: "Проблема найдена в файле - Блоку '' не удалось привязать реальное состояние блока для '', так как состояние занято ''." warning.config.block.state.model.missing_path: "Проблема найдена в файле - В блоке '' отсутствует необходимый 'path' опция для 'model'." diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index 20538d70e..8507154a8 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -178,7 +178,7 @@ warning.config.block.state.missing_properties: " dosyasında soru warning.config.block.state.missing_appearances: " dosyasında sorun bulundu - '' bloğu, 'states' için gerekli 'appearances' bölümü eksik." warning.config.block.state.variant.invalid_appearance: " dosyasında sorun bulundu - '' bloğunda, '' varyantının var olmayan bir görünüm '' kullandığı bir hata var." warning.config.block.state.invalid_vanilla: " dosyasında sorun bulundu - '' bloğu geçersiz bir vanilya blok durumu '' kullanıyor." -warning.config.block.state.unavailable_vanilla: " dosyasında sorun bulundu - '' bloğu kullanılamayan bir vanilya blok durumu '' kullanıyor. Lütfen bu durumu mappings.yml dosyasında serbest bırakın." +warning.config.block.state.unavailable_vanilla: " dosyasında sorun bulundu - '' bloğu kullanılamayan bir vanilya blok durumu '' kullanıyor. Lütfen bu durumu block-state-mappings dosyasında serbest bırakın." warning.config.block.state.invalid_vanilla_id: " dosyasında sorun bulundu - '' bloğu, mevcut yuva aralığı '0~' aşan bir vanilya blok durumu '' kullanıyor." warning.config.block.state.id.conflict: " dosyasında sorun bulundu - '' bloğu, durum '' tarafından işgal edildiği için '' için gerçek blok durumu bağlamada başarısız oldu." warning.config.block.state.model.missing_path: " dosyasında sorun bulundu - '' bloğu, 'model' için gerekli 'path' seçeneği eksik." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 7c2b3abef..97c6d2d43 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -266,7 +266,7 @@ warning.config.block.state.missing_properties: "在文件 发现 warning.config.block.state.missing_appearances: "在文件 发现问题 - 方块 '' 的 'states' 缺少必需的 'appearances' 段落" warning.config.block.state.variant.invalid_appearance: "在文件 发现问题 - 方块 '' 的变体 '' 使用了不存在的 appearance ''" warning.config.block.state.invalid_vanilla: "在文件 发现问题 - 方块 '' 使用了无效的原版方块状态 ''" -warning.config.block.state.unavailable_vanilla: "在文件 发现问题 - 方块 '' 使用了不可用的原版方块状态 '' 请在 mappings.yml 中释放该状态" +warning.config.block.state.unavailable_vanilla: "在文件 发现问题 - 方块 '' 使用了不可用的原版方块状态 '' 请在 block-state-mappings 中释放该状态" warning.config.block.state.invalid_vanilla_id: "在文件 发现问题 - 方块 '' 使用的原版方块状态 '' 超出可用槽位范围 '0~'" warning.config.block.state.id.conflict: "在文件 发现问题 - 方块 '' 无法为 '' 绑定真实方块状态 '' 因该状态已被 '' 占用" warning.config.block.state.invalid_id: "在文件 发现问题 - 方块 '' 使用的真实方块状态 '' 超出可用槽位范围 '0~' 如果槽位已用尽 请在 config.yml 中添加更多服务端侧方块" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 13078a68a..898a99a9e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -383,6 +383,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem Map> properties = singleState ? Map.of() : parseBlockProperties(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), "properties")); // 注册方块容器 Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).getOrRegisterForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), id)); + // 先绑定无效方块 + holder.bindValue(new InactiveCustomBlock(holder)); // 根据properties生成variant provider BlockStateVariantProvider variantProvider = new BlockStateVariantProvider(holder, (owner, propertyMap) -> { @@ -455,8 +457,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem LootTable.fromMap(ResourceConfigUtils.getAsMapOrNull(section.get("loot"), "loot")) ); BlockBehavior blockBehavior = createBlockBehavior(customBlock, MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors"))); - customBlock.setBehavior(blockBehavior); - holder.bindValue(customBlock); // 单状态 if (singleState) { @@ -555,6 +555,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } + // 一定要到最后再绑定 + customBlock.setBehavior(blockBehavior); + holder.bindValue(customBlock); + // 添加方块 AbstractBlockManager.this.byId.put(customBlock.id(), customBlock); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java index abd181734..524dc7865 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.block; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.sparrow.nbt.CompoundTag; @@ -18,6 +19,7 @@ public final class InactiveCustomBlock extends AbstractCustomBlock { public ImmutableBlockState getBlockState(CompoundTag nbt) { return this.cachedData.computeIfAbsent(nbt, k -> { ImmutableBlockState state = new ImmutableBlockState(super.holder, new Reference2ObjectArrayMap<>()); + state.setBehavior(EmptyBlockBehavior.INSTANCE); state.setNbtToSave(state.toNbtToSave(nbt)); return state; }); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java index 562e59b81..3f069b8ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java @@ -6,14 +6,12 @@ import java.util.regex.Pattern; import java.util.stream.IntStream; public class CharacterUtils { - private static final Pattern PATTERN = Pattern.compile("[\\p{Mn}\\p{Me}\\p{Mc}\\p{Cf}]"); - private CharacterUtils() {} public static char[] decodeUnicodeToChars(String unicodeString) { int count = unicodeString.length() / 6; if (unicodeString.length() % 6 != 0) { - throw new LocalizedResourceConfigException("warning.config.image.invalid_unicode_string_length"); + throw new LocalizedResourceConfigException("warning.config.image.invalid_unicode_string", unicodeString); } char[] chars = new char[count]; for (int i = 0, j = 0; j < count; i += 6, j++) { @@ -71,29 +69,6 @@ public class CharacterUtils { return builder.toString(); } - public static boolean containsCombinedCharacter(String input) { - if (input == null || input.isEmpty() || input.length() == 1) return false; - for (int i = 0; i < input.length();) { - int codePoint = input.codePointAt(i); - i += Character.charCount(codePoint); - int type = Character.getType(codePoint); - if (type == Character.NON_SPACING_MARK || - type == Character.ENCLOSING_MARK || - type == Character.COMBINING_SPACING_MARK || - type == Character.FORMAT || - type == Character.CONTROL || - type == Character.SURROGATE || - type == Character.PRIVATE_USE || - PATTERN.matcher(new String(Character.toChars(codePoint))).find() - ) return true; - if (i < input.length()) { - int nextCodePoint = input.codePointAt(i); - if (Character.isSurrogatePair(Character.toChars(codePoint)[0], Character.toChars(nextCodePoint)[0])) return true; - } - } - return false; - } - public static String replaceBackslashWithSlash(String input) { if (input == null || input.isEmpty()) { return input; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index 6dfc48961..b72ebf984 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -38,9 +38,11 @@ public final class DefaultBlockEntitySerializer { } else { BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); ImmutableBlockState blockState = chunk.getBlockState(pos); - BlockEntity blockEntity = type.factory().create(pos, blockState); - blockEntity.loadCustomData(data); - blockEntities.add(blockEntity); + if (blockState.blockEntityType() == type) { + BlockEntity blockEntity = type.factory().create(pos, blockState); + blockEntity.loadCustomData(data); + blockEntities.add(blockEntity); + } } } return blockEntities; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java index 1f0ee3319..991797da3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultSectionSerializer.java @@ -74,8 +74,7 @@ public final class DefaultSectionSerializer { key = Key.of(id); } Holder owner = BuiltInRegistries.BLOCK.get(key).orElseGet(() -> { - Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).registerForHolder( - ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), key)); + Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.BLOCK).registerForHolder(ResourceKey.create(BuiltInRegistries.BLOCK.key().location(), key)); InactiveCustomBlock inactiveBlock = new InactiveCustomBlock(holder); holder.bindValue(inactiveBlock); return holder; From ef38e23040d03ff2c73a9a68fd3f829876aca26c Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 01:36:35 +0800 Subject: [PATCH 214/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AD=97=E4=BD=93?= =?UTF-8?q?=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/block/AbstractBlockManager.java | 22 +++++++---- .../core/font/AbstractFontManager.java | 37 +++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 898a99a9e..8f2f9f035 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -46,6 +46,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager { @@ -428,13 +429,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - CompletableFutures.allOf(internalIdAllocators).thenRun(() -> ResourceConfigUtils.runCatching(path, node, () -> { - for (int i = 0; i < internalIdAllocators.size(); i++) { - CompletableFuture future = internalIdAllocators.get(i); - try { - int internalId = future.get(); - states.get(i).setCustomBlockState(BlockRegistryMirror.byId(internalId + AbstractBlockManager.this.vanillaBlockStateCount)); - } catch (ExecutionException e) { + CompletableFutures.allOf(internalIdAllocators).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> { + if (t != null) { + if (t instanceof CompletionException e) { Throwable cause = e.getCause(); // 这里不会有conflict了,因为之前已经判断过了 if (cause instanceof IdAllocator.IdExhaustedException) { @@ -443,7 +440,16 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem Debugger.BLOCK.warn(() -> "Unknown error while allocating internal block state id.", cause); return; } - } catch (InterruptedException e) { + } + throw new RuntimeException("Unknown error occurred", t); + } + + for (int i = 0; i < internalIdAllocators.size(); i++) { + CompletableFuture future = internalIdAllocators.get(i); + try { + int internalId = future.get(); + states.get(i).setCustomBlockState(BlockRegistryMirror.byId(internalId + AbstractBlockManager.this.vanillaBlockStateCount)); + } catch (InterruptedException | ExecutionException e) { AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating internal block state for block " + id.asString(), e); return; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 37b33b51b..999dcc6da 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -572,7 +573,7 @@ public abstract class AbstractFontManager implements FontManager { codepoints = CharacterUtils.charsToCodePoints(charString.toCharArray()); } for (int j = 0; j < codepoints.length; j++) { - futureCodepoints.add(allocator.assignFixedId(id.asString() + ":" + i + ":" + j, codepoints[i])); + futureCodepoints.add(allocator.assignFixedId(id.asString() + ":" + i + ":" + j, codepoints[j])); } if (tempColumns == -1) { tempColumns = codepoints.length; @@ -607,26 +608,32 @@ public abstract class AbstractFontManager implements FontManager { } } - CompletableFutures.allOf(futureCodepoints).thenRun(() -> ResourceConfigUtils.runCatching(path, node, () -> { + CompletableFutures.allOf(futureCodepoints).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> { + if (t != null) { + if (t instanceof CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof IdAllocator.IdConflictException conflict) { + throw new LocalizedResourceConfigException("warning.config.image.codepoint.conflict", + fontId.toString(), + CharacterUtils.encodeCharsToUnicode(Character.toChars(conflict.id())), + new String(Character.toChars(conflict.id())), + conflict.previousOwner() + ); + } else if (cause instanceof IdAllocator.IdExhaustedException) { + throw new LocalizedResourceConfigException("warning.config.image.codepoint.exhausted", fontId.asString()); + } + } + throw new RuntimeException("Unknown error occurred", t); + } + int[][] codepointGrid = new int[rows][columns]; + for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { try { int codepoint = futureCodepoints.get(i * columns + j).get(); codepointGrid[i][j] = codepoint; - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause instanceof IdAllocator.IdConflictException conflict) { - throw new LocalizedResourceConfigException("warning.config.image.codepoint.conflict", - fontId.toString(), - CharacterUtils.encodeCharsToUnicode(Character.toChars(conflict.id())), - new String(Character.toChars(conflict.id())), - conflict.previousOwner() - ); - } else if (cause instanceof IdAllocator.IdExhaustedException) { - throw new LocalizedResourceConfigException("warning.config.image.codepoint.exhausted", fontId.asString()); - } - } catch (InterruptedException e) { + } catch (InterruptedException | ExecutionException e) { AbstractFontManager.this.plugin.logger().warn("Interrupted while allocating codepoint for image " + id.asString(), e); return; } From 7d346b97798ee95eace2ab818f01d528f7eef258 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 01:54:45 +0800 Subject: [PATCH 215/226] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/BukkitCraftEngine.java | 14 ++++++++++++++ .../core/block/AbstractBlockManager.java | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) 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 9acb350a2..e5a984612 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 @@ -55,6 +55,7 @@ import org.jspecify.annotations.Nullable; import java.io.*; import java.net.URL; import java.net.URLConnection; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -210,6 +211,19 @@ public class BukkitCraftEngine extends CraftEngine { super.furnitureManager = new BukkitFurnitureManager(this); super.onPluginEnable(); super.compatibilityManager().onEnable(); + + // todo 未来版本移除 + Path legacyFile1 = this.dataFolderPath().resolve("additional-real-blocks.yml"); + Path legacyFile2 = this.dataFolderPath().resolve("mappings.yml"); + if (Files.exists(legacyFile1)) { + try { + Files.delete(legacyFile1); + Files.deleteIfExists(legacyFile2); + this.saveResource("resources/internal/configuration/mappings.yml"); + } catch (IOException e) { + this.logger.warn("Failed to delete legacy files", e); + } + } } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 8f2f9f035..c9bda8d59 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -674,7 +674,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 获取原版方块的id Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]); try { - List arranger =blockStateArranger.get(block); + List arranger = AbstractBlockManager.this.blockStateArranger.get(block); if (arranger == null) { throw new LocalizedResourceConfigException("warning.config.block.state.unavailable_vanilla", blockState); } From f08131d771577c3e3eb18e4363e7f692c9d5bd5e Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 02:32:38 +0800 Subject: [PATCH 216/226] =?UTF-8?q?=E5=85=81=E8=AE=B8=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=A7=A3=E6=9E=90shift=E5=92=8Cimage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/build.gradle.kts | 2 + .../core/font/AbstractFontManager.java | 6 +- .../craftengine/core/font/FontManager.java | 2 + .../craftengine/core/font/OffsetFont.java | 17 +++-- .../core/pack/LoadingSequence.java | 6 +- .../plugin/locale/TranslationManagerImpl.java | 13 +++- .../core/util/AdventureHelper.java | 70 +++---------------- 7 files changed, 43 insertions(+), 73 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 0694d2128..352ad960b 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -30,6 +30,8 @@ dependencies { compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") { exclude("com.google.code.gson", "gson") } + compileOnly("net.kyori:adventure-text-serializer-legacy:${rootProject.properties["adventure_bundle_version"]}") + compileOnly("net.kyori:adventure-text-serializer-json-legacy-impl:${rootProject.properties["adventure_bundle_version"]}") // Command compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}") diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 999dcc6da..3fbeefb07 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -2,7 +2,6 @@ package net.momirealms.craftengine.core.font; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.item.AbstractItemManager; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; @@ -76,6 +75,11 @@ public abstract class AbstractFontManager implements FontManager { this.networkTagMapper = new HashMap<>(1024); } + @Override + public OffsetFont offsetFont() { + return offsetFont; + } + @Override public void unload() { this.fonts.clear(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java index aca5c9dd6..e56e39c3b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java @@ -46,6 +46,8 @@ public interface FontManager extends Manageable { IllegalCharacterProcessResult processIllegalCharacters(String raw, char replacement); + OffsetFont offsetFont(); + ConfigParser[] parsers(); default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/OffsetFont.java b/core/src/main/java/net/momirealms/craftengine/core/font/OffsetFont.java index c1ae43aa3..26c7cea5a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/OffsetFont.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/OffsetFont.java @@ -12,7 +12,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; public class OffsetFont { - private final String font; + private static OffsetFont instance; + private final net.momirealms.craftengine.core.util.Key font; private final Key fontKey; private final String NEG_16; @@ -39,10 +40,14 @@ public class OffsetFont { .maximumSize(256) .build(); - public OffsetFont(Section section) { - font = section.getString("font", "minecraft:default"); - fontKey = Key.key(font); + public net.momirealms.craftengine.core.util.Key font() { + return font; + } + @SuppressWarnings("all") + public OffsetFont(Section section) { + font = net.momirealms.craftengine.core.util.Key.of(section.getString("font", "minecraft:default")); + fontKey = Key.key(font.namespace(), font.value()); NEG_16 = convertIfUnicode(section.getString("-16", "")); NEG_24 = convertIfUnicode(section.getString("-24", "")); NEG_32 = convertIfUnicode(section.getString("-32", "")); @@ -71,7 +76,7 @@ public class OffsetFont { public String createOffset(int offset, BiFunction tagDecorator) { if (offset == 0) return ""; - return tagDecorator.apply(this.fastLookup.get(offset, k -> k > 0 ? createPos(k) : createNeg(-k)), this.font); + return tagDecorator.apply(this.fastLookup.get(offset, k -> k > 0 ? createPos(k) : createNeg(-k)), this.font.asString()); } @SuppressWarnings("DuplicatedCode") @@ -148,7 +153,7 @@ public class OffsetFont { private String convertIfUnicode(String s) { if (s.startsWith("\\u")) { - return new String(CharacterUtils.decodeUnicodeToChars(font)); + return new String(CharacterUtils.decodeUnicodeToChars(font.asString())); } else { return s; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java index 71368dae1..46e3998aa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java @@ -4,9 +4,8 @@ public final class LoadingSequence { private LoadingSequence() {} public static final int TEMPLATE = 0; - public static final int BLOCK_STATE_MAPPING = 5; - public static final int GLOBAL_VAR = 10; - public static final int LANG = 20; + public static final int BLOCK_STATE_MAPPING = 10; + public static final int GLOBAL_VAR = 20; public static final int TRANSLATION = 30; public static final int EQUIPMENT = 40; public static final int ITEM = 50; @@ -20,4 +19,5 @@ public final class LoadingSequence { public static final int VANILLA_LOOTS = 130; public static final int EMOJI = 140; public static final int ADVANCEMENT = 150; + public static final int LANG = 160; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index 41b8bb69f..b4e249748 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -2,13 +2,19 @@ package net.momirealms.craftengine.core.plugin.locale; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.block.AbstractBlockManager; +import net.momirealms.craftengine.core.font.FontManager; +import net.momirealms.craftengine.core.font.OffsetFont; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.plugin.PluginProperties; import net.momirealms.craftengine.core.plugin.config.*; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; +import net.momirealms.craftengine.core.plugin.text.minimessage.ImageTag; import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag; +import net.momirealms.craftengine.core.plugin.text.minimessage.ShiftTag; import net.momirealms.craftengine.core.util.AdventureHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -25,6 +31,7 @@ import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import java.util.stream.Collectors; public class TranslationManagerImpl implements TranslationManager { @@ -276,6 +283,10 @@ public class TranslationManagerImpl implements TranslationManager { public class LangParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"lang", "language", "languages"}; + private final Function langProcessor = s -> { + Component deserialize = AdventureHelper.miniMessage().deserialize(AdventureHelper.legacyToMiniMessage(s), ShiftTag.INSTANCE, ImageTag.INSTANCE); + return AdventureHelper.getLegacy().serialize(deserialize); + }; @Override public int loadingSequence() { @@ -293,7 +304,7 @@ public class TranslationManagerImpl implements TranslationManager { Map sectionData = section.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, - entry -> String.valueOf(entry.getValue()) + entry -> this.langProcessor.apply(String.valueOf(entry.getValue())) )); TranslationManagerImpl.this.addClientTranslation(langId, sectionData); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index a8ad7ed18..bc134b433 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -9,10 +9,12 @@ import net.kyori.adventure.text.ComponentIteratorType; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.internal.parser.TokenParser; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONOptions; import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.sparrow.nbt.Tag; @@ -33,6 +35,7 @@ public class AdventureHelper { private final MiniMessage miniMessageCustom; private final GsonComponentSerializer gsonComponentSerializer; private final NBTComponentSerializer nbtComponentSerializer; + private final LegacyComponentSerializer legacyComponentSerializer; private static final TextReplacementConfig REPLACE_LF = TextReplacementConfig.builder().matchLiteral("\n").replacement(Component.newline()).build(); /** * This iterator slices a component into individual parts that @@ -65,6 +68,7 @@ public class AdventureHelper { b.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true); }); } + this.legacyComponentSerializer = LegacyComponentSerializer.builder().build(); this.gsonComponentSerializer = builder.build(); this.nbtComponentSerializer = NBTComponentSerializer.builder() .editItem(item -> { @@ -84,20 +88,10 @@ public class AdventureHelper { private static final AdventureHelper INSTANCE = new AdventureHelper(); } - /** - * Retrieves the singleton instance of AdventureHelper. - * - * @return the singleton instance - */ public static AdventureHelper getInstance() { return SingletonHolder.INSTANCE; } - /** - * Retrieves the MiniMessage instance. - * - * @return the MiniMessage instance - */ public static MiniMessage miniMessage() { return getInstance().miniMessage; } @@ -106,70 +100,22 @@ public class AdventureHelper { return getInstance().miniMessageCustom; } + public static LegacyComponentSerializer getLegacy() { + return getInstance().legacyComponentSerializer; + } + public static MiniMessage strictMiniMessage() { return getInstance().miniMessageStrict; } - /** - * Retrieves the GsonComponentSerializer instance. - * - * @return the GsonComponentSerializer instance - */ public static GsonComponentSerializer getGson() { return getInstance().gsonComponentSerializer; } - /** - * Retrieves the NBTComponentSerializer instance. - * - * @return the NBTComponentSerializer instance - */ public static NBTComponentSerializer getNBT() { return getInstance().nbtComponentSerializer; } - /** - * Sends a message to an audience. - * - * @param audience the audience to send the message to - * @param message the message component - */ - public static void sendMessage(Audience audience, Component message) { - audience.sendMessage(message); - } - - /** - * Plays a sound for an audience. - * - * @param audience the audience to play the sound for - * @param sound the sound to play - */ - public static void playSound(Audience audience, Sound sound) { - audience.playSound(sound); - } - - /** - * Surrounds text with a MiniMessage font tag. - * - * @param text the text to surround - * @param font the font as a {@link Key} - * @return the text surrounded by the MiniMessage font tag - */ - public static String surroundWithMiniMessageFont(String text, Key font) { - return "" + text + ""; - } - - /** - * Surrounds text with a MiniMessage font tag. - * - * @param text the text to surround - * @param font the font as a {@link String} - * @return the text surrounded by the MiniMessage font tag - */ - public static String surroundWithMiniMessageFont(String text, String font) { - return "" + text + ""; - } - /** * Converts a JSON string to a MiniMessage string. * From 66cd1c0962690b3ee71fb27b993a99bf3c1eb7fd Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 05:16:20 +0800 Subject: [PATCH 217/226] =?UTF-8?q?=E5=A4=96=E8=A7=82=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=86=E9=85=8Dpart=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 9 +- .../bukkit/block/BukkitBlockStateWrapper.java | 34 -------- .../block/BukkitCustomBlockStateWrapper.java | 46 ++++++++++ .../block/BukkitVanillaBlockStateWrapper.java | 36 ++++++++ .../block/behavior/DoorBlockBehavior.java | 4 +- .../behavior/DoubleHighBlockBehavior.java | 2 +- ...hedHorizontalDirectionalBlockBehavior.java | 2 +- .../block/behavior/SlabBlockBehavior.java | 2 +- .../block/behavior/SofaBlockBehavior.java | 2 +- .../block/behavior/StairsBlockBehavior.java | 4 +- .../block/behavior/TrapDoorBlockBehavior.java | 2 +- .../bukkit/util/StairsShapeUtils.java | 2 +- .../bukkit/world/BukkitExistingBlock.java | 6 +- .../core/block/AbstractBlockManager.java | 59 +++++++------ .../core/block/AbstractBlockStateWrapper.java | 21 +++++ .../core/block/AutoStateGroup.java | 84 +++++++++++++++++++ .../craftengine/core/block/BlockKeys.java | 16 ++++ .../core/block/BlockStateHolder.java | 2 +- .../core/block/BlockStateWrapper.java | 6 ++ .../{state => }/StatePropertyAccessor.java | 2 +- .../core/block/properties/Properties.java | 3 +- .../block/properties/type/AnchorType.java | 7 ++ .../core/block/properties/type/DoorHinge.java | 5 ++ .../properties/type/DoubleBlockHalf.java | 5 ++ .../properties/type/SingleBlockHalf.java | 5 ++ .../core/block/properties/type/SlabType.java | 7 ++ .../type}/SofaShape.java | 2 +- .../type}/StairsShape.java | 2 +- .../block/state/properties/AnchorType.java | 7 -- .../block/state/properties/DoorHinge.java | 5 -- .../state/properties/DoubleBlockHalf.java | 5 -- .../state/properties/SingleBlockHalf.java | 5 -- .../core/block/state/properties/SlabType.java | 7 -- .../core/font/AbstractFontManager.java | 2 +- .../core/item/AbstractItemManager.java | 2 +- .../pack/allocator/BlockStateAllocator.java | 33 ++++++++ .../pack/allocator/BlockStateCandidate.java | 24 ++++++ .../{cache => allocator}/IdAllocator.java | 2 +- .../MatchBlockPropertyCondition.java | 2 +- .../core/plugin/locale/I18NData.java | 2 +- .../plugin/locale/TranslationManagerImpl.java | 4 - .../core/util/AdventureHelper.java | 4 - .../craftengine/core/util/CharacterUtils.java | 1 - .../craftengine/core/world/ExistingBlock.java | 2 +- gradle.properties | 2 +- 45 files changed, 359 insertions(+), 127 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitVanillaBlockStateWrapper.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java rename core/src/main/java/net/momirealms/craftengine/core/block/{state => }/StatePropertyAccessor.java (83%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/properties/type/AnchorType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoorHinge.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoubleBlockHalf.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SingleBlockHalf.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SlabType.java rename core/src/main/java/net/momirealms/craftengine/core/block/{state/properties => properties/type}/SofaShape.java (53%) rename core/src/main/java/net/momirealms/craftengine/core/block/{state/properties => properties/type}/StairsShape.java (62%) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoorHinge.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoubleBlockHalf.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SingleBlockHalf.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SlabType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateCandidate.java rename core/src/main/java/net/momirealms/craftengine/core/pack/{cache => allocator}/IdAllocator.java (99%) 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 705ef4dd4..304156691 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 @@ -353,10 +353,13 @@ public final class BukkitBlockManager extends AbstractBlockManager { private void initMirrorRegistry() { int size = RegistryUtils.currentBlockRegistrySize(); BlockStateWrapper[] states = new BlockStateWrapper[size]; - for (int i = 0; i < size; i++) { - states[i] = new BukkitBlockStateWrapper(BlockStateUtils.idToBlockState(i), i); + for (int i = 0; i < this.vanillaBlockStateCount; i++) { + states[i] = new BukkitVanillaBlockStateWrapper(BlockStateUtils.idToBlockState(i), i); } - BlockRegistryMirror.init(states, new BukkitBlockStateWrapper(MBlocks.STONE$defaultState, BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState))); + for (int i = this.vanillaBlockStateCount; i < size; i++) { + states[i] = new BukkitCustomBlockStateWrapper(BlockStateUtils.idToBlockState(i), i); + } + BlockRegistryMirror.init(states, states[BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState)]); } // 注册服务端侧的真实方块 diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java deleted file mode 100644 index 8d0190d76..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.momirealms.craftengine.bukkit.block; - -import net.momirealms.craftengine.core.block.BlockStateWrapper; -import net.momirealms.craftengine.core.util.Key; - -public class BukkitBlockStateWrapper implements BlockStateWrapper { - private final Object blockState; - private final int registryId; - - public BukkitBlockStateWrapper(Object blockState, int registryId) { - this.blockState = blockState; - this.registryId = registryId; - } - - @Override - public Object literalObject() { - return this.blockState; - } - - @Override - public int registryId() { - return this.registryId; - } - - @Override - public String toString() { - return this.blockState.toString(); - } - - @Override - public Key ownerId() { - return null; - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java new file mode 100644 index 000000000..f67d21749 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java @@ -0,0 +1,46 @@ +package net.momirealms.craftengine.bukkit.block; + +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.core.block.AbstractBlockStateWrapper; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.Key; + +import java.util.Optional; + +public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper { + + public BukkitCustomBlockStateWrapper(Object blockState, int registryId) { + super(blockState, registryId); + } + + @Override + public Key ownerId() { + return getImmutableBlockState().map(state -> state.owner().key().location()).orElseGet(() -> BlockStateUtils.getBlockOwnerIdFromState(super.blockState)); + } + + @SuppressWarnings("unchecked") + @Override + public T getProperty(String propertyName) { + return (T) getImmutableBlockState().map(state -> { + Property property = state.owner().value().getProperty(propertyName); + if (property == null) + return null; + return state.getNullable(property); + }).orElse(null); + } + + @Override + public boolean hasProperty(String propertyName) { + return getImmutableBlockState().map(state -> state.owner().value().getProperty(propertyName) != null).orElse(false); + } + + @Override + public String getAsString() { + return getImmutableBlockState().map(ImmutableBlockState::toString).orElseGet(() -> BlockStateUtils.fromBlockData(super.blockState).getAsString()); + } + + public Optional getImmutableBlockState() { + return BlockStateUtils.getOptionalCustomBlockState(super.blockState); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitVanillaBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitVanillaBlockStateWrapper.java new file mode 100644 index 000000000..9a13a5d13 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitVanillaBlockStateWrapper.java @@ -0,0 +1,36 @@ +package net.momirealms.craftengine.bukkit.block; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.core.block.AbstractBlockStateWrapper; +import net.momirealms.craftengine.core.block.StatePropertyAccessor; +import net.momirealms.craftengine.core.util.Key; + +public class BukkitVanillaBlockStateWrapper extends AbstractBlockStateWrapper { + private final StatePropertyAccessor accessor; + + public BukkitVanillaBlockStateWrapper(Object blockState, int registryId) { + super(blockState, registryId); + this.accessor = FastNMS.INSTANCE.createStatePropertyAccessor(blockState); + } + + @Override + public Key ownerId() { + return BlockStateUtils.getBlockOwnerIdFromState(super.blockState); + } + + @Override + public T getProperty(String propertyName) { + return this.accessor.getPropertyValue(propertyName); + } + + @Override + public boolean hasProperty(String propertyName) { + return this.accessor.hasProperty(propertyName); + } + + @Override + public String getAsString() { + return BlockStateUtils.fromBlockData(super.blockState).getAsString(); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java index 864563451..677e9dd1c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java @@ -16,8 +16,8 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.DoorHinge; -import net.momirealms.craftengine.core.block.state.properties.DoubleBlockHalf; +import net.momirealms.craftengine.core.block.properties.type.DoorHinge; +import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.Player; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java index 723af40e4..a60b2597f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoubleHighBlockBehavior.java @@ -9,7 +9,7 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.DoubleBlockHalf; +import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.ResourceConfigUtils; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java index 597470895..dae5a50b3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java @@ -11,7 +11,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.AnchorType; +import net.momirealms.craftengine.core.block.properties.type.AnchorType; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java index 3eaf4efcd..f44f55c3a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java @@ -10,7 +10,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.SlabType; +import net.momirealms.craftengine.core.block.properties.type.SlabType; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.behavior.BlockBoundItemBehavior; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java index 7d7498c36..4259fd8e4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SofaBlockBehavior.java @@ -10,7 +10,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.SofaShape; +import net.momirealms.craftengine.core.block.properties.type.SofaShape; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StairsBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StairsBlockBehavior.java index 2ad532eed..1fa475582 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StairsBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StairsBlockBehavior.java @@ -10,8 +10,8 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.SingleBlockHalf; -import net.momirealms.craftengine.core.block.state.properties.StairsShape; +import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf; +import net.momirealms.craftengine.core.block.properties.type.StairsShape; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java index 71db218b5..e945d546c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java @@ -15,7 +15,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.properties.SingleBlockHalf; +import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/StairsShapeUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/StairsShapeUtils.java index dde8b33f7..bd6175bcd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/StairsShapeUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/StairsShapeUtils.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.core.block.state.properties.StairsShape; +import net.momirealms.craftengine.core.block.properties.type.StairsShape; public final class StairsShapeUtils { private StairsShapeUtils() {} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java index 0c8d61650..22402fadc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java @@ -7,11 +7,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.core.block.BlockRegistryMirror; -import net.momirealms.craftengine.core.block.BlockStateWrapper; -import net.momirealms.craftengine.core.block.CustomBlock; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.state.StatePropertyAccessor; +import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.ExistingBlock; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index c9bda8d59..ef7d9d282 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -18,7 +18,9 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.cache.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.BlockStateAllocator; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -68,8 +70,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final Map> appearanceToRealState = new Int2ObjectOpenHashMap<>(); // 用于note_block:0这样格式的自动分配 protected final Map> blockStateArranger = new HashMap<>(); - // 根据registry id找note_block:x中的x值 - protected final Map reversedBlockStateArranger = new HashMap<>(); // 全方块状态映射文件,用于网络包映射 protected final int[] blockStateMappings; // 原版方块状态数量 @@ -82,6 +82,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final ImmutableBlockState[] immutableBlockStates; // 原版方块的属性缓存 protected final BlockSettings[] vanillaBlockSettings; + // 倒推缓存 + protected final BlockStateCandidate[] reversedBlockStateArranger; // 临时存储哪些视觉方块被使用了 protected final Set tempVisualBlockStatesInUse = new HashSet<>(); protected final Set tempVisualBlocksInUse = new HashSet<>(); @@ -99,6 +101,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.vanillaBlockSettings = new BlockSettings[vanillaBlockStateCount]; this.immutableBlockStates = new ImmutableBlockState[customBlockCount]; this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount]; + this.reversedBlockStateArranger = new BlockStateCandidate[vanillaBlockStateCount]; Arrays.fill(this.blockStateMappings, -1); } @@ -127,10 +130,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.modBlockStateOverrides.clear(); this.byId.clear(); this.blockStateArranger.clear(); - this.reversedBlockStateArranger.clear(); this.appearanceToRealState.clear(); Arrays.fill(this.blockStateMappings, -1); Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); + Arrays.fill(this.reversedBlockStateArranger, null); } @Override @@ -272,9 +275,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem continue; } AbstractBlockManager.this.blockStateMappings[beforeState.registryId()] = afterState.registryId(); - List blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(getBlockOwnerId(beforeState), k -> new ArrayList<>()); + Key blockOwnerId = getBlockOwnerId(beforeState); + List blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>()); blockStateWrappers.add(beforeState); - AbstractBlockManager.this.reversedBlockStateArranger.put(beforeState.registryId(), blockStateWrappers.size() - 1); + AbstractBlockManager.this.reversedBlockStateArranger[beforeState.registryId()] = blockParser.createVisualBlockCandidate(beforeState); + } exceptionCollector.throwIfPresent(); } @@ -283,8 +288,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem public class BlockParser implements IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; private final IdAllocator internalIdAllocator; - private final Map appearanceIdAllocators = new HashMap<>(); private final List pendingConfigSections = new ArrayList<>(); + private final BlockStateAllocator[] visualBlockStateAllocators = new BlockStateAllocator[AutoStateGroup.values().length]; public BlockParser() { this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json")); @@ -294,6 +299,29 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.pendingConfigSections.add(section); } + @Nullable + public BlockStateCandidate createVisualBlockCandidate(BlockStateWrapper blockState) { + List groups = AutoStateGroup.findGroups(blockState); + if (!groups.isEmpty()) { + BlockStateCandidate candidate = new BlockStateCandidate(blockState); + for (AutoStateGroup group : groups) { + getOrCreateBlockStateAllocator(group).addCandidate(candidate); + } + return candidate; + } + return null; + } + + private BlockStateAllocator getOrCreateBlockStateAllocator(AutoStateGroup group) { + int index = group.ordinal(); + BlockStateAllocator visualBlockStateAllocator = this.visualBlockStateAllocators[index]; + if (visualBlockStateAllocator == null) { + visualBlockStateAllocator = new BlockStateAllocator(); + this.visualBlockStateAllocators[index] = visualBlockStateAllocator; + } + return visualBlockStateAllocator; + } + @Override public void postProcess() { this.internalIdAllocator.processPendingAllocations(); @@ -323,23 +351,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.pendingConfigSections.clear(); } - @Nullable - public IdAllocator getOrCreateAppearanceIdAllocator(Key type) { - if (!AbstractBlockManager.this.blockStateArranger.containsKey(type)) { - return null; - } - return this.appearanceIdAllocators.computeIfAbsent(type, k -> { - IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("visual-block-states").resolve(k.value() + ".json")); - newAllocator.reset(0, AbstractBlockManager.this.blockStateArranger.get(type).size()); - try { - newAllocator.loadFromCache(); - } catch (IOException e) { - AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states cache for block " + k.asString(), e); - } - return newAllocator; - }); - } - @Override public String[] sectionId() { return CONFIG_SECTION_NAME; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java new file mode 100644 index 000000000..fd3f67fbe --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.core.block; + +public abstract class AbstractBlockStateWrapper implements BlockStateWrapper { + protected final Object blockState; + protected final int registryId; + + protected AbstractBlockStateWrapper(Object blockState, int registryId) { + this.blockState = blockState; + this.registryId = registryId; + } + + @Override + public Object literalObject() { + return this.blockState; + } + + @Override + public int registryId() { + return this.registryId; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java new file mode 100644 index 000000000..b79a46f55 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java @@ -0,0 +1,84 @@ +package net.momirealms.craftengine.core.block; + +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Predicate; + +public enum AutoStateGroup { + LEAVES("leaves", + Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), + (w) -> !(boolean) w.getProperty("waterlogged"), 0 + ), + WATERLOGGED_LEAVES( + "waterlogged_leaves", + Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), + (w) -> w.getProperty("waterlogged"), 0 + ), + TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true, 1), + LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached"), 0), + HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached"), 0), + NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true, 0), + BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true, 0), + RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true, 0), + MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true, 0), + MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true, 1), + SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true, 2); + + private final Set blocks; + private final String id; + private final Predicate predicate; + private final int priority; + + AutoStateGroup(String id, Set blocks, Predicate predicate, int priority) { + this.id = id; + this.blocks = blocks; + this.predicate = predicate; + this.priority = priority; + } + + public int priority() { + return priority; + } + + public Set blocks() { + return blocks; + } + + public String id() { + return id; + } + + private static final Map BY_ID = new HashMap<>(); + private static final Map> BY_BLOCKS = new HashMap<>(); + + static { + for (AutoStateGroup group : AutoStateGroup.values()) { + BY_ID.put(group.id(), group); + BY_ID.put(group.id().toUpperCase(Locale.ROOT), group); + for (Key key : group.blocks) { + BY_BLOCKS.computeIfAbsent(key, k -> new ArrayList<>(4)).add(group); + } + } + } + + @Nullable + public static AutoStateGroup byId(String id) { + return BY_ID.get(id); + } + + public static List findGroups(BlockStateWrapper wrapper) { + return findGroups(wrapper.ownerId(), wrapper); + } + + public static List findGroups(Key id, BlockStateWrapper wrapper) { + List groups = BY_BLOCKS.get(id); + if (groups == null) return Collections.emptyList(); + List result = new ArrayList<>(groups.size()); + for (AutoStateGroup group : groups) { + if (group.predicate.test(wrapper)) result.add(group); + } + return result; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java index ce9cf8079..30490bbad 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java @@ -246,6 +246,22 @@ public final class BlockKeys { public static final Key CACTUS = Key.of("minecraft:cactus"); + public static final Key BROWN_MUSHROOM_BLOCK = Key.of("minecraft:brown_mushroom_block"); + public static final Key RED_MUSHROOM_BLOCK = Key.of("minecraft:red_mushroom_block"); + public static final Key MUSHROOM_STEM = Key.of("minecraft:mushroom_stem"); + + public static final Key OAK_LEAVES = Key.of("minecraft:oak_leaves"); + public static final Key SPRUCE_LEAVES = Key.of("minecraft:spruce_leaves"); + public static final Key BIRCH_LEAVES = Key.of("minecraft:birch_leaves"); + public static final Key JUNGLE_LEAVES = Key.of("minecraft:jungle_leaves"); + public static final Key ACACIA_LEAVES = Key.of("minecraft:acacia_leaves"); + public static final Key DARK_OAK_LEAVES = Key.of("minecraft:dark_oak_leaves"); + public static final Key MANGROVE_LEAVES = Key.of("minecraft:mangrove_leaves"); + public static final Key CHERRY_LEAVES = Key.of("minecraft:cherry_leaves"); + public static final Key PALE_OAK_LEAVES = Key.of("minecraft:pale_oak_leaves"); + public static final Key AZALEA_LEAVES = Key.of("minecraft:azalea_leaves"); + public static final Key FLOWERING_AZALEA_LEAVES = Key.of("minecraft:flowering_azalea_leaves"); + public static final List WOODEN_TRAPDOORS = List.of(OAK_TRAPDOOR, SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR, ACACIA_TRAPDOOR, PALE_OAK_TRAPDOOR, DARK_OAK_TRAPDOOR, MANGROVE_TRAPDOOR, JUNGLE_TRAPDOOR); public static final List CHERRY_TRAPDOORS = List.of(CHERRY_TRAPDOOR); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java index eaa50da56..83c34a1f4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java @@ -18,7 +18,7 @@ public class BlockStateHolder { this.propertyMap = new Reference2ObjectArrayMap<>(propertyMap); } - public Holder owner() { + public Holder.Reference owner() { return this.owner; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java index 846900088..0b19c2ee3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java @@ -9,4 +9,10 @@ public interface BlockStateWrapper { int registryId(); Key ownerId(); + + T getProperty(String propertyName); + + boolean hasProperty(String propertyName); + + String getAsString(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/StatePropertyAccessor.java b/core/src/main/java/net/momirealms/craftengine/core/block/StatePropertyAccessor.java similarity index 83% rename from core/src/main/java/net/momirealms/craftengine/core/block/state/StatePropertyAccessor.java rename to core/src/main/java/net/momirealms/craftengine/core/block/StatePropertyAccessor.java index a69d4e24e..3e1a46e3f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/StatePropertyAccessor.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/StatePropertyAccessor.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.block.state; +package net.momirealms.craftengine.core.block; import java.util.Collection; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java index 42753ec45..641c71a72 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.core.block.properties; -import net.momirealms.craftengine.core.block.state.properties.*; +import net.momirealms.craftengine.core.block.properties.type.*; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Registries; @@ -40,7 +40,6 @@ public final class Properties { register(SLAB_TYPE, new EnumProperty.Factory<>(SlabType.class)); register(SOFA_SHAPE, new EnumProperty.Factory<>(SofaShape.class)); register(ANCHOR_TYPE, new EnumProperty.Factory<>(AnchorType.class)); - } public static void register(Key key, PropertyFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/AnchorType.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/AnchorType.java new file mode 100644 index 000000000..26e95f0a5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/AnchorType.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.block.properties.type; + +public enum AnchorType { + FLOOR, + WALL, + CEILING +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoorHinge.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoorHinge.java new file mode 100644 index 000000000..16ef73e49 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoorHinge.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.core.block.properties.type; + +public enum DoorHinge { + LEFT, RIGHT +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoubleBlockHalf.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoubleBlockHalf.java new file mode 100644 index 000000000..1823e76dd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/DoubleBlockHalf.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.core.block.properties.type; + +public enum DoubleBlockHalf { + UPPER, LOWER +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SingleBlockHalf.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SingleBlockHalf.java new file mode 100644 index 000000000..04cee6405 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SingleBlockHalf.java @@ -0,0 +1,5 @@ +package net.momirealms.craftengine.core.block.properties.type; + +public enum SingleBlockHalf { + TOP, BOTTOM +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SlabType.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SlabType.java new file mode 100644 index 000000000..2f1d2e1e5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SlabType.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.block.properties.type; + +public enum SlabType { + TOP, + BOTTOM, + DOUBLE +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SofaShape.java similarity index 53% rename from core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java rename to core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SofaShape.java index a9d717dcb..82ef28632 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SofaShape.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/SofaShape.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.block.state.properties; +package net.momirealms.craftengine.core.block.properties.type; public enum SofaShape { STRAIGHT, diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/StairsShape.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/StairsShape.java similarity index 62% rename from core/src/main/java/net/momirealms/craftengine/core/block/state/properties/StairsShape.java rename to core/src/main/java/net/momirealms/craftengine/core/block/properties/type/StairsShape.java index b0cac6526..4d8616818 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/StairsShape.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/type/StairsShape.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.block.state.properties; +package net.momirealms.craftengine.core.block.properties.type; public enum StairsShape { STRAIGHT, diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java deleted file mode 100644 index 65bec0f74..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AnchorType.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.core.block.state.properties; - -public enum AnchorType { - FLOOR, - WALL, - CEILING -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoorHinge.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoorHinge.java deleted file mode 100644 index 811514a63..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoorHinge.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.momirealms.craftengine.core.block.state.properties; - -public enum DoorHinge { - LEFT, RIGHT -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoubleBlockHalf.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoubleBlockHalf.java deleted file mode 100644 index 1891b3de8..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/DoubleBlockHalf.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.momirealms.craftengine.core.block.state.properties; - -public enum DoubleBlockHalf { - UPPER, LOWER -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SingleBlockHalf.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SingleBlockHalf.java deleted file mode 100644 index c5b3b83e8..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SingleBlockHalf.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.momirealms.craftengine.core.block.state.properties; - -public enum SingleBlockHalf { - TOP, BOTTOM -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SlabType.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SlabType.java deleted file mode 100644 index 09f4fbbfb..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/SlabType.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.core.block.state.properties; - -public enum SlabType { - TOP, - BOTTOM, - DOUBLE -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 3fbeefb07..80ec3ace5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.cache.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index a4c0164c3..e6b46ea9d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -14,7 +14,7 @@ import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.cache.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.pack.model.*; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java new file mode 100644 index 000000000..5669d0b27 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.core.pack.allocator; + +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class BlockStateAllocator { + private final List blockStates = new ArrayList<>(); + private int pointer = 0; + private int max = -1; + + public void addCandidate(BlockStateCandidate state) { + this.blockStates.add(state); + this.max = this.blockStates.size() - 1; + } + + @Nullable + public BlockStateCandidate findNext() { + while (this.pointer < this.max) { + final BlockStateCandidate state = this.blockStates.get(this.pointer); + if (!state.isUsed()) { + return state; + } + this.pointer++; + } + return null; + } + + public void processPendingAllocations() { + + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateCandidate.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateCandidate.java new file mode 100644 index 000000000..abcfdfbba --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateCandidate.java @@ -0,0 +1,24 @@ +package net.momirealms.craftengine.core.pack.allocator; + +import net.momirealms.craftengine.core.block.BlockStateWrapper; + +public class BlockStateCandidate { + private final BlockStateWrapper blockState; + private boolean used = false; + + public BlockStateCandidate(BlockStateWrapper blockState) { + this.blockState = blockState; + } + + public void setUsed() { + this.used = true; + } + + public boolean isUsed() { + return used; + } + + public BlockStateWrapper blockState() { + return blockState; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java similarity index 99% rename from core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java rename to core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java index 5977120db..47c5e6bd3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.pack.cache; +package net.momirealms.craftengine.core.pack.allocator; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java index e5ce7b198..41db9e150 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.core.plugin.context.condition; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.StatePropertyAccessor; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.block.state.StatePropertyAccessor; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/I18NData.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/I18NData.java index a87b62c10..4d8689096 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/I18NData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/I18NData.java @@ -26,7 +26,7 @@ public class I18NData { if (blockOptional.isPresent()) { List states = blockOptional.get().variantProvider().states(); if (states.size() == 1) { - return List.of("block." + stateToRealBlockId(states.get(0))); + return List.of("block." + stateToRealBlockId(states.getFirst())); } else { ArrayList processed = new ArrayList<>(); for (ImmutableBlockState state : states) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index b4e249748..35b1026ef 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -2,16 +2,12 @@ package net.momirealms.craftengine.core.plugin.locale; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; -import net.momirealms.craftengine.core.block.AbstractBlockManager; -import net.momirealms.craftengine.core.font.FontManager; -import net.momirealms.craftengine.core.font.OffsetFont; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.plugin.PluginProperties; import net.momirealms.craftengine.core.plugin.config.*; -import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.plugin.text.minimessage.ImageTag; import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag; import net.momirealms.craftengine.core.plugin.text.minimessage.ShiftTag; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index bc134b433..c17579e65 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -1,15 +1,11 @@ package net.momirealms.craftengine.core.util; import com.google.gson.JsonElement; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentIteratorType; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.minimessage.internal.parser.TokenParser; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONOptions; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java index 3f069b8ec..08a9155e2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java @@ -2,7 +2,6 @@ package net.momirealms.craftengine.core.util; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import java.util.regex.Pattern; import java.util.stream.IntStream; public class CharacterUtils { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java b/core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java index 947bc664d..d6fac39cf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.state.StatePropertyAccessor; +import net.momirealms.craftengine.core.block.StatePropertyAccessor; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.NotNull; diff --git a/gradle.properties b/gradle.properties index 604850d98..1de766a2f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.97 +nms_helper_version=1.0.98 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.34.5 From 48bdf5f4e0505778ae45df136af238b8d6525590 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Mon, 29 Sep 2025 19:49:27 +0800 Subject: [PATCH 218/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E9=85=8D=E7=BD=AE=E6=9C=AA=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../advancement/BukkitAdvancementManager.java | 2 +- .../bukkit/block/BukkitBlockManager.java | 40 ---------- .../block/BukkitCustomBlockStateWrapper.java | 2 +- .../bukkit/loot/BukkitVanillaLootManager.java | 2 +- common-files/src/main/resources/config.yml | 66 +++++++++++----- .../default/configuration/templates.yml | 2 +- .../core/block/AbstractBlockManager.java | 16 ++-- .../core/block/AbstractBlockStateWrapper.java | 5 ++ .../core/block/AbstractCustomBlock.java | 4 +- .../core/block/BlockStateHolder.java | 2 +- .../furniture/AbstractFurnitureManager.java | 4 +- .../core/font/AbstractFontManager.java | 18 +++-- .../core/item/AbstractItemManager.java | 4 +- .../item/recipe/AbstractRecipeManager.java | 2 +- .../core/pack/AbstractPackManager.java | 79 +++---------------- .../core/pack/LoadingSequence.java | 1 + .../plugin/config/AbstractConfigParser.java | 32 ++++++++ .../core/plugin/config/ConfigParser.java | 7 ++ .../plugin/config/IdObjectConfigParser.java | 22 +++++- .../plugin/config/IdSectionConfigParser.java | 38 ++++++++- .../plugin/config/SectionConfigParser.java | 16 +++- .../config/template/TemplateManagerImpl.java | 2 +- .../plugin/context/GlobalVariableManager.java | 2 +- .../gui/category/ItemBrowserManagerImpl.java | 2 +- .../plugin/locale/TranslationManagerImpl.java | 4 +- .../core/sound/AbstractSoundManager.java | 6 +- 26 files changed, 212 insertions(+), 168 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/config/AbstractConfigParser.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java index ee34b3993..e42a03083 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java @@ -106,7 +106,7 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager { } } - public class AdvancementParser implements IdSectionConfigParser { + public class AdvancementParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"advancements", "advancement"}; @Override 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 304156691..36c421336 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 @@ -75,7 +75,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { public void init() { this.initMirrorRegistry(); this.initFireBlock(); - this.initVanillaBlockSettings(); this.deceiveBukkitRegistry(); this.markVanillaNoteBlocks(); Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); @@ -94,7 +93,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void unload() { super.unload(); - Arrays.fill(this.blockStateMappings, -1); this.previousClientBoundTags = this.clientBoundTags; this.clientBoundTags = new HashMap<>(); for (DelegatingBlock block : this.burnableBlocks) { @@ -221,44 +219,6 @@ public final class BukkitBlockManager extends AbstractBlockManager { } } - private void initVanillaBlockSettings() { - try { - for (int i = 0; i < this.vanillaBlockStateCount; i++) { - Object blockState = BlockStateUtils.idToBlockState(i); - // 确保缓存已被激活 - CoreReflections.method$BlockStateBase$initCache.invoke(blockState); - BlockSettings settings = BlockSettings.of() - .pushReaction(PushReaction.VALUES[((Enum) CoreReflections.field$BlockStateBase$pushReaction.get(blockState)).ordinal()]) - .mapColor(MapColor.get(CoreReflections.field$MapColor$id.getInt(CoreReflections.field$BlockStateBase$mapColor.get(blockState)))) - .canOcclude(FastNMS.INSTANCE.method$BlockStateBase$canOcclude(blockState) ? Tristate.TRUE : Tristate.FALSE) - .isRandomlyTicking(CoreReflections.field$BlockStateBase$isRandomlyTicking.getBoolean(blockState)) - .hardness(CoreReflections.field$BlockStateBase$hardness.getFloat(blockState)) - .replaceable(CoreReflections.field$BlockStateBase$replaceable.getBoolean(blockState)) - .burnable(CoreReflections.field$BlockStateBase$burnable.getBoolean(blockState)) - .luminance(CoreReflections.field$BlockStateBase$lightEmission.getInt(blockState)) - .instrument(Instrument.VALUES[((Enum) CoreReflections.field$BlockStateBase$instrument.get(blockState)).ordinal()]) - .pushReaction(PushReaction.VALUES[((Enum) CoreReflections.field$BlockStateBase$pushReaction.get(blockState)).ordinal()]); - Object block = BlockStateUtils.getBlockOwner(blockState); - settings.resistance(CoreReflections.field$BlockBehaviour$explosionResistance.getFloat(block)) - .friction(CoreReflections.field$BlockBehaviour$friction.getFloat(block)) - .speedFactor(CoreReflections.field$BlockBehaviour$speedFactor.getFloat(block)) - .jumpFactor(CoreReflections.field$BlockBehaviour$jumpFactor.getFloat(block)) - .sounds(toBlockSounds(CoreReflections.field$BlockBehaviour$soundType.get(block))); - if (VersionHelper.isOrAbove1_21_2()) { - settings.blockLight(CoreReflections.field$BlockStateBase$lightBlock.getInt(blockState)); - settings.propagatesSkylightDown(CoreReflections.field$BlockStateBase$propagatesSkylightDown.getBoolean(blockState) ? Tristate.TRUE : Tristate.FALSE); - } else { - Object cache = CoreReflections.field$BlockStateBase$cache.get(blockState); - settings.blockLight(CoreReflections.field$BlockStateBase$Cache$lightBlock.getInt(cache)); - settings.propagatesSkylightDown(CoreReflections.field$BlockStateBase$Cache$propagatesSkylightDown.getBoolean(cache) ? Tristate.TRUE : Tristate.FALSE); - } - this.vanillaBlockSettings[i] = settings; - } - } catch (Exception e) { - this.plugin.logger().warn("Failed to initialize vanilla block settings", e); - } - } - @Override protected void applyPlatformSettings(ImmutableBlockState state) { DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java index f67d21749..f336c1cbb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java @@ -16,7 +16,7 @@ public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper { @Override public Key ownerId() { - return getImmutableBlockState().map(state -> state.owner().key().location()).orElseGet(() -> BlockStateUtils.getBlockOwnerIdFromState(super.blockState)); + return getImmutableBlockState().map(state -> state.owner().value().id()).orElseGet(() -> BlockStateUtils.getBlockOwnerIdFromState(super.blockState)); } @SuppressWarnings("unchecked") diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java index 805808e92..d38e58a24 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java @@ -91,7 +91,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme return this.vanillaLootParser; } - public class VanillaLootParser implements IdSectionConfigParser { + public class VanillaLootParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"}; @Override diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 1ba1c55a3..ee7b4e559 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -145,14 +145,36 @@ resource-pack: item: # [Premium Exclusive] - # Make custom-model-data and item-model clientside by default + # Makes custom-model-data and item-model client-side by default. + # + # This provides several benefits. For example, you can update model values + # dynamically without causing inconsistencies for players' existing items. + # + # The main drawback is that plugins relying on custom-model-data for item + # identification will not work correctly, as this data is not present in + # the server-side item stack. + # + # You can override this global setting per item using the + # client-bound-model option. client-bound-model: true - # Add a tag on custom name and lore + # When enabled (recommended), this option adds both custom-model-data + # and an item-model to optimize client-side rendering. + # + # If disabled, the system falls back to using only custom-model-data. + # You can override this behavior by setting the item-model option + # on a per-item basis. + # + # This option only works if your resource pack supports 1.21.1 or below + always-use-item-model: true + # Since Minecraft renders lore text in italics by default, you can + # optionally prefix any lore with to remove the italic formatting. non-italic-tag: false - # Determines when to trigger the item updater - # This feature may incur some performance overhead. Please do not enable it unless necessary. - # Correct use case: When you designed incorrect weapon attributes and need to update the values for items already held by players. - # Wrong use case: When you want to update an item's name and lore to a newer version (In this case you should use client-bound-data instead of the item updater) + # Defines the trigger condition for the item updater. + # + # Warning: This operation is performance-intensive. Enable only if needed. + # + # Purpose: Reserved for correcting faults on existing player items. + # Not intended for updating names/lore; use 'client-bound-data' for those changes. update-triggers: click-in-inventory: false # this option won't work for players in creative mode drop: false @@ -175,29 +197,31 @@ equipment: humanoid-leggings: minecraft:trims/entity/humanoid_leggings/chainmail block: - # This decides the amount of real blocks on serverside. Requires a restart to apply. - serverside-blocks: 2025 + # This decides the amount of real blocks on serverside. You should only consider increasing this value when your server state is insufficient. + # It is recommended to increase it by 500 each time. This option requires a restart to apply. + serverside-blocks: 2000 # Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience. sound-system: enable: true - # In Adventure Mode, players need the correct tool to break custom blocks. - # Vanilla clients DO NOT recognize custom block IDs (e.g., craftengine:note_block_0). + # Adventure mode requires correct tools to break custom blocks. + # Vanilla clients cannot recognize custom block IDs (e.g., craftengine:custom_100). # - # - When ENABLED: - # - Players can break custom blocks if their tools can mine their VANILLA EQUIVALENTS. - # Example: A tool for "note_block" can break "craftengine:note_block_0". + # ENABLED: + # - Tools that can break vanilla equivalents also break custom variants. + # Example: A "note_block" tool breaks custom blocks based on note blocks # - # - When DISABLED: - # ⚠️ WARNING: - # - Server MUST list ACTUAL CUSTOM BLOCK IDs in item's `can_break` component. - # - Sending custom IDs (e.g., craftengine:note_block_0) to vanilla clients WILL CRASH THEM! - # ✅ Solution: - # - Use `client-bound-data` to safely sync custom block data to clients. + # DISABLED: + # ⚠️ Server MUST specify SERVERSIDE CUSTOM BLOCK IDs in item's `can_break`. + # ⚠️ Sending custom block IDs to vanilla clients WILL CAUSE CRASHES! + # ✅ Recommended: Use `client-bound-data` for safe client synchronization. simplify-adventure-break-check: false # Similar to the option above, but designed for block placement simplify-adventure-place-check: false - # Whether plugin should predict the next block to break - # This can help improve mining experience to some extent at the cost of performance + # Uses raycasting to predict the player's next block break, + # enabling pre-calculation of mining speed attributes. + + # Enables block break prediction. + # Enhances mining responsiveness with moderate performance cost. predict-breaking: enable: false interval: 10 diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 044ccbf3a..5912b31f5 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -933,7 +933,7 @@ templates#block_states: resistance: 1200.0 burnable: false fluid-state: water - distance=7: + distance=7,persistent=false: settings: is-randomly-ticking: true # trapdoor block diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index ef7d9d282..5d5b66231 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -19,8 +19,8 @@ import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.pack.ResourceLocation; import net.momirealms.craftengine.core.pack.allocator.BlockStateAllocator; -import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -80,8 +80,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final Object[] customBlockHolders; // 自定义状态列表,会随着重载变化 protected final ImmutableBlockState[] immutableBlockStates; - // 原版方块的属性缓存 - protected final BlockSettings[] vanillaBlockSettings; // 倒推缓存 protected final BlockStateCandidate[] reversedBlockStateArranger; // 临时存储哪些视觉方块被使用了 @@ -98,7 +96,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.customBlocks = new DelegatingBlock[customBlockCount]; this.customBlockHolders = new Object[customBlockCount]; this.customBlockStates = new DelegatingBlockState[customBlockCount]; - this.vanillaBlockSettings = new BlockSettings[vanillaBlockStateCount]; this.immutableBlockStates = new ImmutableBlockState[customBlockCount]; this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount]; this.reversedBlockStateArranger = new BlockStateCandidate[vanillaBlockStateCount]; @@ -236,7 +233,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem @NotNull Map>> events, @Nullable LootTable lootTable); - public class BlockStateMappingParser implements SectionConfigParser { + public class BlockStateMappingParser extends SectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"}; @Override @@ -285,7 +282,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - public class BlockParser implements IdSectionConfigParser { + public class BlockParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; private final IdAllocator internalIdAllocator; private final List pendingConfigSections = new ArrayList<>(); @@ -618,9 +615,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } // 拆分方块id与属性 - String blockState = blockStateWrapper.toString(); - Key blockId = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}'))); - String propertyNBT = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']')); + String blockState = blockStateWrapper.getAsString(); + int firstIndex = blockState.indexOf('['); + Key blockId = firstIndex == -1 ? Key.of(blockState) : Key.of(blockState.substring(0, firstIndex)); + String propertyNBT = firstIndex == -1 ? "" : blockState.substring(firstIndex + 1, blockState.lastIndexOf(']')); // 结合variants JsonElement combinedVariant = GsonHelper.combine(variants); Map overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java index fd3f67fbe..7ce0ad545 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java @@ -18,4 +18,9 @@ public abstract class AbstractBlockStateWrapper implements BlockStateWrapper { public int registryId() { return this.registryId; } + + @Override + public String toString() { + return this.blockState.toString(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java index 298de8ccd..01e818042 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -19,6 +19,7 @@ import java.util.*; import java.util.function.BiFunction; public abstract class AbstractCustomBlock implements CustomBlock { + protected final Key id; protected final Holder.Reference holder; protected final BlockStateVariantProvider variantProvider; protected final BiFunction placementFunction; @@ -34,6 +35,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { @NotNull Map>> events, @Nullable LootTable lootTable ) { + this.id = holder.key().location(); this.holder = holder; this.lootTable = lootTable; this.events = events; @@ -85,7 +87,7 @@ public abstract class AbstractCustomBlock implements CustomBlock { @NotNull @Override public final Key id() { - return this.holder.key().location(); + return this.id; } public void setBehavior(@Nullable BlockBehavior behavior) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java index 83c34a1f4..eaa50da56 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java @@ -18,7 +18,7 @@ public class BlockStateHolder { this.propertyMap = new Reference2ObjectArrayMap<>(propertyMap); } - public Holder.Reference owner() { + public Holder owner() { return this.owner; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index 008d83b52..8a2a4ad05 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -76,7 +76,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { protected abstract CustomFurniture.Builder furnitureBuilder(); - public class FurnitureParser implements IdSectionConfigParser { + public class FurnitureParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" }; private final List pendingConfigSections = new ArrayList<>(); @@ -110,7 +110,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { @SuppressWarnings("unchecked") @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { - if (byId.containsKey(id)) { + if (AbstractFurnitureManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.furniture.duplicate"); } EnumMap placements = new EnumMap<>(AnchorType.class); diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 80ec3ace5..f8ed7e7c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -232,7 +232,7 @@ public abstract class AbstractFontManager implements FontManager { emoji.content(), PlayerOptionalContext.of(player, ContextHolder.builder() .withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage()) - .withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0)) + .withParameter(EmojiParameters.KEYWORD, emoji.keywords().getFirst()) ).tagResolvers()) ); if (emojis.size() >= maxTimes) break; @@ -390,7 +390,7 @@ public abstract class AbstractFontManager implements FontManager { return this.fonts.computeIfAbsent(key, Font::new); } - public class EmojiParser implements IdSectionConfigParser { + public class EmojiParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"emoji", "emojis"}; @Override @@ -457,7 +457,7 @@ public abstract class AbstractFontManager implements FontManager { } } - public class ImageParser implements IdSectionConfigParser { + public class ImageParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"images", "image"}; private final Map idAllocators = new HashMap<>(); @@ -577,7 +577,11 @@ public abstract class AbstractFontManager implements FontManager { codepoints = CharacterUtils.charsToCodePoints(charString.toCharArray()); } for (int j = 0; j < codepoints.length; j++) { - futureCodepoints.add(allocator.assignFixedId(id.asString() + ":" + i + ":" + j, codepoints[j])); + if (codepoints[j] == 0) { + futureCodepoints.add(CompletableFuture.completedFuture(0)); + } else { + futureCodepoints.add(allocator.assignFixedId(id.asString() + ":" + i + ":" + j, codepoints[j])); + } } if (tempColumns == -1) { tempColumns = codepoints.length; @@ -607,7 +611,11 @@ public abstract class AbstractFontManager implements FontManager { } columns = codepoints.length; for (int i = 0; i < codepoints.length; i++) { - futureCodepoints.add(allocator.assignFixedId(id.asString() + ":0:" + i, codepoints[i])); + if (codepoints[i] == 0) { + futureCodepoints.add(CompletableFuture.completedFuture(0)); + } else { + futureCodepoints.add(allocator.assignFixedId(id.asString() + ":0:" + i, codepoints[i])); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index e6b46ea9d..b1d0539ed 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -270,7 +270,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected abstract void registerArmorTrimPattern(Collection equipments); - public class EquipmentParser implements IdSectionConfigParser { + public class EquipmentParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"equipments", "equipment"}; @Override @@ -313,7 +313,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } } - public class ItemParser implements IdSectionConfigParser { + public class ItemParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"}; private final Map idAllocators = new HashMap<>(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java index 6a9fc4273..22c095387 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java @@ -121,7 +121,7 @@ public abstract class AbstractRecipeManager implements RecipeManager { return true; } - public class RecipeParser implements IdSectionConfigParser { + public class RecipeParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"recipes", "recipe"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c0b51bace..c4ed3e352 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -86,6 +86,7 @@ public abstract class AbstractPackManager implements PackManager { private final BiConsumer generationEventDispatcher; private final Map loadedPacks = new HashMap<>(); private final Map sectionParsers = new HashMap<>(); + private final TreeSet sortedParsers = new TreeSet<>(); private final JsonObject vanillaAtlas; private Map cachedConfigFiles = Collections.emptyMap(); private Map cachedAssetFiles = Collections.emptyMap(); @@ -293,6 +294,7 @@ public abstract class AbstractPackManager implements PackManager { for (String id : parser.sectionId()) { this.sectionParsers.put(id, parser); } + this.sortedParsers.add(parser); return true; } @@ -548,10 +550,9 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/models/block/custom/fence_side.json"); } - private TreeMap> updateCachedConfigFiles() { - TreeMap> cachedConfigs = new TreeMap<>(); + private void updateCachedConfigFiles() { Map previousFiles = this.cachedConfigFiles; - this.cachedConfigFiles = new Object2ObjectOpenHashMap<>(32); + this.cachedConfigFiles = new HashMap<>(64, 0.5f); for (Pack pack : loadedPacks()) { if (!pack.enabled()) continue; Path configurationFolderPath = pack.configurationFolder(); @@ -596,9 +597,7 @@ public abstract class AbstractPackManager implements PackManager { } } for (Map.Entry entry : cachedFile.config().entrySet()) { - processConfigEntry(entry, path, cachedFile.pack(), (p, c) -> - cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c) - ); + processConfigEntry(entry, path, cachedFile.pack(), ConfigParser::addConfig); } } return FileVisitResult.CONTINUE; @@ -608,76 +607,20 @@ public abstract class AbstractPackManager implements PackManager { this.plugin.logger().severe("Error while reading config file", e); } } - return cachedConfigs; } private void loadResourceConfigs(Predicate predicate) { long o1 = System.nanoTime(); - TreeMap> cachedConfigs = this.updateCachedConfigFiles(); + this.updateCachedConfigFiles(); long o2 = System.nanoTime(); this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms"); - for (Map.Entry> entry : cachedConfigs.entrySet()) { - ConfigParser parser = entry.getKey(); + for (ConfigParser parser : this.sortedParsers) { if (!predicate.test(parser)) continue; long t1 = System.nanoTime(); parser.preProcess(); - switch (parser) { - case SectionConfigParser configParser -> { - for (CachedConfigSection cached : entry.getValue()) { - ResourceConfigUtils.runCatching( - cached.filePath(), - cached.prefix(), - () -> configParser.parseSection(cached.pack(), cached.filePath(), cached.config()), - () -> GsonHelper.get().toJson(cached.config()) - ); - } - } - case IdObjectConfigParser configParser -> { - for (CachedConfigSection cached : entry.getValue()) { - for (Map.Entry configEntry : cached.config().entrySet()) { - String key = configEntry.getKey(); - Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); - String node = cached.prefix() + "." + key; - ResourceConfigUtils.runCatching( - cached.filePath(), - node, - () -> configParser.parseObject(cached.pack(), cached.filePath(), node, id, configEntry.getValue()), - () -> GsonHelper.get().toJson(configEntry.getValue()) - ); - } - } - } - case IdSectionConfigParser configParser -> { - for (CachedConfigSection cached : entry.getValue()) { - for (Map.Entry configEntry : cached.config().entrySet()) { - String key = configEntry.getKey(); - Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); - if (!(configEntry.getValue() instanceof Map section)) { - TranslationManager.instance().log("warning.config.structure.not_section", - cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); - continue; - } - Map config = castToMap(section, false); - if ((boolean) config.getOrDefault("debug", false)) { - this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config))); - } - if (!(boolean) config.getOrDefault("enable", true)) { - continue; - } - String node = cached.prefix() + "." + key; - ResourceConfigUtils.runCatching( - cached.filePath(), - node, - () -> configParser.parseSection(cached.pack(), cached.filePath(), node, id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)), - () -> GsonHelper.get().toJson(section) - ); - } - } - } - default -> { - } - } + parser.loadAll(); parser.postProcess(); + parser.clear(); long t2 = System.nanoTime(); this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms"); } @@ -2252,9 +2195,9 @@ public abstract class AbstractPackManager implements PackManager { } private List>> updateCachedAssets(@NotNull PackCacheData cacheData, @Nullable FileSystem fs) throws IOException { - Map> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); + Map> conflictChecker = new HashMap<>(Math.max(128, this.cachedAssetFiles.size()), 0.6f); Map previousFiles = this.cachedAssetFiles; - this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); + this.cachedAssetFiles = new HashMap<>(Math.max(128, this.cachedAssetFiles.size()), 0.6f); List folders = new ArrayList<>(); folders.addAll(loadedPacks().stream() diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java index 46e3998aa..f8928a7ef 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.pack; public final class LoadingSequence { private LoadingSequence() {} + // 模板第一位 public static final int TEMPLATE = 0; public static final int BLOCK_STATE_MAPPING = 10; public static final int GLOBAL_VAR = 20; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/AbstractConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/AbstractConfigParser.java new file mode 100644 index 000000000..cbbc609fb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/AbstractConfigParser.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.plugin.config; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.momirealms.craftengine.core.pack.CachedConfigSection; + +public abstract class AbstractConfigParser implements ConfigParser { + protected final ObjectArrayList configStorage; + + public AbstractConfigParser() { + this.configStorage = new ObjectArrayList<>(); + } + + @Override + public void addConfig(CachedConfigSection section) { + this.configStorage.add(section); + } + + @Override + public void loadAll() { + Object[] elements = this.configStorage.elements(); + for (int i = 0, size = this.configStorage.size(); i < size; i++) { + parseSection((CachedConfigSection) elements[i]); + } + } + + @Override + public void clear() { + this.configStorage.clear(); + } + + protected abstract void parseSection(CachedConfigSection section); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java index 48ba108f9..5fc06c91e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.plugin.config; +import net.momirealms.craftengine.core.pack.CachedConfigSection; import org.jetbrains.annotations.NotNull; public interface ConfigParser extends Comparable { @@ -18,4 +19,10 @@ public interface ConfigParser extends Comparable { default void preProcess() { } + + void addConfig(CachedConfigSection section); + + void loadAll(); + + void clear(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java index 9238a31cc..2a7f49f8d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdObjectConfigParser.java @@ -1,13 +1,31 @@ package net.momirealms.craftengine.core.plugin.config; +import net.momirealms.craftengine.core.pack.CachedConfigSection; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.nio.file.Path; +import java.util.Map; -public interface IdObjectConfigParser extends ConfigParser { +public abstract class IdObjectConfigParser extends AbstractConfigParser { - default void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException { + @Override + protected void parseSection(CachedConfigSection cached) { + for (Map.Entry configEntry : cached.config().entrySet()) { + String key = configEntry.getKey(); + Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); + String node = cached.prefix() + "." + key; + ResourceConfigUtils.runCatching( + cached.filePath(), + node, + () -> parseObject(cached.pack(), cached.filePath(), node, id, configEntry.getValue()), + () -> GsonHelper.get().toJson(configEntry.getValue()) + ); + } } + + protected abstract void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java index e14e42ba2..ff5828380 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java @@ -1,14 +1,48 @@ package net.momirealms.craftengine.core.plugin.config; +import net.momirealms.craftengine.core.pack.CachedConfigSection; import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.nio.file.Path; import java.util.Map; -public interface IdSectionConfigParser extends ConfigParser { +import static net.momirealms.craftengine.core.util.MiscUtils.castToMap; - default void parseSection(Pack pack, Path path, String node, Key id, Map section) throws LocalizedException { +public abstract class IdSectionConfigParser extends AbstractConfigParser { + + @Override + protected void parseSection(CachedConfigSection cached) { + for (Map.Entry configEntry : cached.config().entrySet()) { + String key = configEntry.getKey(); + Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); + if (!(configEntry.getValue() instanceof Map section)) { + TranslationManager.instance().log("warning.config.structure.not_section", + cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); + continue; + } + Map config = castToMap(section, false); + if ((boolean) config.getOrDefault("debug", false)) { + CraftEngine.instance().logger().info(GsonHelper.get().toJson(CraftEngine.instance().templateManager().applyTemplates(id, config))); + } + if (!(boolean) config.getOrDefault("enable", true)) { + continue; + } + String node = cached.prefix() + "." + key; + ResourceConfigUtils.runCatching( + cached.filePath(), + node, + () -> parseSection(cached.pack(), cached.filePath(), node, id, MiscUtils.castToMap(CraftEngine.instance().templateManager().applyTemplates(id, config), false)), + () -> GsonHelper.get().toJson(section) + ); + } } + + protected abstract void parseSection(Pack pack, Path path, String node, Key id, Map section) throws LocalizedException; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java index 871d1674f..626a9ffc8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/SectionConfigParser.java @@ -1,13 +1,25 @@ package net.momirealms.craftengine.core.plugin.config; +import net.momirealms.craftengine.core.pack.CachedConfigSection; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.nio.file.Path; import java.util.Map; -public interface SectionConfigParser extends ConfigParser { +public abstract class SectionConfigParser extends AbstractConfigParser { - default void parseSection(Pack pack, Path path, Map section) throws LocalizedException { + @Override + protected void parseSection(CachedConfigSection cached) { + ResourceConfigUtils.runCatching( + cached.filePath(), + cached.prefix(), + () -> parseSection(cached.pack(), cached.filePath(), cached.config()), + () -> GsonHelper.get().toJson(cached.config()) + ); } + + protected abstract void parseSection(Pack pack, Path path, Map section) throws LocalizedException; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java index 33c2622f4..3356a760f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java @@ -37,7 +37,7 @@ public class TemplateManagerImpl implements TemplateManager { return this.templateParser; } - public class TemplateParser implements IdObjectConfigParser { + public class TemplateParser extends IdObjectConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"templates", "template"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java index ee677e725..078a8c600 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java @@ -36,7 +36,7 @@ public class GlobalVariableManager implements Manageable { return this.parser; } - public class GlobalVariableParser implements IdObjectConfigParser { + public class GlobalVariableParser extends IdObjectConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"global-variables", "global-variable"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index ed34e31a9..b4d6d30f4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -96,7 +96,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { return Optional.ofNullable(this.byId.get(key)); } - public class CategoryParser implements IdSectionConfigParser { + public class CategoryParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"categories", "category"}; @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index 35b1026ef..98db56742 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -246,7 +246,7 @@ public class TranslationManagerImpl implements TranslationManager { } } - public class I18NParser implements IdSectionConfigParser { + public class I18NParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"i18n", "internationalization", "translation", "translations"}; @Override @@ -277,7 +277,7 @@ public class TranslationManagerImpl implements TranslationManager { } } - public class LangParser implements IdSectionConfigParser { + public class LangParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"lang", "language", "languages"}; private final Function langProcessor = s -> { Component deserialize = AdventureHelper.miniMessage().deserialize(AdventureHelper.legacyToMiniMessage(s), ShiftTag.INSTANCE, ImageTag.INSTANCE); diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java index 2e6def245..3c5886417 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java @@ -66,8 +66,8 @@ public abstract class AbstractSoundManager implements SoundManager { protected abstract void registerSounds(Collection sounds); - public class SongParser implements IdSectionConfigParser { - public static final String[] CONFIG_SECTION_NAME = new String[] {"jukebox_songs", "jukebox_song", "jukebox-songs", "jukebox-song"}; + public class SongParser extends IdSectionConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[] {"jukebox-songs", "jukebox-song", "jukebox_songs", "jukebox_song"}; @Override public int loadingSequence() { @@ -93,7 +93,7 @@ public abstract class AbstractSoundManager implements SoundManager { } } - public class SoundParser implements IdSectionConfigParser { + public class SoundParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"sounds", "sound"}; @Override From a85b94392ee9945c645bc10e2e466467865d26de Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 01:14:40 +0800 Subject: [PATCH 219/226] =?UTF-8?q?=E5=A4=96=E8=A7=82=E6=96=B9=E5=9D=97?= =?UTF-8?q?=E5=88=86=E9=85=8D=20=E6=9C=AA=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/api/BukkitAdaptors.java | 48 ++- .../bukkit/api/CraftEngineImages.java | 40 +++ .../bukkit/block/BukkitBlockManager.java | 17 +- .../bukkit/font/BukkitFontManager.java | 6 + .../bukkit/plugin/BukkitCraftEngine.java | 5 + .../plugin/command/BukkitCommandManager.java | 3 +- .../feature/DebugCleanCacheCommand.java | 84 +++++ common-files/src/main/resources/commands.yml | 7 + .../configuration/blocks/chessboard_block.yml | 8 +- .../configuration/blocks/chinese_lantern.yml | 2 +- .../configuration/blocks/copper_coil.yml | 4 +- .../blocks/ender_pearl_flower.yml | 6 +- .../configuration/blocks/fairy_flower.yml | 2 +- .../configuration/blocks/flame_cane.yml | 2 +- .../configuration/blocks/gunpowder_block.yml | 4 +- .../configuration/blocks/palm_tree.yml | 8 +- .../default/configuration/blocks/pebble.yml | 6 +- .../default/configuration/blocks/reed.yml | 2 +- .../configuration/blocks/safe_block.yml | 16 +- .../configuration/blocks/topaz_ore.yml | 2 +- .../default/configuration/templates.yml | 246 +------------ .../src/main/resources/translations/en.yml | 2 + .../core/block/AbstractBlockManager.java | 332 +++++++++++------- .../core/block/AbstractBlockStateWrapper.java | 12 + .../core/block/AutoStateGroup.java | 65 +++- .../craftengine/core/block/BlockKeys.java | 10 + .../core/block/BlockStateWrapper.java | 8 +- .../core/font/AbstractFontManager.java | 10 + .../craftengine/core/font/FontManager.java | 4 + .../core/item/AbstractItemManager.java | 8 + .../core/pack/AbstractPackManager.java | 10 +- .../pack/allocator/BlockStateAllocator.java | 33 -- .../core/pack/allocator/IdAllocator.java | 6 +- .../allocator/VisualBlockStateAllocator.java | 152 ++++++++ .../allocator/cache/AllocationCacheFile.java | 62 ++++ .../allocator/cache/CacheFileStorage.java | 98 ++++++ .../pack/allocator/cache/CacheFileType.java | 39 ++ .../pack/allocator/cache/CacheSerializer.java | 51 +++ .../pack/allocator/cache/CacheStorage.java | 32 ++ 39 files changed, 975 insertions(+), 477 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java index 6dcd0167a..c667f832e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java @@ -3,37 +3,63 @@ package net.momirealms.craftengine.bukkit.api; import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.world.WorldPosition; -import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; public final class BukkitAdaptors { private BukkitAdaptors() {} - public static BukkitServerPlayer adapt(final Player player) { + /** + * Adapts a Bukkit Player to a CraftEngine BukkitServerPlayer. + * This provides access to CraftEngine-specific player functionality and data. + * + * @param player the Bukkit Player to adapt, must not be null + * @return a non-null BukkitServerPlayer instance wrapping the provided player + */ + @NotNull + public static BukkitServerPlayer adapt(@NotNull final Player player) { return BukkitCraftEngine.instance().adapt(player); } - public static BukkitWorld adapt(final World world) { + /** + * Adapts a Bukkit World to a CraftEngine BukkitWorld. + * This enables CraftEngine world operations on Bukkit world instances. + * + * @param world the Bukkit World to adapt, must not be null + * @return a non-null BukkitWorld instance wrapping the provided world + */ + @NotNull + public static BukkitWorld adapt(@NotNull final World world) { return new BukkitWorld(world); } - public static BukkitEntity adapt(final Entity entity) { + /** + * Adapts a Bukkit Entity to a CraftEngine BukkitEntity. + * This provides CraftEngine entity functionality for Bukkit entities. + * + * @param entity the Bukkit Entity to adapt, must not be null + * @return a non-null BukkitEntity instance wrapping the provided entity + */ + @NotNull + public static BukkitEntity adapt(@NotNull final Entity entity) { return new BukkitEntity(entity); } - public static BukkitExistingBlock adapt(final Block block) { + /** + * Adapts a Bukkit Block to a CraftEngine BukkitExistingBlock. + * This enables CraftEngine block operations on Bukkit block instances. + * + * @param block the Bukkit Block to adapt, must not be null + * @return a non-null BukkitExistingBlock instance wrapping the provided block + */ + @NotNull + public static BukkitExistingBlock adapt(@NotNull final Block block) { return new BukkitExistingBlock(block); } - - public static Location toLocation(WorldPosition position) { - return LocationUtils.toLocation(position); - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java new file mode 100644 index 000000000..3a420339f --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.bukkit.api; + +import net.momirealms.craftengine.bukkit.font.BukkitFontManager; +import net.momirealms.craftengine.core.font.BitmapImage; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public final class CraftEngineImages { + + private CraftEngineImages() {} + + /** + * Returns an unmodifiable map of all currently loaded custom images. + * The map keys represent unique identifiers, and the values are the corresponding BitmapImage instances. + * + *

Important: Do not attempt to access this method during the onEnable phase + * as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method + * after the event is fired to obtain the complete image list. + * + * @return a non-null map containing all loaded custom images + */ + @NotNull + public static Map loadedImages() { + return BukkitFontManager.instance().loadedImages(); + } + + /** + * Gets a custom image by ID + * + * @param id id + * @return the custom image + */ + @Nullable + public static BitmapImage byId(@NotNull Key id) { + return BukkitFontManager.instance().loadedImages().get(id); + } +} 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 36c421336..abcf5d08f 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 @@ -44,7 +44,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { // 事件监听器 private final BlockEventListener blockEventListener; // 用于缓存string形式的方块状态到原版方块状态 - private final Map blockStateCache = new HashMap<>(1024); + private final Map blockStateCache = new HashMap<>(1024); // 用于临时存储可燃烧自定义方块的列表 private final List burnableBlocks = new ArrayList<>(); // 可燃烧的方块 @@ -169,25 +169,22 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public BlockStateWrapper createVanillaBlockState(String blockState) { - Object state = parseBlockState(blockState); - if (state == null) return null; - return BlockStateUtils.toBlockStateWrapper(state); + return this.blockStateCache.computeIfAbsent(blockState, k -> { + Object state = parseBlockState(k); + if (state == null) return null; + return BlockStateUtils.toBlockStateWrapper(state); + }); } @Nullable private Object parseBlockState(String state) { - if (this.blockStateCache.containsKey(state)) { - return this.blockStateCache.get(state); - } try { Object registryOrLookUp = MBuiltInRegistries.BLOCK; if (CoreReflections.method$Registry$asLookup != null) { registryOrLookUp = CoreReflections.method$Registry$asLookup.invoke(registryOrLookUp); } Object result = CoreReflections.method$BlockStateParser$parseForBlock.invoke(null, registryOrLookUp, state, false); - Object resultState = CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result); - this.blockStateCache.put(state, resultState); - return resultState; + return CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result); } catch (Exception e) { Debugger.BLOCK.warn(() -> "Failed to create block state: " + state, e); return null; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java index f6304a758..637fb20a5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java @@ -36,11 +36,17 @@ import java.lang.reflect.InvocationTargetException; import java.util.*; public class BukkitFontManager extends AbstractFontManager implements Listener { + private static BukkitFontManager instance; private final BukkitCraftEngine plugin; public BukkitFontManager(BukkitCraftEngine plugin) { super(plugin); this.plugin = plugin; + instance = this; + } + + public static BukkitFontManager instance() { + return instance; } @Override 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 e5a984612..fe991ca7f 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 @@ -348,6 +348,11 @@ public class BukkitCraftEngine extends CraftEngine { return (BukkitPackManager) packManager; } + @Override + public BukkitFontManager fontManager() { + return (BukkitFontManager) fontManager; + } + @SuppressWarnings("ResultOfMethodCallIgnored") @Override public void saveResource(String resourcePath) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 024b38db9..14c4d9378 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -56,7 +56,8 @@ public class BukkitCommandManager extends AbstractCommandManager new ListResourceCommand(this, plugin), new UploadPackCommand(this, plugin), new SendResourcePackCommand(this, plugin), - new DebugSaveDefaultResourcesCommand(this, plugin) + new DebugSaveDefaultResourcesCommand(this, plugin), + new DebugCleanCacheCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java new file mode 100644 index 000000000..8337140f6 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -0,0 +1,84 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.momirealms.craftengine.bukkit.api.CraftEngineItems; +import net.momirealms.craftengine.bukkit.font.BukkitFontManager; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.util.Key; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class DebugCleanCacheCommand extends BukkitCommandFeature { + + public DebugCleanCacheCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .required("type", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(List.of(Suggestion.suggestion("custom-model-data"), Suggestion.suggestion("custom-block-states"), Suggestion.suggestion("visual-block-states"), Suggestion.suggestion("font"))); + } + })) + .handler(context -> { + if (this.plugin().isReloading()) { + context.sender().sendMessage("The plugin is reloading. Please wait until the process is complete."); + return; + } + String type = context.get("type"); + switch (type) { + case "custom-model-data" -> { + BukkitItemManager instance = BukkitItemManager.instance(); + Set ids = CraftEngineItems.loadedItems().keySet().stream().map(Key::toString).collect(Collectors.toSet()); + int total = 0; + for (Map.Entry entry : instance.itemParser().idAllocators().entrySet()) { + List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); + total += removed.size(); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e); + return; + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unused item: " + id); + } + } + context.sender().sendMessage("Cleaned " + total + " unused custom model data"); + } + case "custom-block-states" -> { + } + case "visual-block-states" -> { + } + case "font", "images" -> { + BukkitFontManager instance = this.plugin().fontManager(); + + } + } + }); + } + + @Override + public String getFeatureID() { + return "debug_clean_cache"; + } +} diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index 3a2b6c8de..9ba12ceeb 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -210,6 +210,13 @@ debug_save_default_resources: - /craftengine debug save-default-resources - /ce debug save-default-resources +debug_clean_cache: + enable: true + permission: ce.command.debug.clean_cache + usage: + - /craftengine debug clean-cache + - /ce debug clean-cache + debug_test: enable: true permission: ce.command.debug.test diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml index 07e6a3f1e..7e36d6d45 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml @@ -34,7 +34,7 @@ items: default: north appearances: east: - state: note_block:18 + auto-state: solid model: path: minecraft:block/custom/chessboard_block y: 270 @@ -43,16 +43,16 @@ items: textures: pattern: minecraft:block/custom/chessboard_block north: - state: note_block:19 + auto-state: solid model: path: minecraft:block/custom/chessboard_block y: 180 south: - state: note_block:20 + auto-state: solid model: path: minecraft:block/custom/chessboard_block west: - state: note_block:21 + auto-state: solid model: path: minecraft:block/custom/chessboard_block y: 90 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml index 4776b2d4d..420809bc8 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml @@ -25,7 +25,7 @@ items: luminance: 15 map-color: 36 state: - state: note_block:15 + auto-state: solid model: path: minecraft:block/custom/chinese_lantern generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml index 94185e5aa..9d9b8bf83 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml @@ -36,7 +36,7 @@ items: default: false appearances: off: - state: cactus:0 + auto-state: cactus model: path: minecraft:block/custom/copper_coil generation: @@ -47,7 +47,7 @@ items: top: minecraft:block/custom/copper_coil side: minecraft:block/custom/copper_coil_side on: - state: cactus:1 + auto-state: cactus model: path: minecraft:block/custom/copper_coil_on generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml index ccb1fad33..0b61f7161 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml @@ -90,7 +90,7 @@ blocks: range: 0~2 appearances: stage_0: - state: tripwire:1 + auto-state: lower_tripwire models: - path: minecraft:block/custom/ender_pearl_flower_stage_0 generation: @@ -98,7 +98,7 @@ blocks: textures: cross: minecraft:block/custom/ender_pearl_flower_stage_0 stage_1: - state: tripwire:0 + auto-state: higher_tripwire models: - path: minecraft:block/custom/ender_pearl_flower_stage_1 generation: @@ -106,7 +106,7 @@ blocks: textures: cross: minecraft:block/custom/ender_pearl_flower_stage_1 stage_2: - state: sugar_cane:3 + auto-state: sugar_cane models: - path: minecraft:block/custom/ender_pearl_flower_stage_2 generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml index 5e2b00ba4..52a2815fd 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml @@ -26,7 +26,7 @@ items: loot: template: default:loot_table/self state: - state: sugar_cane:0 + auto-state: sugar_cane models: - path: minecraft:block/custom/fairy_flower_1 weight: 100 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml index f24859eef..4213b62bd 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml @@ -53,7 +53,7 @@ items: range: 0~5 appearances: default: - state: sugar_cane:2 + auto-state: sugar_cane models: - path: minecraft:block/custom/flame_cane_1 weight: 1 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml index 17ccff09d..e114b183c 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml @@ -27,7 +27,7 @@ items: instrument: snare map-color: 45 state: - state: note_block:16 + auto-state: solid model: path: minecraft:block/custom/gunpowder_block generation: @@ -59,7 +59,7 @@ items: instrument: basedrum map-color: 45 state: - state: note_block:17 + auto-state: solid model: path: minecraft:block/custom/solid_gunpowder_block generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 61ccd87bc..6002d41ac 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -27,7 +27,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/palm_log_top texture_side_path: minecraft:block/custom/palm_log model_vertical_path: minecraft:block/custom/palm_log @@ -57,7 +56,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/stripped_palm_log_top texture_side_path: minecraft:block/custom/stripped_palm_log model_vertical_path: minecraft:block/custom/stripped_palm_log @@ -90,7 +88,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/palm_log texture_side_path: minecraft:block/custom/palm_log model_vertical_path: minecraft:block/custom/palm_wood @@ -120,7 +117,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/stripped_palm_log texture_side_path: minecraft:block/custom/stripped_palm_log model_vertical_path: minecraft:block/custom/stripped_palm_wood @@ -151,7 +147,7 @@ items: template: default:model/simplified_cube_all arguments: path: minecraft:block/custom/palm_planks - state: note_block:12 + auto-state: solid default:palm_sapling: material: nether_brick settings: @@ -187,7 +183,7 @@ items: range: 0~1 appearances: default: - state: oak_sapling:0 + auto-state: sapling model: path: minecraft:block/custom/palm_sapling generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml index 5a164c44e..23fe24d71 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml @@ -58,7 +58,7 @@ items: default: 1 appearances: one: - state: tripwire:2 + auto-state: lower_tripwire models: - path: minecraft:block/custom/pebble_1 weight: 1 @@ -72,7 +72,7 @@ items: weight: 1 y: 270 two: - state: tripwire:3 + auto-state: lower_tripwire models: - path: minecraft:block/custom/pebble_2 weight: 1 @@ -86,7 +86,7 @@ items: weight: 1 y: 270 three: - state: tripwire:4 + auto-state: higher_tripwire models: - path: minecraft:block/custom/pebble_3 weight: 1 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml index c43425c81..de1e00be9 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml @@ -25,7 +25,7 @@ items: loot: template: default:loot_table/self state: - state: sugar_cane:1 + auto-state: sugar_cane model: path: minecraft:block/custom/reed recipes: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index 710804ef9..63b13e353 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -48,7 +48,7 @@ items: default: false appearances: east: - state: note_block:22 + auto-state: note_block model: path: minecraft:block/custom/safe_block y: 90 @@ -59,7 +59,7 @@ items: side: minecraft:block/custom/safe_block_side top: minecraft:block/custom/safe_block_top east_open: - state: note_block:23 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open y: 90 @@ -70,30 +70,30 @@ items: side: minecraft:block/custom/safe_block_side top: minecraft:block/custom/safe_block_top north: - state: note_block:24 + auto-state: note_block model: path: minecraft:block/custom/safe_block north_open: - state: note_block:25 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open south: - state: note_block:26 + auto-state: note_block model: path: minecraft:block/custom/safe_block y: 180 south_open: - state: note_block:27 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open y: 180 west: - state: note_block:28 + auto-state: note_block model: path: minecraft:block/custom/safe_block y: 270 west_open: - state: note_block:29 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open y: 270 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index 0eee914e4..6721d3027 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -50,7 +50,7 @@ blocks: arguments: break_power: 2 state: - state: note_block:13 + auto-state: solid model: template: default:model/simplified_cube_all arguments: diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 5912b31f5..da609c8b0 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -862,7 +862,7 @@ templates#block_states: default: y appearances: axisY: - state: ${base_block} + auto-state: solid model: path: ${model_vertical_path} generation: @@ -871,7 +871,7 @@ templates#block_states: end: ${texture_top_path} side: ${texture_side_path} axisX: - state: ${base_block} + auto-state: solid model: x: 90 y: 90 @@ -882,7 +882,7 @@ templates#block_states: end: ${texture_top_path} side: ${texture_side_path} axisZ: - state: ${base_block} + auto-state: solid model: x: 90 path: ${model_horizontal_path} @@ -913,7 +913,7 @@ templates#block_states: range: 1~7 appearances: default: - state: ${default_state} + auto-state: leaves model: path: ${model_path} generation: @@ -921,7 +921,7 @@ templates#block_states: textures: all: ${texture_path} waterlogged: - state: ${waterlogged_state} + auto-state: waterlogged_leaves model: path: ${model_path} variants: @@ -2231,6 +2231,11 @@ templates#block_states: x: 180 y: 180 variants: + waterlogged=true: + settings: + resistance: 1200.0 + burnable: false + fluid-state: water facing=east,half=bottom,shape=inner_left,waterlogged=false: appearance: facing=east,half=bottom,shape=inner_left,waterlogged=false facing=east,half=bottom,shape=inner_right,waterlogged=false: @@ -2313,244 +2318,85 @@ templates#block_states: appearance: facing=west,half=top,shape=straight,waterlogged=false facing=east,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=east,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=east,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=east,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=east,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=straight,waterlogged=true: appearance: facing=east,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=inner_left,waterlogged=true: appearance: facing=east,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=inner_right,waterlogged=true: appearance: facing=east,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=outer_left,waterlogged=true: appearance: facing=east,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=outer_right,waterlogged=true: appearance: facing=east,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=straight,waterlogged=true: appearance: facing=east,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=north,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=north,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=north,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=north,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=straight,waterlogged=true: appearance: facing=north,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=inner_left,waterlogged=true: appearance: facing=north,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=inner_right,waterlogged=true: appearance: facing=north,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=outer_left,waterlogged=true: appearance: facing=north,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=outer_right,waterlogged=true: appearance: facing=north,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=straight,waterlogged=true: appearance: facing=north,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=south,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=south,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=south,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=south,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=straight,waterlogged=true: appearance: facing=south,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=inner_left,waterlogged=true: appearance: facing=south,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=inner_right,waterlogged=true: appearance: facing=south,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=outer_left,waterlogged=true: appearance: facing=south,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=outer_right,waterlogged=true: appearance: facing=south,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=straight,waterlogged=true: appearance: facing=south,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=west,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=west,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=west,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=west,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=straight,waterlogged=true: appearance: facing=west,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=inner_left,waterlogged=true: appearance: facing=west,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=inner_right,waterlogged=true: appearance: facing=west,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=outer_left,waterlogged=true: appearance: facing=west,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=outer_right,waterlogged=true: appearance: facing=west,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=straight,waterlogged=true: appearance: facing=west,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water + # pressure plate block default:block_state/pressure_plate: properties: @@ -3171,6 +3017,11 @@ templates#block_states: - item: ${fence_side_item} rotation: 270 variants: + waterlogged=true: + settings: + resistance: 1200.0 + burnable: false + fluid-state: water east=false,north=false,south=false,waterlogged=false,west=false: appearance: east=false,north=false,south=false,waterlogged=false,west=false east=true,north=false,south=false,waterlogged=false,west=false: @@ -3205,100 +3056,37 @@ templates#block_states: appearance: east=true,north=true,south=true,waterlogged=false,west=true east=false,north=false,south=false,waterlogged=true,west=false: appearance: east=false,north=false,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=false,waterlogged=true,west=false: appearance: east=true,north=false,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=false,waterlogged=true,west=false: appearance: east=false,north=true,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=false,south=true,waterlogged=true,west=false: appearance: east=false,north=false,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=false,south=false,waterlogged=true,west=true: appearance: east=false,north=false,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=false,waterlogged=true,west=false: appearance: east=true,north=true,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=true,waterlogged=true,west=false: appearance: east=true,north=false,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=false,waterlogged=true,west=true: appearance: east=true,north=false,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=true,waterlogged=true,west=false: appearance: east=false,north=true,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=false,waterlogged=true,west=true: appearance: east=false,north=true,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=false,south=true,waterlogged=true,west=true: appearance: east=false,north=false,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=true,waterlogged=true,west=false: appearance: east=true,north=true,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=false,waterlogged=true,west=true: appearance: east=true,north=true,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=true,waterlogged=true,west=true: appearance: east=true,north=false,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=true,waterlogged=true,west=true: appearance: east=false,north=true,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=true,waterlogged=true,west=true: appearance: east=true,north=true,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water + # recipes templates#recipes: default:recipe/planks: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 5679ea63c..acd02e1a4 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -277,6 +277,8 @@ warning.config.block.state.entity_renderer.better_model.missing_model: " warning.config.block.state.entity_renderer.model_engine.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'model_engine' entity renderer." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." +warning.config.block.state.invalid_auto_state: "Issue found in file - The block '' is using an invalid auto-state ''. Allowed values: []." +warning.config.block.state.auto_state.exhausted: "Issue found in file - Cannot allocate visual block state for block '' as the slots('') in group '' have been exhausted." warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in block-state-mappings." warning.config.block.state.invalid_vanilla_id: "Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." warning.config.block.state.invalid_id: "Issue found in file - The block state ID range () used by block '' is outside the valid range of 0 to . Please add more server-side blocks in 'config.yml' if the current slots are exhausted." diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 5d5b66231..8f0af28ff 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -18,9 +18,9 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.allocator.BlockStateAllocator; import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate; import net.momirealms.craftengine.core.pack.allocator.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -60,8 +60,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final List cachedSuggestions = new ArrayList<>(); // 缓存的使用中的命名空间 protected final Set namespacesInUse = new HashSet<>(); - // 用于检测单个外观方块状态是否被绑定了不同模型 - protected final Map tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>(); // Map<方块类型, Map<方块状态NBT,模型>>,用于生成block state json protected final Map> blockStateOverrides = new HashMap<>(); // 用于生成mod使用的block state json @@ -81,7 +79,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 自定义状态列表,会随着重载变化 protected final ImmutableBlockState[] immutableBlockStates; // 倒推缓存 - protected final BlockStateCandidate[] reversedBlockStateArranger; + protected final BlockStateCandidate[] autoVisualBlockStateCandidates; + // 用于检测单个外观方块状态是否被绑定了不同模型 + protected final JsonElement[] tempVanillaBlockStateModels; // 临时存储哪些视觉方块被使用了 protected final Set tempVisualBlockStatesInUse = new HashSet<>(); protected final Set tempVisualBlocksInUse = new HashSet<>(); @@ -91,14 +91,15 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) { super(plugin); this.vanillaBlockStateCount = vanillaBlockStateCount; - this.blockParser = new BlockParser(); - this.blockStateMappingParser = new BlockStateMappingParser(); this.customBlocks = new DelegatingBlock[customBlockCount]; this.customBlockHolders = new Object[customBlockCount]; this.customBlockStates = new DelegatingBlockState[customBlockCount]; this.immutableBlockStates = new ImmutableBlockState[customBlockCount]; this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount]; - this.reversedBlockStateArranger = new BlockStateCandidate[vanillaBlockStateCount]; + this.autoVisualBlockStateCandidates = new BlockStateCandidate[vanillaBlockStateCount]; + this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount]; + this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates); + this.blockStateMappingParser = new BlockStateMappingParser(); Arrays.fill(this.blockStateMappings, -1); } @@ -130,7 +131,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.appearanceToRealState.clear(); Arrays.fill(this.blockStateMappings, -1); Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); - Arrays.fill(this.reversedBlockStateArranger, null); + Arrays.fill(this.autoVisualBlockStateCandidates, null); + for (AutoStateGroup autoStateGroup : AutoStateGroup.values()) { + autoStateGroup.reset(); + } } @Override @@ -188,7 +192,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } protected void clearCache() { - this.tempVanillaBlockStateModels.clear(); + Arrays.fill(this.tempVanillaBlockStateModels, null); this.tempVisualBlockStatesInUse.clear(); this.tempVisualBlocksInUse.clear(); } @@ -275,26 +279,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem Key blockOwnerId = getBlockOwnerId(beforeState); List blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>()); blockStateWrappers.add(beforeState); - AbstractBlockManager.this.reversedBlockStateArranger[beforeState.registryId()] = blockParser.createVisualBlockCandidate(beforeState); - + AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState); } exceptionCollector.throwIfPresent(); } - } - - public class BlockParser extends IdSectionConfigParser { - public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; - private final IdAllocator internalIdAllocator; - private final List pendingConfigSections = new ArrayList<>(); - private final BlockStateAllocator[] visualBlockStateAllocators = new BlockStateAllocator[AutoStateGroup.values().length]; - - public BlockParser() { - this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json")); - } - - public void addPendingConfigSection(PendingConfigSection section) { - this.pendingConfigSections.add(section); - } @Nullable public BlockStateCandidate createVisualBlockCandidate(BlockStateWrapper blockState) { @@ -302,40 +290,58 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (!groups.isEmpty()) { BlockStateCandidate candidate = new BlockStateCandidate(blockState); for (AutoStateGroup group : groups) { - getOrCreateBlockStateAllocator(group).addCandidate(candidate); + group.addCandidate(candidate); } return candidate; } return null; } + } - private BlockStateAllocator getOrCreateBlockStateAllocator(AutoStateGroup group) { - int index = group.ordinal(); - BlockStateAllocator visualBlockStateAllocator = this.visualBlockStateAllocators[index]; - if (visualBlockStateAllocator == null) { - visualBlockStateAllocator = new BlockStateAllocator(); - this.visualBlockStateAllocators[index] = visualBlockStateAllocator; - } - return visualBlockStateAllocator; + public class BlockParser extends IdSectionConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; + private final IdAllocator internalIdAllocator; + private final VisualBlockStateAllocator visualBlockStateAllocator; + private final List pendingConfigSections = new ArrayList<>(); + + public BlockParser(BlockStateCandidate[] candidates) { + this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json")); + this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState); + } + + public void addPendingConfigSection(PendingConfigSection section) { + this.pendingConfigSections.add(section); } @Override public void postProcess() { + this.visualBlockStateAllocator.processPendingAllocations(); + try { + this.visualBlockStateAllocator.saveToCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e); + } this.internalIdAllocator.processPendingAllocations(); try { this.internalIdAllocator.saveToCache(); } catch (IOException e) { - AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block state allocation", e); + AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block states allocation", e); } } @Override public void preProcess() { + this.visualBlockStateAllocator.reset(); + try { + this.visualBlockStateAllocator.loadFromCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states allocation cache", e); + } this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1); try { this.internalIdAllocator.loadFromCache(); } catch (IOException e) { - AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block state allocation cache", e); + AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block states allocation cache", e); } for (PendingConfigSection section : this.pendingConfigSections) { ResourceConfigUtils.runCatching( @@ -437,9 +443,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - CompletableFutures.allOf(internalIdAllocators).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> { - if (t != null) { - if (t instanceof CompletionException e) { + CompletableFutures.allOf(internalIdAllocators).whenComplete((v1, t1) -> ResourceConfigUtils.runCatching(path, node, () -> { + if (t1 != null) { + if (t1 instanceof CompletionException e) { Throwable cause = e.getCause(); // 这里不会有conflict了,因为之前已经判断过了 if (cause instanceof IdAllocator.IdExhaustedException) { @@ -449,7 +455,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return; } } - throw new RuntimeException("Unknown error occurred", t); + throw new RuntimeException("Unknown error occurred", t1); } for (int i = 0; i < internalIdAllocators.size(); i++) { @@ -472,110 +478,164 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem ); BlockBehavior blockBehavior = createBlockBehavior(customBlock, MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors"))); - // 单状态 + Map> appearanceConfigs; + Map> futureVisualStates = new HashMap<>(); if (singleState) { - BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state")); - this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models")); - ImmutableBlockState onlyState = states.getFirst(); - // 为唯一的状态绑定外观 - onlyState.setVanillaBlockState(appearanceState); - parseBlockEntityRender(stateSection.get("entity-renderer")).ifPresent(onlyState::setConstantRenderers); + appearanceConfigs = Map.of("", stateSection); } else { - BlockStateWrapper anyAppearanceState = null; - Map appearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"); - // 也不能为空 - if (appearancesSection.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances"); - } - Map appearances = Maps.newHashMap(); - // 先解析所有的外观 - for (Map.Entry entry : appearancesSection.entrySet()) { - Map appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - // 解析对应的视觉方块 - BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(appearanceSection.get("state"), "warning.config.block.state.missing_state")); - this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(appearanceSection, "model", "models")); - appearances.put(entry.getKey(), new BlockStateAppearance(appearanceState, parseBlockEntityRender(appearanceSection.get("entity-renderer")))); - if (anyAppearanceState == null) { - anyAppearanceState = appearanceState; - } - } - // 解析变体 - Map variantsSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"); - for (Map.Entry entry : variantsSection.entrySet()) { - Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - String variantNBT = entry.getKey(); - // 先解析nbt,找到需要修改的方块状态 - CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT); - if (tag == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT); - } - List possibleStates = variantProvider.getPossibleStates(tag); - Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); - if (anotherSetting != null) { - for (ImmutableBlockState possibleState : possibleStates) { - possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting)); - } - } - String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance")); - if (appearanceName != null) { - BlockStateAppearance appearance = appearances.get(appearanceName); - if (appearance == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName); - } - for (ImmutableBlockState possibleState : possibleStates) { - possibleState.setVanillaBlockState(appearance.blockState()); - appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); - } - } - } - // 为没有外观的方块状态填充 - for (ImmutableBlockState blockState : states) { - if (blockState.vanillaBlockState() == null) { - blockState.setVanillaBlockState(anyAppearanceState); - } + Map rawAppearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"); + appearanceConfigs = new LinkedHashMap<>(4); + for (Map.Entry entry : rawAppearancesSection.entrySet()) { + appearanceConfigs.put(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey())); } } - // 获取方块实体行为 - EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior(); - boolean isEntityBlock = entityBlockBehavior != null; - - // 绑定行为 - for (ImmutableBlockState state : states) { - if (isEntityBlock) { - state.setBlockEntityType(entityBlockBehavior.blockEntityType()); - } - state.setBehavior(blockBehavior); - int internalId = state.customBlockState().registryId(); - BlockStateWrapper visualState = state.vanillaBlockState(); - int appearanceId = visualState.registryId(); - int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount; - AbstractBlockManager.this.immutableBlockStates[index] = state; - AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId; - AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); - AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState); - AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState)); - AbstractBlockManager.this.applyPlatformSettings(state); - // generate mod assets - if (Config.generateModAssets()) { - AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels.get(appearanceId)) - .orElseGet(() -> { - // 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题 - // 未来需要靠mod重构彻底解决问题 - JsonObject json = new JsonObject(); - json.addProperty("model", "minecraft:block/air"); - return json; - })); + for (Map.Entry> entry : appearanceConfigs.entrySet()) { + Map appearanceSection = entry.getValue(); + if (appearanceSection.containsKey("state")) { + String appearanceName = entry.getKey(); + futureVisualStates.put( + appearanceName, + this.visualBlockStateAllocator.assignFixedBlockState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, parsePluginFormattedBlockState(appearanceSection.get("state").toString())) + ); + } else if (stateSection.containsKey("auto-state")) { + String autoStateId = stateSection.get("auto-state").toString(); + AutoStateGroup group = AutoStateGroup.byId(autoStateId); + if (group == null) { + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values())); + } + String appearanceName = entry.getKey(); + futureVisualStates.put( + appearanceName, + this.visualBlockStateAllocator.requestAutoState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, group) + ); + } else { + throw new LocalizedResourceConfigException("warning.config.block.state.missing_state"); } } - // 一定要到最后再绑定 - customBlock.setBehavior(blockBehavior); - holder.bindValue(customBlock); + CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> { + if (t2 != null) { + if (t2 instanceof CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof VisualBlockStateAllocator.StateExhaustedException exhausted) { + throw new LocalizedResourceConfigException("warning.config.block.state.auto_state.exhausted", exhausted.group().id(), String.valueOf(exhausted.group().candidateCount())); + } else { + Debugger.BLOCK.warn(() -> "Unknown error while allocating visual block state.", cause); + return; + } + } + throw new RuntimeException("Unknown error occurred", t2); + } - // 添加方块 - AbstractBlockManager.this.byId.put(customBlock.id(), customBlock); + BlockStateAppearance anyAppearance = null; + Map appearances = new HashMap<>(); + for (Map.Entry> entry : appearanceConfigs.entrySet()) { + String appearanceName = entry.getKey(); + Map appearanceSection = entry.getValue(); + BlockStateWrapper visualBlockState; + try { + visualBlockState = futureVisualStates.get(appearanceName).get(); + } catch (InterruptedException | ExecutionException e) { + AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating visual block state for block " + id.asString(), e); + return; + } + this.arrangeModelForStateAndVerify(visualBlockState, ResourceConfigUtils.get(appearanceSection, "model", "models")); + BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))); + appearances.put(appearanceName, blockStateAppearance); + if (anyAppearance == null) { + anyAppearance = blockStateAppearance; + } + } + // 至少有一个外观吧 + Objects.requireNonNull(anyAppearance, "any appearance should not be null"); + + ExceptionCollector exceptionCollector = new ExceptionCollector<>(); + if (!singleState) { + Map variantsSection = ResourceConfigUtils.getAsMapOrNull(stateSection.get("variants"), "variants"); + if (variantsSection != null) { + for (Map.Entry entry : variantsSection.entrySet()) { + Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); + String variantNBT = entry.getKey(); + // 先解析nbt,找到需要修改的方块状态 + CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT); + if (tag == null) { + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT)); + continue; + } + List possibleStates = variantProvider.getPossibleStates(tag); + Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); + if (anotherSetting != null) { + for (ImmutableBlockState possibleState : possibleStates) { + possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting)); + } + } + String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance")); + if (appearanceName != null) { + BlockStateAppearance appearance = appearances.get(appearanceName); + if (appearance == null) { + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName)); + continue; + } + for (ImmutableBlockState possibleState : possibleStates) { + possibleState.setVanillaBlockState(appearance.blockState()); + appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); + } + } + } + } + } + + // 获取方块实体行为 + EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior(); + boolean isEntityBlock = entityBlockBehavior != null; + + // 绑定行为 + for (ImmutableBlockState state : states) { + if (isEntityBlock) { + state.setBlockEntityType(entityBlockBehavior.blockEntityType()); + } + state.setBehavior(blockBehavior); + int internalId = state.customBlockState().registryId(); + BlockStateWrapper visualState = state.vanillaBlockState(); + // 校验,为未绑定外观的强行添加外观 + if (visualState == null) { + visualState = anyAppearance.blockState(); + state.setVanillaBlockState(visualState); + anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); + } + int appearanceId = visualState.registryId(); + int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount; + AbstractBlockManager.this.immutableBlockStates[index] = state; + AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId; + AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); + AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState); + AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState)); + AbstractBlockManager.this.applyPlatformSettings(state); + // generate mod assets + if (Config.generateModAssets()) { + AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels[appearanceId]) + .orElseGet(() -> { + // 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题 + // 未来需要靠mod重构彻底解决问题 + JsonObject json = new JsonObject(); + json.addProperty("model", "minecraft:block/air"); + return json; + })); + } + } + + // 一定要到最后再绑定 + customBlock.setBehavior(blockBehavior); + holder.bindValue(customBlock); + + // 添加方块 + AbstractBlockManager.this.byId.put(customBlock.id(), customBlock); + + // 抛出次要警告 + exceptionCollector.throwIfPresent(); + }); }, () -> GsonHelper.get().toJson(section))); } @@ -622,12 +682,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 结合variants JsonElement combinedVariant = GsonHelper.combine(variants); Map overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()); - AbstractBlockManager.this.tempVanillaBlockStateModels.put(blockStateWrapper.registryId(), combinedVariant); JsonElement previous = overrideMap.get(propertyNBT); if (previous != null && !previous.equals(combinedVariant)) { throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous)); } overrideMap.put(propertyNBT, combinedVariant); + AbstractBlockManager.this.tempVanillaBlockStateModels[blockStateWrapper.registryId()] = combinedVariant; } private JsonObject parseAppearanceModelSectionAsJson(Map section) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java index 7ce0ad545..4589397a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java @@ -23,4 +23,16 @@ public abstract class AbstractBlockStateWrapper implements BlockStateWrapper { public String toString() { return this.blockState.toString(); } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BlockStateWrapper that)) return false; + return this.registryId == that.registryId(); + } + + @Override + public int hashCode() { + return this.registryId; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java index b79a46f55..f917f72b4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.core.block; +import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate; import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -9,37 +11,68 @@ import java.util.function.Predicate; public enum AutoStateGroup { LEAVES("leaves", Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), - (w) -> !(boolean) w.getProperty("waterlogged"), 0 + (w) -> !(boolean) w.getProperty("waterlogged") ), WATERLOGGED_LEAVES( "waterlogged_leaves", Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), - (w) -> w.getProperty("waterlogged"), 0 + (w) -> w.getProperty("waterlogged") ), - TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true, 1), - LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached"), 0), - HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached"), 0), - NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true, 0), - BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true, 0), - RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true, 0), - MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true, 0), - MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true, 1), - SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true, 2); + LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached")), + HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached")), + NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true), + BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true), + RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true), + MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true), + TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true), + SUGAR_CANE("sugar_cane", Set.of(BlockKeys.SUGAR_CANE), (w) -> true), + CACTUS("cactus", Set.of(BlockKeys.CACTUS), (w) -> true), + SAPLING("sapling", Set.of(BlockKeys.OAK_SAPLING, BlockKeys.SPRUCE_SAPLING, BlockKeys.BIRCH_SAPLING, BlockKeys.JUNGLE_SAPLING, BlockKeys.ACACIA_SAPLING, BlockKeys.DARK_OAK_SAPLING, BlockKeys.CHERRY_SAPLING, BlockKeys.PALE_OAK_SAPLING), (w) -> true), + MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true), + SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true); private final Set blocks; private final String id; private final Predicate predicate; - private final int priority; + private final List candidates = new ArrayList<>(); + private int pointer; - AutoStateGroup(String id, Set blocks, Predicate predicate, int priority) { + AutoStateGroup(String id, Set blocks, Predicate predicate) { this.id = id; this.blocks = blocks; this.predicate = predicate; - this.priority = priority; } - public int priority() { - return priority; + public void reset() { + this.pointer = 0; + this.candidates.clear(); + } + + public void addCandidate(@NotNull BlockStateCandidate candidate) { + this.candidates.add(candidate); + } + + public int candidateCount() { + return candidates.size(); + } + + @Nullable + public BlockStateCandidate findNextCandidate() { + while (this.pointer < this.candidates.size()) { + final BlockStateCandidate state = this.candidates.get(this.pointer); + if (!state.isUsed()) { + return state; + } + this.pointer++; + } + return null; + } + + public boolean test(BlockStateWrapper state) { + if (!this.blocks.contains(state.ownerId())) { + return false; + } + return this.predicate.test(state); } public Set blocks() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java index 30490bbad..94bbf2342 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java @@ -7,6 +7,7 @@ import java.util.List; public final class BlockKeys { private BlockKeys() {} + public static final Key SUGAR_CANE = Key.of("minecraft:sugar_cane"); public static final Key NOTE_BLOCK = Key.of("minecraft:note_block"); public static final Key TRIPWIRE = Key.of("minecraft:tripwire"); public static final Key CRAFTING_TABLE = Key.of("minecraft:crafting_table"); @@ -262,6 +263,15 @@ public final class BlockKeys { public static final Key AZALEA_LEAVES = Key.of("minecraft:azalea_leaves"); public static final Key FLOWERING_AZALEA_LEAVES = Key.of("minecraft:flowering_azalea_leaves"); + public static final Key OAK_SAPLING = Key.of("minecraft:oak_sapling"); + public static final Key SPRUCE_SAPLING = Key.of("minecraft:spruce_sapling"); + public static final Key BIRCH_SAPLING = Key.of("minecraft:birch_sapling"); + public static final Key JUNGLE_SAPLING = Key.of("minecraft:jungle_sapling"); + public static final Key DARK_OAK_SAPLING = Key.of("minecraft:dark_oak_sapling"); + public static final Key ACACIA_SAPLING = Key.of("minecraft:acacia_sapling"); + public static final Key CHERRY_SAPLING = Key.of("minecraft:cherry_sapling"); + public static final Key PALE_OAK_SAPLING = Key.of("minecraft:pale_oak_sapling"); + public static final List WOODEN_TRAPDOORS = List.of(OAK_TRAPDOOR, SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR, ACACIA_TRAPDOOR, PALE_OAK_TRAPDOOR, DARK_OAK_TRAPDOOR, MANGROVE_TRAPDOOR, JUNGLE_TRAPDOOR); public static final List CHERRY_TRAPDOORS = List.of(CHERRY_TRAPDOOR); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java index 0b19c2ee3..5ef5223b1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java @@ -1,8 +1,9 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; -public interface BlockStateWrapper { +public interface BlockStateWrapper extends Comparable { Object literalObject(); @@ -15,4 +16,9 @@ public interface BlockStateWrapper { boolean hasProperty(String propertyName); String getAsString(); + + @Override + default int compareTo(@NotNull BlockStateWrapper o) { + return Integer.compare(registryId(), o.registryId()); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index f8ed7e7c3..5c44b4428 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -80,6 +80,16 @@ public abstract class AbstractFontManager implements FontManager { return offsetFont; } + @Override + public Map loadedImages() { + return Collections.unmodifiableMap(this.images); + } + + @Override + public Map emojis() { + return Collections.unmodifiableMap(this.emojis); + } + @Override public void unload() { this.fonts.clear(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java index e56e39c3b..7cf65807b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java @@ -48,6 +48,10 @@ public interface FontManager extends Manageable { OffsetFont offsetFont(); + Map loadedImages(); + + Map emojis(); + ConfigParser[] parsers(); default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index b1d0539ed..9d3c7b909 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -69,6 +69,14 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl ItemDataModifiers.init(); } + public ItemParser itemParser() { + return itemParser; + } + + public EquipmentParser equipmentParser() { + return equipmentParser; + } + protected static void registerVanillaItemExtraBehavior(ItemBehavior behavior, Key... items) { for (Key key : items) { VANILLA_ITEM_EXTRA_BEHAVIORS.computeIfAbsent(key, k -> new ArrayList<>()).add(behavior); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c4ed3e352..6ee669d9a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -5,7 +5,6 @@ import com.google.common.collect.Multimap; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.google.gson.*; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.font.BitmapImage; import net.momirealms.craftengine.core.font.Font; import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; @@ -27,7 +26,9 @@ import net.momirealms.craftengine.core.pack.obfuscation.ObfA; import net.momirealms.craftengine.core.pack.revision.Revision; import net.momirealms.craftengine.core.pack.revision.Revisions; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.*; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; import net.momirealms.craftengine.core.plugin.locale.I18NData; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -615,7 +616,10 @@ public abstract class AbstractPackManager implements PackManager { long o2 = System.nanoTime(); this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms"); for (ConfigParser parser : this.sortedParsers) { - if (!predicate.test(parser)) continue; + if (!predicate.test(parser)) { + parser.clear(); + continue; + } long t1 = System.nanoTime(); parser.preProcess(); parser.loadAll(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java deleted file mode 100644 index 5669d0b27..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.momirealms.craftengine.core.pack.allocator; - -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public class BlockStateAllocator { - private final List blockStates = new ArrayList<>(); - private int pointer = 0; - private int max = -1; - - public void addCandidate(BlockStateCandidate state) { - this.blockStates.add(state); - this.max = this.blockStates.size() - 1; - } - - @Nullable - public BlockStateCandidate findNext() { - while (this.pointer < this.max) { - final BlockStateCandidate state = this.blockStates.get(this.pointer); - if (!state.isUsed()) { - return state; - } - this.pointer++; - } - return null; - } - - public void processPendingAllocations() { - - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java index 47c5e6bd3..9a84c875a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java @@ -152,7 +152,7 @@ public class IdAllocator { * @param shouldRemove 判断是否应该移除的谓词 * @return 被移除的ID数量 */ - public int cleanupUnusedIds(Predicate shouldRemove) { + public List cleanupUnusedIds(Predicate shouldRemove) { List idsToRemove = new ArrayList<>(); for (String id : this.cachedIdMap.keySet()) { if (shouldRemove.test(id)) { @@ -160,15 +160,13 @@ public class IdAllocator { } } - int removedCount = 0; for (String id : idsToRemove) { Integer removedId = this.cachedIdMap.remove(id); if (removedId != null && !this.forcedIdMap.containsValue(removedId)) { this.occupiedIdSet.clear(removedId); - removedCount++; } } - return removedCount; + return idsToRemove; } /** diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java new file mode 100644 index 000000000..ab7d7cd86 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java @@ -0,0 +1,152 @@ +package net.momirealms.craftengine.core.pack.allocator; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.momirealms.craftengine.core.block.AutoStateGroup; +import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.util.FileUtils; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.Pair; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public class VisualBlockStateAllocator { + private final Path cacheFilePath; + private final Map cachedBlockStates = new HashMap<>(); + private final Map>> pendingAllocations = new LinkedHashMap<>(); + @SuppressWarnings("unchecked") + private final List>>[] pendingAllocationFutures = new List[AutoStateGroup.values().length]; + private final BlockStateCandidate[] candidates; + private final Function factory; + + public VisualBlockStateAllocator(Path cacheFilePath, BlockStateCandidate[] candidates, Function factory) { + this.cacheFilePath = cacheFilePath; + this.candidates = candidates; + this.factory = factory; + } + + public void reset() { + Arrays.fill(this.pendingAllocationFutures, new ArrayList<>()); + this.cachedBlockStates.clear(); + this.pendingAllocations.clear(); + } + + public CompletableFuture assignFixedBlockState(String name, BlockStateWrapper state) { + this.cachedBlockStates.remove(name); + BlockStateCandidate candidate = this.candidates[state.registryId()]; + if (candidate != null) { + candidate.setUsed(); + } + return CompletableFuture.completedFuture(state); + } + + public CompletableFuture requestAutoState(String name, AutoStateGroup group) { + CompletableFuture future = new CompletableFuture<>(); + this.pendingAllocations.put(name, new Pair<>(group, future)); + this.pendingAllocationFutures[group.ordinal()].add(Pair.of(name, future)); + return future; + } + + public void processPendingAllocations() { + // 先处理缓存的 + for (Map.Entry entry : this.cachedBlockStates.entrySet()) { + int registryId = entry.getValue().registryId(); + // 检查候选方块是否可用 + BlockStateCandidate candidate = this.candidates[registryId]; + if (candidate != null) { + // 未被使用 + if (!candidate.isUsed()) { + // 获取当前的安排任务 + Pair> pair = this.pendingAllocations.get(entry.getKey()); + // 如果候选满足组,那么直接允许起飞 + if (pair != null && pair.left().test(candidate.blockState())) { + pair.right().complete(candidate.blockState()); + } + // 尽管未被使用,该槽位也应该被占用,以避免被自动分配到 + candidate.setUsed(); + } + // 被使用了就随他去 + } + // 没有候选也随他去 + } + + this.pendingAllocations.clear(); + + for (AutoStateGroup group : AutoStateGroup.values()) { + List>> pendingAllocationFuture = this.pendingAllocationFutures[group.ordinal()]; + for (Pair> pair : pendingAllocationFuture) { + BlockStateCandidate nextCandidate = group.findNextCandidate(); + if (nextCandidate != null) { + nextCandidate.setUsed(); + this.cachedBlockStates.put(pair.left(), nextCandidate.blockState()); + pair.right().complete(nextCandidate.blockState()); + } else { + pair.right().completeExceptionally(new StateExhaustedException(group)); + } + } + } + } + + public static class StateExhaustedException extends RuntimeException { + private final AutoStateGroup group; + + public StateExhaustedException(AutoStateGroup group) { + this.group = group; + } + + public AutoStateGroup group() { + return group; + } + } + + /** + * 从文件加载缓存 + */ + public void loadFromCache() throws IOException { + if (!Files.exists(this.cacheFilePath)) { + return; + } + JsonElement element = GsonHelper.readJsonFile(this.cacheFilePath); + if (element instanceof JsonObject jsonObject) { + for (Map.Entry entry : jsonObject.entrySet()) { + if (entry.getValue() instanceof JsonPrimitive primitive) { + String id = primitive.getAsString(); + BlockStateWrapper state = this.factory.apply(id); + if (state != null) { + this.cachedBlockStates.put(entry.getKey(), state); + } + } + } + } + } + + /** + * 保存缓存到文件 + */ + public void saveToCache() throws IOException { + // 创建按ID排序的TreeMap + Map sortedById = new TreeMap<>(); + for (Map.Entry entry : this.cachedBlockStates.entrySet()) { + sortedById.put(entry.getValue(), entry.getKey()); + } + // 创建有序的JSON对象 + JsonObject sortedJsonObject = new JsonObject(); + for (Map.Entry entry : sortedById.entrySet()) { + sortedJsonObject.addProperty(entry.getValue(), entry.getKey().getAsString()); + } + if (sortedJsonObject.isEmpty()) { + if (Files.exists(this.cacheFilePath)) { + Files.delete(this.cacheFilePath); + } + } else { + FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent()); + GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java new file mode 100644 index 000000000..4a636da69 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java @@ -0,0 +1,62 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; + +public class AllocationCacheFile, A> { + private final Map cache; + private final CacheStorage cacheStorage; + private final CacheSerializer serializer; + + public AllocationCacheFile(CacheStorage cacheStorage, CacheSerializer serializer) { + this.cache = new HashMap<>(); + this.cacheStorage = cacheStorage; + this.serializer = serializer; + } + + public Map cache() { + return this.cache; + } + + public void clear() { + this.cache.clear(); + } + + public CompletableFuture load() { + return this.cacheStorage.load().thenAccept(a -> { + Map deserialized = this.serializer.deserialize(a); + this.cache.putAll(deserialized); + }); + } + + public CompletableFuture save() { + Map sortedById = new TreeMap<>(); + for (Map.Entry entry : this.cache.entrySet()) { + sortedById.put(entry.getValue(), entry.getKey()); + } + return this.cacheStorage.save(this.serializer.serialize(sortedById)); + } + + public Iterable> entrySet() { + return this.cache.entrySet(); + } + + public Set keySet() { + return this.cache.keySet(); + } + + public void put(String name, T newId) { + this.cache.put(name, newId); + } + + public T remove(String name) { + return this.cache.remove(name); + } + + public T get(String name) { + return this.cache.get(name); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java new file mode 100644 index 000000000..d230558fa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java @@ -0,0 +1,98 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +public interface CacheFileStorage { + + CompletableFuture load(); + + CompletableFuture save(A value); + + boolean needForceUpdate(); + + static LocalFileCacheStorage local(Path path, CacheFileType type) { + return new LocalFileCacheStorage<>(path, type); + } + + abstract class AbstractRemoteFileCacheStorage implements CacheFileStorage { + + @Override + public boolean needForceUpdate() { + return true; + } + } + + class LocalFileCacheStorage implements CacheFileStorage { + private final CacheFileType fileType; + private final Path filePath; + private long lastModified = 0L; + + public LocalFileCacheStorage(Path filePath, CacheFileType type) { + this.filePath = filePath; + this.fileType = type; + updateLastModified(); + } + + @Override + public boolean needForceUpdate() { + try { + if (!Files.exists(this.filePath)) { + return this.lastModified != 0L; // 文件被删除了,需要更新 + } + long currentModified = Files.getLastModifiedTime(this.filePath).toMillis(); + if (currentModified > this.lastModified) { + this.lastModified = currentModified; + return true; // 文件被修改了,需要强制更新 + } + return false; + } catch (IOException e) { + // 如果无法读取文件信息,保守起见返回 true 强制更新 + return true; + } + } + + @Override + public CompletableFuture load() { + if (!Files.exists(this.filePath)) { + this.lastModified = 0L; // 重置最后修改时间 + return CompletableFuture.completedFuture(this.fileType.create()); + } + try { + A result = this.fileType.read(this.filePath); + updateLastModified(); // 加载成功后更新最后修改时间 + return CompletableFuture.completedFuture(result); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture save(A value) { + try { + this.fileType.write(this.filePath, value); + updateLastModified(); // 保存成功后更新最后修改时间 + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + /** + * 更新最后修改时间 + */ + private void updateLastModified() { + try { + if (Files.exists(this.filePath)) { + this.lastModified = Files.getLastModifiedTime(filePath).toMillis(); + } else { + this.lastModified = 0L; + } + } catch (IOException e) { + this.lastModified = 0L; // 出错时重置 + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java new file mode 100644 index 000000000..b4446a511 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java @@ -0,0 +1,39 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import com.google.gson.JsonObject; +import net.momirealms.craftengine.core.util.GsonHelper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public interface CacheFileType { + JsonCacheFileType JSON = new JsonCacheFileType(); + + T read(Path path) throws IOException; + + void write(Path path, T value) throws IOException; + + T create(); + + class JsonCacheFileType implements CacheFileType { + + @Override + public JsonObject read(Path path) throws IOException { + if (Files.exists(path)) { + return GsonHelper.readJsonFile(path).getAsJsonObject(); + } + return new JsonObject(); + } + + @Override + public void write(Path path, JsonObject value) throws IOException { + GsonHelper.writeJsonFile(value, path); + } + + @Override + public JsonObject create() { + return new JsonObject(); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java new file mode 100644 index 000000000..161caad29 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.HashMap; +import java.util.Map; + +public interface CacheSerializer, A> { + + A serialize(Map obj); + + Map deserialize(A obj); + + static > CacheSerializer json() { + return new JsonSerializer<>(); + } + + class JsonSerializer> implements CacheSerializer { + + @Override + public JsonObject serialize(Map obj) { + JsonObject jsonObject = new JsonObject(); + for (Map.Entry entry : obj.entrySet()) { + if (entry.getKey() instanceof Integer i) { + jsonObject.addProperty(entry.getValue(), i); + } else if (entry.getKey() instanceof String s) { + jsonObject.addProperty(entry.getValue(), s); + } + } + return jsonObject; + } + + @SuppressWarnings("unchecked") + @Override + public Map deserialize(JsonObject obj) { + Map map = new HashMap<>(); + for (Map.Entry entry : obj.entrySet()) { + if (entry.getValue() instanceof JsonPrimitive primitive) { + if (primitive.isNumber()) { + map.put(entry.getKey(), (T) (Integer) primitive.getAsInt()); + } else if (primitive.isString()) { + map.put(entry.getKey(), (T) primitive.getAsString()); + } + } + } + return map; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java new file mode 100644 index 000000000..10856d762 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CompletableFuture; + +public class CacheStorage { + private final CacheFileStorage storage; + private A lastReadValue; + + public CacheStorage(CacheFileStorage storage) { + this.storage = storage; + } + + public CompletableFuture save(@NotNull final A value) { + if (!value.equals(this.lastReadValue) || this.storage.needForceUpdate()) { + this.lastReadValue = value; + return this.storage.save(value); + } + return CompletableFuture.completedFuture(null); + } + + public CompletableFuture load() { + if (this.lastReadValue != null && !this.storage.needForceUpdate()) { + return CompletableFuture.completedFuture(this.lastReadValue); + } + return this.storage.load().thenApply(a -> { + this.lastReadValue = a; + return a; + }); + } +} From ba1fef4de77208178e495581c9b24693d88d9943 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 01:20:32 +0800 Subject: [PATCH 220/226] =?UTF-8?q?=E7=AE=80=E6=98=93=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/blocks/hami_melon.yml | 2 +- .../core/block/AbstractBlockManager.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml index 46e1b51e7..00cc12d7c 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/hami_melon.yml @@ -79,7 +79,7 @@ blocks: - minecraft:mineable/axe - minecraft:sword_efficient state: - state: note_block:30 + auto-state: solid model: template: default:model/cube arguments: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 8f0af28ff..cd43c04cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -315,29 +315,29 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem @Override public void postProcess() { - this.visualBlockStateAllocator.processPendingAllocations(); - try { - this.visualBlockStateAllocator.saveToCache(); - } catch (IOException e) { - AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e); - } this.internalIdAllocator.processPendingAllocations(); try { this.internalIdAllocator.saveToCache(); } catch (IOException e) { AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block states allocation", e); } + this.visualBlockStateAllocator.processPendingAllocations(); + try { + this.visualBlockStateAllocator.saveToCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e); + } } @Override public void preProcess() { + this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1); this.visualBlockStateAllocator.reset(); try { this.visualBlockStateAllocator.loadFromCache(); } catch (IOException e) { AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states allocation cache", e); } - this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1); try { this.internalIdAllocator.loadFromCache(); } catch (IOException e) { @@ -498,8 +498,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem appearanceName, this.visualBlockStateAllocator.assignFixedBlockState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, parsePluginFormattedBlockState(appearanceSection.get("state").toString())) ); - } else if (stateSection.containsKey("auto-state")) { - String autoStateId = stateSection.get("auto-state").toString(); + } else if (appearanceSection.containsKey("auto-state")) { + String autoStateId = appearanceSection.get("auto-state").toString(); AutoStateGroup group = AutoStateGroup.byId(autoStateId); if (group == null) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values())); From eda7ff749d92c94c518bc3d7fecbc96576043105 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 02:44:14 +0800 Subject: [PATCH 221/226] =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/DebugCleanCacheCommand.java | 170 ++++++++++++++++-- .../src/main/resources/translations/en.yml | 2 +- .../core/block/AbstractBlockManager.java | 12 +- .../core/font/AbstractFontManager.java | 5 +- .../allocator/VisualBlockStateAllocator.java | 35 +++- 5 files changed, 198 insertions(+), 26 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java index 8337140f6..d6b01bcd3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -1,14 +1,22 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; -import net.momirealms.craftengine.bukkit.api.CraftEngineItems; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.font.BukkitFontManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.font.BitmapImage; +import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.pack.allocator.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.util.FileUtils; import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; +import org.bukkit.inventory.ItemStack; import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.Command; import org.incendo.cloud.context.CommandContext; @@ -18,11 +26,11 @@ import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.SuggestionProvider; import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; +import java.util.stream.Stream; public class DebugCleanCacheCommand extends BukkitCommandFeature { @@ -48,9 +56,14 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature switch (type) { case "custom-model-data" -> { BukkitItemManager instance = BukkitItemManager.instance(); - Set ids = CraftEngineItems.loadedItems().keySet().stream().map(Key::toString).collect(Collectors.toSet()); + Map> idsMap = new HashMap<>(); + for (CustomItem item : instance.loadedItems().values()) { + Set ids = idsMap.computeIfAbsent(item.clientBoundMaterial(), k -> new HashSet<>()); + ids.add(item.id().asString()); + } int total = 0; - for (Map.Entry entry : instance.itemParser().idAllocators().entrySet()) { + for (Map.Entry entry : getAllCachedCustomModelData().entrySet()) { + Set ids = idsMap.getOrDefault(entry.getKey(), Collections.emptySet()); List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); total += removed.size(); try { @@ -60,18 +73,82 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature return; } for (String id : removed) { - this.plugin().logger().info("Cleaned unused item: " + id); + this.plugin().logger().info("Cleaned unsued item: " + id); } } context.sender().sendMessage("Cleaned " + total + " unused custom model data"); } - case "custom-block-states" -> { - } - case "visual-block-states" -> { - } case "font", "images" -> { BukkitFontManager instance = this.plugin().fontManager(); - + Map> idsMap = new HashMap<>(); + for (BitmapImage image : instance.loadedImages().values()) { + Set ids = idsMap.computeIfAbsent(image.font(), k -> new HashSet<>()); + String id = image.id().toString(); + ids.add(id); + for (int i = 0; i < image.rows(); i++) { + for (int j = 0; j < image.columns(); j++) { + String imageArgs = id + ":" + i + ":" + j; + ids.add(imageArgs); + } + } + } + int total = 0; + for (Map.Entry entry : getAllCachedFont().entrySet()) { + Key font = entry.getKey(); + Set ids = idsMap.getOrDefault(font, Collections.emptySet()); + List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving codepoint allocation for font " + font.asString(), e); + return; + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued image: " + id); + } + total += removed.size(); + } + context.sender().sendMessage("Cleaned " + total + " unused codepoints"); + } + case "custom-block-states" -> { + BukkitBlockManager instance = BukkitBlockManager.instance(); + Set ids = new HashSet<>(); + for (CustomBlock customBlock : instance.loadedBlocks().values()) { + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + ids.add(state.toString()); + } + } + IdAllocator idAllocator = instance.blockParser().internalIdAllocator(); + List removed = idAllocator.cleanupUnusedIds(i -> !ids.contains(i)); + try { + idAllocator.saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving custom block states allocation", e); + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued block state: " + id); + } + context.sender().sendMessage("Cleaned " + removed.size() + " unused custom block states"); + } + case "visual-block-states" -> { + BukkitBlockManager instance = BukkitBlockManager.instance(); + Set ids = new HashSet<>(); + for (CustomBlock customBlock : instance.loadedBlocks().values()) { + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + ids.add(state.vanillaBlockState()); + } + } + VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator(); + List removed = visualBlockStateAllocator.cleanupUnusedIds(i -> !ids.contains(i)); + try { + visualBlockStateAllocator.saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving visual block states allocation", e); + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued block appearance: " + id); + } + context.sender().sendMessage("Cleaned " + removed.size() + " unused block state appearances"); } } }); @@ -81,4 +158,71 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature public String getFeatureID() { return "debug_clean_cache"; } + + public Map getAllCachedCustomModelData() { + Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("custom-model-data"); + + Map idAllocators = new HashMap<>(); + try (Stream files = Files.list(cacheDir)) { + files.filter(this::isJsonFile) + .forEach(file -> processIdAllocatorFile(cacheDir, file, idAllocators)); + + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to process: " + cacheDir.getFileName(), e); + } + + return idAllocators; + } + + public Map getAllCachedFont() { + Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("font"); + + try { + List namespaces = FileUtils.collectNamespaces(cacheDir); + Map idAllocators = new HashMap<>(); + + for (Path namespace : namespaces) { + processNamespace(namespace, idAllocators); + } + return idAllocators; + + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to load cached id allocators from: " + cacheDir, e); + return Collections.emptyMap(); + } + } + + private void processNamespace(Path namespace, Map idAllocators) { + if (!Files.isDirectory(namespace)) { + return; + } + + try (Stream files = Files.list(namespace)) { + files.filter(this::isJsonFile) + .forEach(file -> processIdAllocatorFile(namespace, file, idAllocators)); + + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to process namespace: " + namespace.getFileName(), e); + } + } + + private boolean isJsonFile(Path file) { + return Files.isRegularFile(file) && file.getFileName().toString().endsWith(".json"); + } + + private void processIdAllocatorFile(Path namespace, Path file, Map idAllocators) { + try { + String namespaceName = namespace.getFileName().toString(); + String fileName = FileUtils.pathWithoutExtension(file.getFileName().toString()); + + Key font = Key.of(namespaceName, fileName); + IdAllocator allocator = new IdAllocator(file); + allocator.loadFromCache(); + + idAllocators.put(font, allocator); + + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to load id allocator from: " + file, e); + } + } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index acd02e1a4..2c0551596 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -278,7 +278,7 @@ warning.config.block.state.entity_renderer.model_engine.missing_model: " warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." warning.config.block.state.invalid_auto_state: "Issue found in file - The block '' is using an invalid auto-state ''. Allowed values: []." -warning.config.block.state.auto_state.exhausted: "Issue found in file - Cannot allocate visual block state for block '' as the slots('') in group '' have been exhausted." +warning.config.block.state.auto_state.exhausted: "Issue found in file - The visual state group '' has reached its maximum capacity of '' slots and cannot allocate a state for block ''." warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in block-state-mappings." warning.config.block.state.invalid_vanilla_id: "Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." warning.config.block.state.invalid_id: "Issue found in file - The block state ID range () used by block '' is outside the valid range of 0 to . Please add more server-side blocks in 'config.yml' if the current slots are exhausted." diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index cd43c04cd..9fcd76918 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -313,6 +313,14 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.pendingConfigSections.add(section); } + public IdAllocator internalIdAllocator() { + return internalIdAllocator; + } + + public VisualBlockStateAllocator visualBlockStateAllocator() { + return visualBlockStateAllocator; + } + @Override public void postProcess() { this.internalIdAllocator.processPendingAllocations(); @@ -514,7 +522,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> { + CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> ResourceConfigUtils.runCatching(path, node, () -> { if (t2 != null) { if (t2 instanceof CompletionException e) { Throwable cause = e.getCause(); @@ -635,7 +643,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 抛出次要警告 exceptionCollector.throwIfPresent(); - }); + }, () -> GsonHelper.get().toJson(section))); }, () -> GsonHelper.get().toJson(section))); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 5c44b4428..f7a3f02b9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -32,6 +32,7 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; public abstract class AbstractFontManager implements FontManager { private final CraftEngine plugin; @@ -511,10 +512,6 @@ public abstract class AbstractFontManager implements FontManager { }); } - public Map idAllocators() { - return this.idAllocators; - } - @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractFontManager.this.images.containsKey(id)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java index ab7d7cd86..db4576bd0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.core.pack.allocator; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -15,6 +17,7 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.function.Predicate; public class VisualBlockStateAllocator { private final Path cacheFilePath; @@ -32,7 +35,9 @@ public class VisualBlockStateAllocator { } public void reset() { - Arrays.fill(this.pendingAllocationFutures, new ArrayList<>()); + for (int i = 0; i < this.pendingAllocationFutures.length; i++) { + this.pendingAllocationFutures[i] = new ArrayList<>(); + } this.cachedBlockStates.clear(); this.pendingAllocations.clear(); } @@ -53,6 +58,19 @@ public class VisualBlockStateAllocator { return future; } + public List cleanupUnusedIds(Predicate shouldRemove) { + List idsToRemove = new ArrayList<>(); + for (Map.Entry entry : this.cachedBlockStates.entrySet()) { + if (shouldRemove.test(entry.getValue())) { + idsToRemove.add(entry.getKey()); + } + } + for (String id : idsToRemove) { + this.cachedBlockStates.remove(id); + } + return idsToRemove; + } + public void processPendingAllocations() { // 先处理缓存的 for (Map.Entry entry : this.cachedBlockStates.entrySet()) { @@ -64,12 +82,17 @@ public class VisualBlockStateAllocator { if (!candidate.isUsed()) { // 获取当前的安排任务 Pair> pair = this.pendingAllocations.get(entry.getKey()); - // 如果候选满足组,那么直接允许起飞 - if (pair != null && pair.left().test(candidate.blockState())) { - pair.right().complete(candidate.blockState()); + if (pair != null) { + // 如果候选满足组,那么直接允许起飞 + if (pair.left().test(candidate.blockState())) { + pair.right().complete(candidate.blockState()); + } else { + // 不满足候选组要求,那就等着分配新的吧 + } + } else { + // 尽管未被使用,该槽位也应该被占用,以避免被自动分配到 + candidate.setUsed(); } - // 尽管未被使用,该槽位也应该被占用,以避免被自动分配到 - candidate.setUsed(); } // 被使用了就随他去 } From fec7dfe47335f7775af0aa11b8db9ff348f033f1 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 02:56:34 +0800 Subject: [PATCH 222/226] Update DebugCleanCacheCommand.java --- .../plugin/command/feature/DebugCleanCacheCommand.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java index d6b01bcd3..2371f0c9f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -13,6 +13,7 @@ import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.FileUtils; import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; @@ -165,7 +166,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature Map idAllocators = new HashMap<>(); try (Stream files = Files.list(cacheDir)) { files.filter(this::isJsonFile) - .forEach(file -> processIdAllocatorFile(cacheDir, file, idAllocators)); + .forEach(file -> processIdAllocatorFile("minecraft", file, idAllocators)); } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to process: " + cacheDir.getFileName(), e); @@ -199,7 +200,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature try (Stream files = Files.list(namespace)) { files.filter(this::isJsonFile) - .forEach(file -> processIdAllocatorFile(namespace, file, idAllocators)); + .forEach(file -> processIdAllocatorFile(namespace.getFileName().toString(), file, idAllocators)); } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to process namespace: " + namespace.getFileName(), e); @@ -210,9 +211,8 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature return Files.isRegularFile(file) && file.getFileName().toString().endsWith(".json"); } - private void processIdAllocatorFile(Path namespace, Path file, Map idAllocators) { + private void processIdAllocatorFile(String namespaceName, Path file, Map idAllocators) { try { - String namespaceName = namespace.getFileName().toString(); String fileName = FileUtils.pathWithoutExtension(file.getFileName().toString()); Key font = Key.of(namespaceName, fileName); From bf8e4afd0ba62ecae71a8b87737ed63675065194 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 03:21:19 +0800 Subject: [PATCH 223/226] =?UTF-8?q?=E5=AE=8C=E5=96=84debug=E6=8C=87?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DebugAppearanceStateUsageCommand.java | 113 ++++++++++-------- .../feature/DebugCleanCacheCommand.java | 1 - .../feature/DebugRealStateUsageCommand.java | 75 ++++++------ common-files/src/main/resources/commands.yml | 2 + .../configuration/blocks/topaz_ore.yml | 2 +- .../core/block/AbstractBlockManager.java | 5 +- .../core/font/AbstractFontManager.java | 1 - .../core/pack/allocator/IdAllocator.java | 4 + .../allocator/VisualBlockStateAllocator.java | 9 +- 9 files changed, 121 insertions(+), 91 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java index 2192b12d4..78b10ff30 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java @@ -1,10 +1,27 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.Command; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature { @@ -15,54 +32,56 @@ public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder -// .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().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); -// } -// })) + .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().blockStateArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); + } + })) .handler(context -> { -// String data = context.get("id"); -// BukkitBlockManager blockManager = plugin().blockManager(); -// Key baseBlockId = Key.of(data); -// List appearances = blockManager.blockAppearanceArranger().get(baseBlockId); -// if (appearances == null) return; -// int i = 0; -// Component block = Component.text(baseBlockId + ": "); -// plugin().senderFactory().wrap(context.sender()).sendMessage(block); -// -// List batch = new ArrayList<>(); -// for (int appearance : appearances) { -// Component text = Component.text("|"); -// List reals = blockManager.appearanceToRealStates(appearance); -// if (reals == null || reals.isEmpty()) { -// Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN); -// hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN)); -// text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)); -// } else { -// Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.RED); -// List hoverChildren = new ArrayList<>(); -// hoverChildren.add(Component.newline()); -// hoverChildren.add(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.RED)); -// for (int real : reals) { -// hoverChildren.add(Component.newline()); -// hoverChildren.add(Component.text(blockManager.getImmutableBlockStateUnsafe(real).toString()).color(NamedTextColor.GRAY)); -// } -// text = text.color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover.children(hoverChildren))); -// } -// batch.add(text); -// i++; -// if (batch.size() == 100) { -// plugin().senderFactory().wrap(context.sender()) -// .sendMessage(Component.text("").children(batch)); -// batch.clear(); -// } -// } -// if (!batch.isEmpty()) { -// plugin().senderFactory().wrap(context.sender()) -// .sendMessage(Component.text("").children(batch)); -// batch.clear(); -// } + String data = context.get("id"); + BukkitBlockManager blockManager = plugin().blockManager(); + Key baseBlockId = Key.of(data); + List appearances = blockManager.blockStateArranger().get(baseBlockId); + if (appearances == null) return; + int i = 0; + Component block = Component.text(baseBlockId + ": "); + plugin().senderFactory().wrap(context.sender()).sendMessage(block); + VisualBlockStateAllocator allocator = blockManager.blockParser().visualBlockStateAllocator(); + List batch = new ArrayList<>(); + for (BlockStateWrapper appearance : appearances) { + Component text = Component.text("|"); + List reals = blockManager.appearanceToRealStates(appearance.registryId()); + if (reals.isEmpty()) { + Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN); + hover = hover.append(Component.newline()).append(Component.text(appearance.getAsString()).color(NamedTextColor.GREEN)); + text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)); + } else { + boolean isFixed = allocator.isForcedState(appearance); + NamedTextColor namedTextColor = isFixed ? NamedTextColor.RED : NamedTextColor.YELLOW; + Component hover = Component.text(baseBlockId.value() + ":" + i).color(namedTextColor); + List hoverChildren = new ArrayList<>(); + hoverChildren.add(Component.newline()); + hoverChildren.add(Component.text(appearance.getAsString()).color(namedTextColor)); + for (int real : reals) { + hoverChildren.add(Component.newline()); + hoverChildren.add(Component.text(blockManager.getImmutableBlockStateUnsafe(real).toString()).color(NamedTextColor.GRAY)); + } + text = text.color(namedTextColor).hoverEvent(HoverEvent.showText(hover.children(hoverChildren))); + } + batch.add(text); + i++; + if (batch.size() == 100) { + plugin().senderFactory().wrap(context.sender()) + .sendMessage(Component.text("").children(batch)); + batch.clear(); + } + } + if (!batch.isEmpty()) { + plugin().senderFactory().wrap(context.sender()) + .sendMessage(Component.text("").children(batch)); + batch.clear(); + } }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java index 2371f0c9f..3c3ad6ed7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -13,7 +13,6 @@ import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.FileUtils; import net.momirealms.craftengine.core.util.Key; import org.bukkit.command.CommandSender; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java index d30863b8c..14e9888d9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugRealStateUsageCommand.java @@ -1,11 +1,22 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.block.BlockManager; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.config.Config; import org.bukkit.command.CommandSender; import org.incendo.cloud.Command; +import java.util.ArrayList; +import java.util.List; + public class DebugRealStateUsageCommand extends BukkitCommandFeature { public DebugRealStateUsageCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { @@ -15,45 +26,33 @@ public class DebugRealStateUsageCommand extends BukkitCommandFeature assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder -// .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().blockAppearanceArranger().keySet().stream().map(it -> Suggestion.suggestion(it.toString())).toList()); -// } -// })) .handler(context -> { -// String data = context.get("id"); -// BukkitBlockManager blockManager = plugin().blockManager(); -// Key baseBlockId = Key.of(data); -// List reals = blockManager.realBlockArranger().get(baseBlockId); -// if (reals == null) return; -// int i = 0; -// Component block = Component.text(baseBlockId + ": "); -// plugin().senderFactory().wrap(context.sender()).sendMessage(block); -// -// List batch = new ArrayList<>(100); -// for (int real : reals) { -// ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(real); -// if (state.isEmpty()) { -// Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.GREEN); -// batch.add(Component.text("|").color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover))); -// } else { -// Component hover = Component.text("craftengine:" + baseBlockId.value() + "_" + i).color(NamedTextColor.RED); -// hover = hover.append(Component.newline()).append(Component.text(state.toString()).color(NamedTextColor.GRAY)); -// batch.add(Component.text("|").color(NamedTextColor.RED).hoverEvent(HoverEvent.showText(hover))); -// } -// i++; -// if (batch.size() == 100) { -// plugin().senderFactory().wrap(context.sender()) -// .sendMessage(Component.text("").children(batch)); -// batch.clear(); -// } -// } -// if (!batch.isEmpty()) { -// plugin().senderFactory().wrap(context.sender()) -// .sendMessage(Component.text("").children(batch)); -// batch.clear(); -// } + BukkitBlockManager blockManager = plugin().blockManager(); + plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Serverside block state usage:")); + List batch = new ArrayList<>(100); + IdAllocator idAllocator = blockManager.blockParser().internalIdAllocator(); + for (int i = 0; i < Config.serverSideBlocks(); i++) { + ImmutableBlockState state = blockManager.getImmutableBlockStateUnsafe(i + blockManager.vanillaBlockStateCount()); + if (state.isEmpty()) { + Component hover = Component.text(BlockManager.createCustomBlockKey(i).asString()).color(NamedTextColor.GREEN); + batch.add(Component.text("|").color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover))); + } else { + NamedTextColor namedTextColor = idAllocator.isForced(state.toString()) ? NamedTextColor.RED : NamedTextColor.YELLOW; + Component hover = Component.text(BlockManager.createCustomBlockKey(i).asString()).color(namedTextColor); + hover = hover.append(Component.newline()).append(Component.text(state.toString()).color(NamedTextColor.GRAY)); + batch.add(Component.text("|").color(namedTextColor).hoverEvent(HoverEvent.showText(hover))); + } + if (batch.size() == 100) { + plugin().senderFactory().wrap(context.sender()) + .sendMessage(Component.text("").children(batch)); + batch.clear(); + } + } + if (!batch.isEmpty()) { + plugin().senderFactory().wrap(context.sender()) + .sendMessage(Component.text("").children(batch)); + batch.clear(); + } }); } diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index 9ba12ceeb..a96b802e0 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -152,7 +152,9 @@ debug_real_state_usage: permission: ce.command.debug.state_usage usage: - /craftengine debug real-state-usage + - /craftengine debug serverside-state-usage - /ce debug real-state-usage + - /ce debug serverside-state-usage debug_item_data: enable: true diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index 6721d3027..f120a9776 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -68,7 +68,7 @@ blocks: arguments: break_power: 2 state: - state: note_block:14 + auto-state: solid model: template: default:model/simplified_cube_all arguments: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 9fcd76918..86719197c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.core.block; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -155,6 +154,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Optional.ofNullable(this.byId.get(id)); } + public Map> blockStateArranger() { + return this.blockStateArranger; + } + protected abstract void applyPlatformSettings(ImmutableBlockState state); @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index f7a3f02b9..639076b6c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -32,7 +32,6 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; public abstract class AbstractFontManager implements FontManager { private final CraftEngine plugin; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java index 9a84c875a..b8657238e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java @@ -124,6 +124,10 @@ public class IdAllocator { return CompletableFuture.completedFuture(id); } + public boolean isForced(String name) { + return this.forcedIdMap.containsKey(name); + } + public List> getFixedIdsBetween(int minId, int maxId) { BiMap inverse = this.forcedIdMap.inverse(); List> result = new ArrayList<>(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java index db4576bd0..7138465fa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java @@ -1,7 +1,5 @@ package net.momirealms.craftengine.core.pack.allocator; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -27,6 +25,7 @@ public class VisualBlockStateAllocator { private final List>>[] pendingAllocationFutures = new List[AutoStateGroup.values().length]; private final BlockStateCandidate[] candidates; private final Function factory; + private final Set forcedStates = new HashSet<>(); public VisualBlockStateAllocator(Path cacheFilePath, BlockStateCandidate[] candidates, Function factory) { this.cacheFilePath = cacheFilePath; @@ -40,10 +39,16 @@ public class VisualBlockStateAllocator { } this.cachedBlockStates.clear(); this.pendingAllocations.clear(); + this.forcedStates.clear(); + } + + public boolean isForcedState(final BlockStateWrapper state) { + return this.forcedStates.contains(state); } public CompletableFuture assignFixedBlockState(String name, BlockStateWrapper state) { this.cachedBlockStates.remove(name); + this.forcedStates.add(state); BlockStateCandidate candidate = this.candidates[state.registryId()]; if (candidate != null) { candidate.setUsed(); From aa5e58fb266318c2c080b8d89edd41e165ee73e3 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 30 Sep 2025 11:24:13 +0800 Subject: [PATCH 224/226] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D1.20?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/core/pack/allocator/IdAllocator.java | 2 +- .../core/pack/allocator/VisualBlockStateAllocator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java index b8657238e..3a42d71ed 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java @@ -235,7 +235,7 @@ public class IdAllocator { sortedJsonObject.addProperty(entry.getValue(), entry.getKey()); } - if (sortedJsonObject.isEmpty()) { + if (sortedJsonObject.asMap().isEmpty()) { if (Files.exists(this.cacheFilePath)) { Files.delete(this.cacheFilePath); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java index 7138465fa..7087b55d2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java @@ -168,7 +168,7 @@ public class VisualBlockStateAllocator { for (Map.Entry entry : sortedById.entrySet()) { sortedJsonObject.addProperty(entry.getValue(), entry.getKey().getAsString()); } - if (sortedJsonObject.isEmpty()) { + if (sortedJsonObject.asMap().isEmpty()) { if (Files.exists(this.cacheFilePath)) { Files.delete(this.cacheFilePath); } From 2772c77247da7022539053e380beac8f2e1f6328 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Tue, 30 Sep 2025 12:20:49 +0800 Subject: [PATCH 225/226] =?UTF-8?q?feat(func):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BC=A4=E5=AE=B3=E7=8E=A9=E5=AE=B6=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/user/BukkitServerPlayer.java | 13 ++++- .../core/entity/player/Player.java | 2 + .../plugin/context/event/EventFunctions.java | 1 + .../context/function/CommonFunctions.java | 1 + .../context/function/DamageFunction.java | 51 +++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 36ed88089..2b578908c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -5,6 +5,8 @@ import com.google.common.collect.Lists; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder; @@ -45,6 +47,8 @@ import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.EquipmentSlot; @@ -506,7 +510,7 @@ public class BukkitServerPlayer extends Player { if (this.gameTicks % 20 == 0) { this.updateGUI(); } - if (this.isDestroyingBlock) { + if (this.isDestroyingBlock) { this.tickBlockDestroy(); } if (Config.predictBreaking() && !this.isDestroyingCustomBlock) { @@ -1133,4 +1137,11 @@ public class BukkitServerPlayer extends Player { Location location = new Location((org.bukkit.World) worldPosition.world().platformWorld(), worldPosition.x(), worldPosition.y(), worldPosition.z(), worldPosition.yRot(), worldPosition.xRot()); this.platformPlayer().teleportAsync(location, PlayerTeleportEvent.TeleportCause.PLUGIN); } + + @Override + public void damage(double amount, Key damageType) { + @SuppressWarnings("deprecation") + DamageType type = Registry.DAMAGE_TYPE.get(KeyUtils.toNamespacedKey(damageType)); + this.platformPlayer().damage(amount, DamageSource.builder(type != null ? type : DamageType.GENERIC).build()); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 6df8b25c7..f0da08202 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -169,4 +169,6 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract CooldownData cooldown(); public abstract void teleport(WorldPosition worldPosition); + + public abstract void damage(double amount, Key damageType); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java index 8c36f5af6..e23bee3d9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java @@ -45,6 +45,7 @@ public class EventFunctions { register(CommonFunctions.TELEPORT, new TeleportFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.SET_VARIABLE, new SetVariableFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.TOAST, new ToastFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.DAMAGE, new DamageFunction.FactoryImpl<>(EventConditions::fromMap)); } public static void register(Key key, FunctionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java index 143b02f1d..56c9ab931 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java @@ -34,4 +34,5 @@ public final class CommonFunctions { public static final Key TELEPORT = Key.of("craftengine:teleport"); public static final Key TOAST = Key.of("craftengine:toast"); public static final Key SET_VARIABLE = Key.of("craftengine:set_variable"); + public static final Key DAMAGE = Key.of("craftengine:damage"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java new file mode 100644 index 000000000..6ebb22d35 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; + +public class DamageFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final Key damageType; + private final NumberProvider amount; + + public DamageFunction(PlayerSelector selector, Key damageType, NumberProvider amount, List> predicates) { + super(predicates); + this.selector = selector; + this.damageType = damageType; + this.amount = amount; + } + + @Override + protected void runInternal(CTX ctx) { + selector.get(ctx).forEach(p -> p.damage(amount.getDouble(ctx), damageType)); + } + + @Override + public Key type() { + return CommonFunctions.DAMAGE; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + PlayerSelector selector = PlayerSelectors.fromObject(arguments.getOrDefault("target", "self"), conditionFactory()); + Key damageType = Key.of(ResourceConfigUtils.getAsString(arguments.getOrDefault("damage-type", "generic"))); + NumberProvider amount = NumberProviders.fromObject(arguments.getOrDefault("amount", 1f)); + return new DamageFunction<>(selector, damageType, amount, getPredicates(arguments)); + } + } +} From db07951974cb6be7538b42c1ea45649e6deaf98e Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 18:27:58 +0800 Subject: [PATCH 226/226] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=B2=E7=9F=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/DebugCleanCacheCommand.java | 210 ++++++++++-------- .../feature/DebugTargetBlockCommand.java | 9 +- .../core/item/AbstractItemManager.java | 25 ++- .../core/plugin/config/Config.java | 6 + gradle.properties | 2 +- 5 files changed, 141 insertions(+), 111 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java index 3c3ad6ed7..bf41e6f10 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -41,10 +41,10 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature @Override public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder - .required("type", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { + .optional("type", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { @Override public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { - return CompletableFuture.completedFuture(List.of(Suggestion.suggestion("custom-model-data"), Suggestion.suggestion("custom-block-states"), Suggestion.suggestion("visual-block-states"), Suggestion.suggestion("font"))); + return CompletableFuture.completedFuture(List.of(Suggestion.suggestion("custom-model-data"), Suggestion.suggestion("custom-block-states"), Suggestion.suggestion("visual-block-states"), Suggestion.suggestion("font"), Suggestion.suggestion("all"))); } })) .handler(context -> { @@ -52,103 +52,17 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature context.sender().sendMessage("The plugin is reloading. Please wait until the process is complete."); return; } - String type = context.get("type"); + String type = context.getOrDefault("type", "all"); switch (type) { - case "custom-model-data" -> { - BukkitItemManager instance = BukkitItemManager.instance(); - Map> idsMap = new HashMap<>(); - for (CustomItem item : instance.loadedItems().values()) { - Set ids = idsMap.computeIfAbsent(item.clientBoundMaterial(), k -> new HashSet<>()); - ids.add(item.id().asString()); - } - int total = 0; - for (Map.Entry entry : getAllCachedCustomModelData().entrySet()) { - Set ids = idsMap.getOrDefault(entry.getKey(), Collections.emptySet()); - List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); - total += removed.size(); - try { - entry.getValue().saveToCache(); - } catch (IOException e) { - this.plugin().logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e); - return; - } - for (String id : removed) { - this.plugin().logger().info("Cleaned unsued item: " + id); - } - } - context.sender().sendMessage("Cleaned " + total + " unused custom model data"); - } - case "font", "images" -> { - BukkitFontManager instance = this.plugin().fontManager(); - Map> idsMap = new HashMap<>(); - for (BitmapImage image : instance.loadedImages().values()) { - Set ids = idsMap.computeIfAbsent(image.font(), k -> new HashSet<>()); - String id = image.id().toString(); - ids.add(id); - for (int i = 0; i < image.rows(); i++) { - for (int j = 0; j < image.columns(); j++) { - String imageArgs = id + ":" + i + ":" + j; - ids.add(imageArgs); - } - } - } - int total = 0; - for (Map.Entry entry : getAllCachedFont().entrySet()) { - Key font = entry.getKey(); - Set ids = idsMap.getOrDefault(font, Collections.emptySet()); - List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); - try { - entry.getValue().saveToCache(); - } catch (IOException e) { - this.plugin().logger().warn("Error while saving codepoint allocation for font " + font.asString(), e); - return; - } - for (String id : removed) { - this.plugin().logger().info("Cleaned unsued image: " + id); - } - total += removed.size(); - } - context.sender().sendMessage("Cleaned " + total + " unused codepoints"); - } - case "custom-block-states" -> { - BukkitBlockManager instance = BukkitBlockManager.instance(); - Set ids = new HashSet<>(); - for (CustomBlock customBlock : instance.loadedBlocks().values()) { - for (ImmutableBlockState state : customBlock.variantProvider().states()) { - ids.add(state.toString()); - } - } - IdAllocator idAllocator = instance.blockParser().internalIdAllocator(); - List removed = idAllocator.cleanupUnusedIds(i -> !ids.contains(i)); - try { - idAllocator.saveToCache(); - } catch (IOException e) { - this.plugin().logger().warn("Error while saving custom block states allocation", e); - } - for (String id : removed) { - this.plugin().logger().info("Cleaned unsued block state: " + id); - } - context.sender().sendMessage("Cleaned " + removed.size() + " unused custom block states"); - } - case "visual-block-states" -> { - BukkitBlockManager instance = BukkitBlockManager.instance(); - Set ids = new HashSet<>(); - for (CustomBlock customBlock : instance.loadedBlocks().values()) { - for (ImmutableBlockState state : customBlock.variantProvider().states()) { - ids.add(state.vanillaBlockState()); - } - } - VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator(); - List removed = visualBlockStateAllocator.cleanupUnusedIds(i -> !ids.contains(i)); - try { - visualBlockStateAllocator.saveToCache(); - } catch (IOException e) { - this.plugin().logger().warn("Error while saving visual block states allocation", e); - } - for (String id : removed) { - this.plugin().logger().info("Cleaned unsued block appearance: " + id); - } - context.sender().sendMessage("Cleaned " + removed.size() + " unused block state appearances"); + case "custom-model-data" -> handleCustomModelData(context); + case "font", "images" -> handleFont(context); + case "custom-block-states" -> handleCustomBlockState(context); + case "visual-block-states" -> handleVisualBlockState(context); + case "all" -> { + handleCustomModelData(context); + handleFont(context); + handleCustomBlockState(context); + handleVisualBlockState(context); } } }); @@ -159,6 +73,106 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature return "debug_clean_cache"; } + private void handleVisualBlockState(CommandContext context) { + BukkitBlockManager instance = BukkitBlockManager.instance(); + Set ids = new HashSet<>(); + for (CustomBlock customBlock : instance.loadedBlocks().values()) { + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + ids.add(state.vanillaBlockState()); + } + } + VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator(); + List removed = visualBlockStateAllocator.cleanupUnusedIds(i -> !ids.contains(i)); + try { + visualBlockStateAllocator.saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving visual block states allocation", e); + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued block appearance: " + id); + } + context.sender().sendMessage("Cleaned " + removed.size() + " unused block state appearances"); + } + + private void handleCustomBlockState(CommandContext context) { + BukkitBlockManager instance = BukkitBlockManager.instance(); + Set ids = new HashSet<>(); + for (CustomBlock customBlock : instance.loadedBlocks().values()) { + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + ids.add(state.toString()); + } + } + IdAllocator idAllocator = instance.blockParser().internalIdAllocator(); + List removed = idAllocator.cleanupUnusedIds(i -> !ids.contains(i)); + try { + idAllocator.saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving custom block states allocation", e); + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued block state: " + id); + } + context.sender().sendMessage("Cleaned " + removed.size() + " unused custom block states"); + } + + private void handleFont(CommandContext context) { + BukkitFontManager instance = this.plugin().fontManager(); + Map> idsMap = new HashMap<>(); + for (BitmapImage image : instance.loadedImages().values()) { + Set ids = idsMap.computeIfAbsent(image.font(), k -> new HashSet<>()); + String id = image.id().toString(); + ids.add(id); + for (int i = 0; i < image.rows(); i++) { + for (int j = 0; j < image.columns(); j++) { + String imageArgs = id + ":" + i + ":" + j; + ids.add(imageArgs); + } + } + } + int total = 0; + for (Map.Entry entry : getAllCachedFont().entrySet()) { + Key font = entry.getKey(); + Set ids = idsMap.getOrDefault(font, Collections.emptySet()); + List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving codepoint allocation for font " + font.asString(), e); + return; + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued image: " + id); + } + total += removed.size(); + } + context.sender().sendMessage("Cleaned " + total + " unused codepoints"); + } + + private void handleCustomModelData(CommandContext context) { + BukkitItemManager instance = BukkitItemManager.instance(); + Map> idsMap = new HashMap<>(); + for (CustomItem item : instance.loadedItems().values()) { + Set ids = idsMap.computeIfAbsent(item.clientBoundMaterial(), k -> new HashSet<>()); + ids.add(item.id().asString()); + } + int total = 0; + for (Map.Entry entry : getAllCachedCustomModelData().entrySet()) { + Set ids = idsMap.getOrDefault(entry.getKey(), Collections.emptySet()); + List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); + total += removed.size(); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e); + return; + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unsued item: " + id); + } + } + context.sender().sendMessage("Cleaned " + total + " unused custom model data"); + } + public Map getAllCachedCustomModelData() { Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("custom-model-data"); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugTargetBlockCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugTargetBlockCommand.java index 4ed380551..c684296a8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugTargetBlockCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugTargetBlockCommand.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; @@ -10,6 +11,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.sender.Sender; +import net.momirealms.craftengine.core.plugin.config.Config; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.command.CommandSender; @@ -44,15 +46,14 @@ public class DebugTargetBlockCommand extends BukkitCommandFeature Sender sender = plugin().senderFactory().wrap(context.sender()); sender.sendMessage(Component.text(bData)); int id = BlockStateUtils.blockStateToId(blockState); - - Object holder = BukkitBlockManager.instance().getMinecraftBlockHolder(id); - if (holder != null) { + if (!BlockStateUtils.isVanillaBlock(id)) { + Object holder = BukkitBlockManager.instance().getMinecraftBlockHolder(id); ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(id); if (immutableBlockState != null) { sender.sendMessage(Component.text(immutableBlockState.toString())); } ImmutableBlockState dataInCache = plugin().worldManager().getWorld(block.getWorld().getUID()).getBlockStateAtIfLoaded(LocationUtils.toBlockPos(block.getLocation())); - sender.sendMessage(Component.text("cache-state: " + !dataInCache.isEmpty())); + sender.sendMessage(Component.text("cache-state: " + (dataInCache != null && !dataInCache.isEmpty()))); try { @SuppressWarnings("unchecked") Set tags = (Set) CoreReflections.field$Holder$Reference$tags.get(holder); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 9d3c7b909..7928753e7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -338,7 +338,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } private boolean needsItemModelCompatibility() { - return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2); + return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && VersionHelper.isOrAbove1_21_2(); //todo 能否通过客户端包解决问题 } public Map idAllocators() { @@ -403,6 +403,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // custom model data CompletableFuture customModelDataFuture; + boolean forceCustomModelData; if (!isVanillaItem) { // 如果用户指定了,说明要手动分配,不管他是什么版本,都强制设置模型值 @@ -415,9 +416,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData)); } customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).assignFixedId(id.asString(), customModelData); + forceCustomModelData = true; } // 用户没指定custom-model-data,则看当前资源包版本兼容需求 else { + forceCustomModelData = false; // 如果最低版本要1.21.1以下支持 if (needsCustomModelDataCompatibility()) { customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).requestAutoId(id.asString()); @@ -428,6 +431,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } } } else { + forceCustomModelData = false; // 原版物品不应该有这个 customModelDataFuture = CompletableFuture.completedFuture(0); } @@ -458,15 +462,17 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // item model Key itemModel = null; + boolean forceItemModel = false; // 如果这个版本可以使用 item model if (!isVanillaItem && needsItemModelCompatibility()) { // 如果用户主动设定了item model,那么肯定要设置 if (section.containsKey("item-model")) { itemModel = Key.from(section.get("item-model").toString()); + forceItemModel = true; } // 用户没设置item model也没设置custom model data,那么为他生成一个基于物品id的item model - else if (customModelData == 0) { + else if (customModelData == 0 || Config.alwaysUseItemModel()) { itemModel = id; } // 用户没设置item model但是有custom model data,那么就使用custom model data @@ -476,11 +482,17 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl boolean clientBoundModel = VersionHelper.PREMIUM && (section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel()); CustomItem.Builder itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial); - if (customModelData > 0) { + + // 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model + Map modelSection = MiscUtils.castToMap(section.get("model"), true); + Map legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true); + boolean hasModelSection = modelSection != null || legacyModelSection != null; + + if (customModelData > 0 && (hasModelSection || forceCustomModelData)) { if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData)); else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); } - if (itemModel != null) { + if (itemModel != null && (hasModelSection || forceItemModel)) { if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModel)); else itemBuilder.dataModifier(new ItemModelModifier<>(itemModel)); } @@ -586,10 +598,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return; } - // 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model - Map modelSection = MiscUtils.castToMap(section.get("model"), true); - Map legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true); - if (modelSection == null && legacyModelSection == null) { + if (!hasModelSection) { collector.throwIfPresent(); return; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 357414f9a..788862341 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -164,6 +164,7 @@ public class Config { protected boolean item$update_triggers$pick_up; protected int item$custom_model_data_starting_value$default; protected Map item$custom_model_data_starting_value$overrides; + protected boolean item$always_use_item_model; protected String equipment$sacrificed_vanilla_armor$type; protected Key equipment$sacrificed_vanilla_armor$asset_id; @@ -407,6 +408,7 @@ public class Config { item$update_triggers$drop = config.getBoolean("item.update-triggers.drop", false); item$update_triggers$pick_up = config.getBoolean("item.update-triggers.pick-up", false); item$custom_model_data_starting_value$default = config.getInt("item.custom-model-data-starting-value.default", 10000); + item$always_use_item_model = config.getBoolean("item.always-use-item-model", true) && VersionHelper.isOrAbove1_21_2(); Section customModelDataOverridesSection = config.getSection("item.custom-model-data-starting-value.overrides"); if (customModelDataOverridesSection != null) { @@ -549,6 +551,10 @@ public class Config { return instance.block$serverside_blocks; } + public static boolean alwaysUseItemModel() { + return instance.item$always_use_item_model; + } + public static boolean filterConfigurationPhaseDisconnect() { return instance.filterConfigurationPhaseDisconnect; } diff --git a/gradle.properties b/gradle.properties index 1de766a2f..f633a3e61 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.63.8 +project_version=0.0.63.9 config_version=47 lang_version=32 project_group=net.momirealms

A7Us`8(zFx+$Kss(C1l-og&$frYp2Hp&W35aYAO@#T%Lj&n&n{BW z-WF18_$p#!%Zdmn(pWBKS`TDMqky=<0-urx$pzY>Im=K=6ejB{3RLm-o0nCL9#ZQ) zx`q{)tDdUuoyigb-DJUM@sy&(-75r6W65sTk=+h+%;Y5i?(6jJUnSzgBDObu$LBEI93ng0w&5267Uywe38^cMjAoQ z&1HRJ0n~n*k3X)vDyELOqKQ^GpR)mQO|!!t-Yih*l_us57fEnk_{jJrv@~;CSt-7h zLc1NM$L){cMnNNk-nlKaJdXb#A<^idG-Y ziM|S?2s2Lg%UIlZhYxco#tgGpA(EThLs|0Z==a;ooY&GkjmxkaWDG~qkm>Ko5n-yGr$-7w~#aRFGLO9 z)2_h|+m*Fje=)VzTR){7QBQOfU7-+omi>@19iw*`lM});4khgF3+4+9X^afMlDRA^r!WR`Be3~5QXz7}$$R|EGilH69YTh5INON@*+cB;bYy??IsLchS4hbeJm z{e^Ap+#%%&7j< zy+bN=z5$KF*V3~{u|K+)ud_%dZh0tdAWNyqz17U=_+`=dKUhGsk_JEe<>4vFd0*b+OjWlDs zbB40bE^(bi2s#Bu!?u9MsH`WS1wuFGpirwr(#fB)t)aGZ@{c%g#FB<(eHVv^;b$3} zrwulvNMI37^}1OQ#iNGql3y{100<5`q^#5-%-9dr{E*kEN;?qWYE}Dg#3ygRA-}M< znq5H00T}zG`5QI@AtbBco)aDQ+ol|htjw}$_DQdmkY!ITL|{rH^)1uN2eS4c7KIJz znAF|AUU)o4+Y)okMejf=)rWF*&roU){>glu6Yj=ehC&xyFH?J(i&J<+XqloD9#5dX z!E>?v6~cqP3LU{|**iK#tWKXBprUf!5>=H&-`&j|#s)IA4?T2=n&FmgGgd-)T62l4 z;^2#wfIwD+^7gN?#$=56@(7nT{4+roY`E`FWK{~{#n%iiwU*`V&X3%M+vvWFGOGyw zw6ffhD7w2eG}UU=yn|(<6vDmcJ)ADZh$#0c@d@t~5tz#`*Nkr3!U7@T>QFk|b+%&G zY-HRlW;6(0Q8kP8IGy!K97TDvicR&}H)b^E zqea<5d3~VS{esy?eJ|t`y)W~^Kf)M=UwlK0{(Vi{xow8{C24axgzILIZf$Ums`Bd& z0^HMV35KQJ<2IL*eIW^Vp}{F){dSRFNw#`2n7@Mgp#&YyEsnK!G=A+durS!hwHKmi;^LW76sS^=6~WEz4B1>yosEa=7w{L3o~F&NxTB&!j|xxN0^uW4mDIC4j^GSPRy~3V)QH> zDC?4vwQ`2iXiuCXx{DWwF=-$#;A=PpU1s2HeyM(iNr@ZxI%ZL8p7AozqWVq3nKXb@ ztlkHP$rK0s$3DUu5=*k*zoi7tk__$&yj|rDYPna&mCs~mKtqo>F4vJjl^NGr%j(gD zF^Hyo*$?zT2EqRVL$PgcOj>9_Ku#?F7Y4!qfT92WdHcUlTemlqce3dF_rnyaN78iY zwh&a%4+;1(ba3KdF%qbTsDb_yHi=}KtCu5N!Gp3jU7j@hq|J`#~+5 z;b@KQ@8H|(9j5B}?3EoJpPur{sVivKj(;};sxT@l6DIBUm zWr5XXmX0Zmv(^BH!m6BX?3wVN_!ZOGCkubfoggiRjI?pI`MfUaYeI6*F7H-2om9rM zK4f25B6iS88E5`EDZu1PPb?a78%-PW?;Y1!JiPnc@W)ffrN1hYTs^51B+lY}yb!&# z$``TOoJhIrYn&AbD0Bk1RabWE)0sbAKV^aY6L(~*K6le-4!!u;6W45xt@bm72ouQm zq-pXckThATxEjqqTM!}6T=L>lCR#2tb9yHO=ZUFo(su+`^8|%EwYit~Z;*-EYjZWPe?{OJW>+bi=Rou8QC2rCX~t`VD!X7YmAkqyN%)^ z0$VntwSDFwi@zE+2=A#e39+;sF|x=)#zFQemx#7r+_*b)-@8=!T^w^**HiKiH_}3AB>~qwVp9EL`3E^WB+nN}JfrL-WZP6Ii1TJ*;Q*Ne zxO~nmq5t?_%T3+dPPKvh0g9PZs0_^M;X{|}(qUb{Y0uL6v&BMSty;Bahh0xnY`!(r zGzl^zF?+PAIIeYOk<1{s;bL{46FQw#X-M)QE76aviB!EHrt;e4l7bxz+ayIk6=PBPcL|Wkb z=|S0b6UxPwMk6Lq297qMdYXF%vched z$6K}92ztrPs8ArrJum1B%NTQm^QW-X*iy}PiB8l+cg56=D6=qzQCi!zqkRY;p4bkW z+oCbykEBep1hB7Lptn#k|MBc!vUK9U!`npy1T5dUXQ6%Z)EUaAkOg`0?~G(V+@v6o zK8j;Q&fK;3K0GvT%*51BT+^A}n!Z$~u-Ks|Xr>4*+Ng1{l7u_v%*hlg7;C;3V<4(v zb4%80zGU1v1NAbzhKS~4yBSjl6nG4y)`6RRHu-3A3$H%EJnl@U6Lfk6Ru(vEWn1o+H{y6m>Q3#=+syWy&1~FrTlk z^OCP?qQx5b@O)ubwCawrb({OQIoAOE5^;@k^#>);Qy6{71(!~%>XUSv%Rzjn+k)8X zVA{20oix_Mj?!3M)48R7UTN!M4^Q$Hf1&JpVMb_?ZNxS;ZM!bc5p%B2@8Sefk4kn% zgM8@=2?Rj#Z7MhNl?e$XFpvH%=gnwYusuZWmbIM^Lh%FPU;9D$GxNoO<%VRgY15=6 zg{wV9Zr!MKJ8Ew0@VNso;TOY$>Kkvfoad%LKj@S8#oaRIkIidlzqumsnm-E2&*;24 zGD!0**!a%XE?8|{+jFO8)2AnWf&SXnus$9VyDHvJ3vi~pQ7Dk*iob;Pn2o;S)0*4d zHLn-g-bPa~7%C~X)Xt05;rsmpsMbgxIM_S|({`|MzbbIPaIp(WrW#5;e>5PYTc03+ z-;yg=0G6t#2?4L?b^FDw@JkyKFNTi1h4Pr)QGKv>N{1|P#@uB9lj!Kc+>fAbr@5Uo z9~N9xYb7=i*t2UfFBOfa_AbpBc5A3=cGwG1C6pIv(I6QwqiR(|D7^}q8ZASn3Q^E(G{>mm2Ks$a( z>v5mLy-$P-XeKJ&Qt2>tSP9e9{|LR?nQN6w>J=XUg9lopKc{yv4{k&3Cb?jR!anJe zX)cj7_i4Ec)Mj&37Voy!p`NH!8q`*^oZKJKgd82}Z1b%HPzf4$qWfVD_!|XO8ErbZAQ{5CLV3Bk^T}Vf0~<2+gtf4xT2XEoQeSHs>wCGA|17$F zVCzRCS0Wa*oudk2whjCb%%V^UCEKy*=6+lD<1a#PB?*?aheQvilPts4;zc=nm9?f_ zvV^6oRb};^!yxrWWfs2mTii^;F9J(}Xx&>Ia*=1W@wryKowr$(C@sw@bwz_QF_xg(-7D%%?_jUr(fea!6(_*E@U{iOO-b4C?ms+qV{P$g9@6`ZGRkXCQq zf_W8)g%G;j0X(zeGTSkzbI7*doz;=Ny1fr2#qs{mbdQ4Y=rZy*X1Pnz{d6zL#iMyA z8Mbh9ze14n*0F6wFV~QI>zNL$DuZT=yR4p^q?|!d(uIq$S)3z^?ke==QaNOiNSegF zVi`fal5h%QpXz{eXQo1Km3pogXK$n@e`$Xl79Oac(OM38k&b>yFQ-D$z(WMC#Bn)X zXU@i`AO`!WaX58Xg<`2j7c#h2w9wxon25-yX&5kM!zK(>6|1H;IW`o|5GuOR+Q!*h zwfvfiYB%t6fkg`icpQT=M(iCdh@FfvNEVgwHS_dF_$f{W<5I|O`m zs`z%NQPj}NBm2i&f7x~ZgYRkdTL#*8chW~TqqPxj_RL-yY7Orv5xg46=<_Cw^2s*c zI}42=@5@044esz{f-ZFXvqMKb<(o%$aB1&WJ5&Q!I;E(<+oNt=Z*xAmOVMa6hMLD0 z=eFVx1QT5t)X5grzPs14=-k-zJ4C7$e5ySsLvD;5&pY%O*K3<7x{I9dgFP>+Sm7d? ze*!kqVn4V${B6ZGx;B6NMr>~8cikOcmnxO3hB)S=9}4!swimRfsAt}hfSQSg_?LB3Ks zGEi!yW+|6Hr@Y=kyzs|^{F9}JhRpTa{rkR!D_TQX>e-Qra-Rlc3!;el^S@db9;>!- z1K_c!1Uq;=ZeZ;000`f$74W9d&etfLEI&ckkOotYz!GU~U9u_P9bBp|-oSi!C!hMa zRJ*pfb^?p)mZLXJ-&(RQCDCuOV>Jw!1S_T!PIUN ztG?2>!Eku^Jxkr?<5Z<{%j3kd`E@Z51^u{O;&SDws+E1v*=L5CfmyjSupWeEsyNMz zvGj1-v<^@@LTGt~3*?N8v>s}dZ)`|-ZI0={oVbERB6A9nbjPP?+Ec1%UC;sGye>Uk zo@nL?3+q%3)AlZi{ixB`s$sm4FXdZ0M`+?O$*~_`Qlrt|9AKm`?|+`=@DbIFM(p5R zDnAgM5H5$dbvmIOv@lh9^*c|2upP|)>ieH^^`S`P7D8zv@~LH}+qoOoz{*_i*((SN z)JKX!Lqsj4PY?t&iC_s?F^s)pDCUyJB9{r~wCtc;w0re?)byFddY+tx=W?rSWz zDdo&1pPZx-DWg`(ZaTz+fBGwD|M9H_7W>pwR#M4~tBXNMJ1e`sv*&{da~V6cC#xKd zUwpd9Fh0!RyFGt}3ieK2N1GEkKli^qougXG;3mei^)z|0f8BUsWtNzv!Tw!)Yq5CF zQNB^mt1vXNjXfMei6S3<+ynyXmPnTi@_mGEmOg$4aK z)wc=36ob-bGJD(ibh&D1D+5zBiK;)sK>{oeGhZ1W&4Sa72UtLaCm*&)(GSyw`4y}1?Dfd1G zyLmLrgkB96Eg$-rQseKY2Hs%pQ+lV!9tc4$>MZwA<370ZQ`*PZv2VQcr=*NB9z#Ku z#)Z}mTS_NigLX?l9+bN!@IwRgBaK3l-p$zQg~RvAkvoDGh)K;NDlHVr^>K%cv9fR% zxt?3N`Y?{}4Y~7i8<_$eq8iaE9-NpGH~N0(p3EGOsS7U9br;>XWU2g6=U+UPQG9dg zr6oy;8%#Uf;3O03)4>thTF>J2+n!fK%`S>>=o<}y)ZKDD>KVeI=hGp4==t2SzVQ=5 zgdgWA?41Jvf-Q^=er;#V+D%0a?pC<`hpRNvh=z-zi1#tl55~`#El%|+xW+zgDOLU2 z;;v-{yW8(d|9%3FDHMHI9?Xc2Qj2YBB>k*5=PG6IN!9UvoI5;ba?4DBmn;NU~)qhZlLH;At{+ z>H7I~bLy{;;k`oo*7l8?dT{e`QjttnE9_=ey`oulR1+p;o9M~3%6ADD{S1Yp;vkbq zVXi}<(vj`Pe0my2D~{VmInFVnZyLy=rkZ+TxzVM_@x_Nq@Ogv|H)_wR{cOiF++#wb zFp_z#uC6kUSt{}KDdkwhj)R{c$@2hx$%LW=xd}#vb*)xAr;^^K8>bh^zQ0@2oC;Eh z9Gu&NL5E<6#K#re`}d#OC32kOw=Rxt-BlbE>^oG8G9PFP*U%Vmmb}0+yUEIDmP6_p zu>xhV{)wNFk=X;Ew(N9<3Z<8$D!`5%*#+d|r}2irprr9ZtXMiY=F^RTE6!YAbXtj> zCp^-Bf@Ja}TO@a|omfr?8s4a>qkCr!Vpc~Pu6Ys^{;8T=zqjYyBtlGbQmF`@_~U)@ zHB@FTPnLfVm6JARUF;ArhYoqB>T$Qk*MWRU@>ge*-4e(f9^ZwkslOTeOe1Ln$NK!4 zHLr(DF25OD2o8541@rfQ%yDzFWcP?qv$~Zn#3L`B2r^zcLP5mJ?Br}3!{@n=n@FI^S?O~+ZL1&Ho$v5;b-Oxm~<6r1zU z>cJDWUbQ1Dmr07^I(pfDMPy{u9`rc@oDd%Cmc5=b_T;7H4$)KroL- zQ1aAIc(~+VU6MtvW64v!;$``LnBrH$W>07;RM7KM0L}Sny*4cbX@ilc0s#=6=4>*y z6F-{5xh!=13=yv*FI+!HBGy$hyci#!bLFz^8LDo9r*_V0@*6vK(D`0CH}>y_8rz+M zD-hdpa#gWmtsMAD^|pSZtRu0~f`rw)0r%{MEJ$p_d6X)pCpJ;hv-S=qwzdmeLJ zQ8^PuITOByDp;9=kaQW<7a~&}7Ki|4J?nIz2oR9Q9O-+fNSd^&`C!hYBXWl}Azg=S zh97_0xd0cr-rBw!b$|vRx{jy45lq!DIz3RF1o2jew-IcE@rx<$sB?{ftJ1GTVO@H* zf`^gi`O=xl;`f6LwBtDFkFt#Nm_-6~9#QM9P|CZ>t&SGiPr4M5X!0 zS(qM=qyB3Ax@}vnP{w#`>c5NL2lk!WyZDCimg!C6tlNi#jrD8oGTCo2to5D_hR7*Sl;mVd-;W zqHrR`Dw6s^dIc@{B>Av*e6qHTR=;I|YKK|uwTFxGs;S&^+)s=ip7MMEvr;&%;7DAI z^yfSo(8;Yoq-Noq#;Xk_Skm5)G%X1Dr{ucI=Xk?n;Z)xdz|y$9YnRT#srVZpl{A1b z?;@2#ldr7Q>7WqNS-!2WeXP_7OaK#u<^x?iv}_(!oXQ?l0a5HFh=}dz&F7b-M$i{~ z8t&v|1XbHO7KTl;8dar{BgHY5(+(1ME=>=PDYX^ZL?}xv=_*eaVCn)7J|7dN9 zIp(WeI^ThBW)G^okZ*u|nC+7i59`>^zD00AAy#rhJ*fhh3CbkH9c~<$OkNwVkR~bX z#>?fI+N^mII2l%_1*0-Idh-66d8r!prd$w{o1!W#*>t#7kt(+^-e^{=zROt|Bc9bD zUZfgT?|>Q1_*b&+yinAHc+A2ffs~wgF4TPxOMIpUlTBYDd*IA8OP*Yzs58^iT)B!Q zU|xzVM{PdCGh-2cdC#P#G(#mUWmp+~ISH~7TVl8=cE#wtcAg*1r*Y1+EDND%(Bhdo z6V-5BW*UWob&Zx2{m=O4lJ*#<5uy#Yqa{ubmZlEz z9iPTgEG$Dx*+i8m*_!A7$yON` z%7WLR_q6lBXpuY(p!w((4Lfxfv+&aD(`u{>nxc7~k8C?q%R|IUMy?7|F>0o)OR`!V zB2Z?hxq(}oL-;_pRxV$oT{n6{wygo3$_JaLFYC~jV?MtkiEK74>hKe>8Qc~@16?a- z#I*^p)jX{`kzb}e?#3~=G-Mlc$cxD@OZf-P=ia?xIhW*L|3aNkntsae-M!weUa6G7 z>+J}EpMThCn-{$M0zxo`cx6D(YS9;NGTafr!mkBsOlXuvaVS)p z8jYl(ZVUk=K1t_uKXdY-wy z@(go6k7UjD`5&CB^# zGwYjIzARXx4fBTkgkCPp4kKRNqFcqzoOP~ZCf5ArRCo5e&>+Cf_v(6rZUMaUYpYENw=6?-}_O=Y2D6SooNI?I#YC2`J?&-$}wbz(k$r zqHd@B3I!jTmcG!jW&OZ2SMx<`7kNBEsej0Y{%YU;wHrL?1m<14f} z(|2a}fphr@wgt=qxJZ9yIzHsNrgEr~S5Wllgc-r&`WD9bX3YNqjg1-zB zUw+#9<-MdmsWC1fp6Dn972ER`yvkQb0x8P#} zr2Krl^tVdSJz6?u(a^1QKpF>WuUL{omqF%dkV zCnyaJD&8|dK+P~{aoGu2q;ZK)V_s=oRF*yr|#Evw%xd&4gjWN0KQ zy}u$vVCqkaLs;}K5V=~l(?}|7qyjJ^)S;@ z?_gPSDwAJ%oUWcC?{uFHs7IEe?GEd|KaEB z?8edg^&BWy`C4){FYO^p!FPb_l=ICEOI2@FdbE83CMu9$jT=Hivqa$ot>m@j7Lc<~IRZ3Fhr zx+8G5p!1bSWB%=tM_=II^2#H zyu4k=re-|I$&M)UO~GPl*NPx0X2)8@H zsp`xw0`qgi+yZgt1;cpx^6X*^Vl8t`P-|rfK_g8HQpWL?SepYB0sD8_9Ohi)gAgh3 z;pG!CDrEpH8834KS0PM!LM9oXKU4h7xT#`cCko2O8xF^tIo!VOMvxRn9aR_|5-xZf z0Uhg%Wks!q2`t6 zjHX>A9(BqckUwn<6(Q-bUXO;uTQx;dpBFWv_`^Ea#Zl?D+1*v7CUzNv!b;$_!{CI9 zuZoT^=spvm_O#%H50F^CMaa(0BHxs!+)fRB(`xp5NdmjW;C6jFa`^K*-}0K0_(j-m8H_;*29>=7VXk%_as9+< zZ~fxoz2)p~(;gAOVb=RrACdi}^B&xwly(*oKT#>}`Eo}-_GW!U5kErWP{Y>)J#qw= zFz>n)NpH`_9A=Osyld=l$%r#*(dByN;C8i#1hz>a?xYQW`_YEprWsQDQH0*T7=r!K ziba7jijhXu4^WLn85jnp(~(DdGfHL?l1Fhl#G9j76l&tqvPGQj!!ZhPQ8Nws5G(VL zA|NA1Wf;bB&>muXU>}`MXMw3~K1?O0%SK%Sv@NPUln`hRG4EKE5Bm;=fj12kV0+U@ zH$-!1co#%rt5M&49-|5c(6D>QxPc*Fq~ox9DPynqF^3m&6w#>!Qkx?uu0}rk3jJ&Z zWIkXz@n&d#{q32s4fE5&`|I0fp^neS-tncr*|c&{K7tQ@!bt_^;0*bbX1qZX%9w~! z1ZTtqXLLB0AgnBQ9`2P(EiB5`#zFAOod^*T!Xkt#YBWTQ4O=6J?!f|^%_D?1CscPv zsVE}4Z_Xt~TSBcULnA`fKE!k%&U7DPG{!(}-dI-5JnYCNuQLC(uhF4YRg5!4-6305 zx)XVDir@h9mttiuXkWcayv@9}Y&p}u&A`V!>XK2SO-(W#A6hcqbrU5w@^uvvx^q~I z#K3`Pi4s~iDLFXCo^|vbjp9PM)3+p}bLgFp1<2;x71>&_x}@XDS8+?u*;ZhrTuXuA zB2$0q#^A=bUh$0qzEg79>T2KkXf;>py-7N|W^1DzDqRs(&;L`|EHgJ+cYl>moTz;jhPP?`aP5(5rD&8|%j9LcYuB*SnUE{7yIy zr{LC5(C{>m*46;(kXkOKFX~Lkb%7A~g(oOiHz;$3p^*~DU?y0_wSwo!fi*AA?1D9s zGAl3jHQLdfAYyY==g{A|dD!@ePq{5&=zAPEH^buo4{fQ-EVgYLUpsX^0{<>FmTu7N z8XSKUI*MGOPxND3!Mw`7X)++Y~mTQB%rizGRX7sE5;Y*It z8bN12wT;5oierG5DfgeW*QgD433E(4>L<7x*rCmH^6ywAyu5a8Z_4IxF$)9JtXD8a zjy(%0Tg*H&ZSGU=8M(ttQsIQitymRt4r%-JF}j{{IXT&FI+d%~T7v0S-?h|3YctFJ z#pLw!Ey37-l=!$irHzt`vQn1Jl$=oNZN76AE_IHB!eQ~pk-$`P{JD9Oa6|Q(MmcVP zgPLWb%`c{A!&Y1x#^IsMV)t%9R;Efl6Fv4JA!y;Vy*6bxPJR%HfVO#QLvhfu^0%8bzRp?R8<0jpy!N~l}_r0HeigX{pwiKzEo49hO2C86{c?VNdY z8sT=sP$Ol8!msiwqkTSXvxuK>p&&|5i1=3q&3&3Lh8S~xJc9_k+e)Axipsbfs%uQC zlA&~A1At}pXdwX|_U|W5%4RrF6!=vLZ#vlkU^{?F*@Hy-FsTEDNqFa}eVK3WD;~Pg zT^#01A|}@sp_WBaTO2)d=c$qdzYX%0bCkQkEhgf^-+hA)bsnMWeML{v%YxY@FHD8q zf_(>kqTJo`eaT#^++&Y@EP7PGL+RnhE>WIQ?xCnH`tPB*2>yq~eO6!Ht{J;1_GZS` z{-#)f=Cjxk-tHNQ%}U;U|Gh+e{F1(g$O2(pp>aprOmfs*szkMO=2X8jihK!ayT);1 z(B?SNgE1fHseO+WhQyHH#6jZ`iY8bM+C5eWM-)JtLl>VejhHb*ay4V!9bcC`$^@Fs z5z8jI#4#h5;wJd-I5&%OYCkwB@p@GD<>N8hA!EZ9U)Y8T1yNb0v8bdwpoqn&Rj1|| znAvUINPBnxKCP+s z?q(3wSD?}0JAs3T0ypgBk6`nAxJ|0_1&lM{e(hZxiDFiq`ggruln%|srD(2I9)B$O z+jg0#p@Z+HEjEd?B>A?y(Uj67ml!4XFonh_Kx5QwO!GS^`zWf6WT4*QeKhbJmGz5c z@DcEM5Uv1W{2O99i%ZF;LH3ENTj`6n>>EPG53-^ca+%*%`lV*DwaVm^aoN%v{^m}Yl2**HrdR<)aaeZ{lHdaAM+lt7| zvxEu)6@K8iQ)t4L_#ka&7XiL}<0*w!X?qhA4cQ%Y+aoM|ihpvVk_k^qzqZMkh`9j@ z9(7W=3fGx?hL9~MQ0Qr~@)04IG{XFrPB=K`!snbXOb*`h#rp1rYGy59ys`wvoSI28 zNwsu7)AtfEW_8%i7KYf&=?&B7F{m6`$(~l$Eru>PEos%T;lH%`eA)#+;?2Ey#kIDb zQy%Dmo7uA7`Su*^t_E=aJ#!g48uAa#q+HoXHlXVwVPXT2wIZyE=UZD%_);%1j+E`; zrZ|luOQ?$}MdE_b=1D0Znsdp-mVi6rqEA304U1@zP$-i0u#b&9H^{&eK+6sAxCPdq zi+m*iHj)Q6x>=yu@J*n&QOoLY8VJ=Eh$4(soM>IJ_F{guG9r5K>ZGyrqqwQ}_~7bl zLfHfS*_<(VYg69_xFH?V`+y3T+g5hz?+C>su3>-f$}PV_J?i@0u;$1IbGyyC2t24n z*?t?r>Y8W90G2~^;kj~Exz#%lo%RgK&G_ZytDngm4h(bUMqPOLSG*V%Pewhz6PTYT zFsD8lLhi&xI74yy7jZYkl<)co#;HO}M}9=4hZN{0;aL#sGIq@{;FFmxg0n@F4h`bL zS=44Av`-Wxc!YEd0Wj@A5Kg4a#MqulWUJ%2W3G%jLFw)VQ$ zv~i{C#*E#O<}WR@C5T8Dd}7ugB5J`g)+jP}vnnv-+Y(MklVb?UI3Fxk=y`#O>P_uF zWic}C^{wv*Bj3b7mfSnammgRbyB}FZtO6snJGS%x*bJYsl9;_5xm9Kap=F%*Tq1RW zEA<8U_DF=pQBmDl3D~3@h=!fz2_B`@3?G<%p|5g@kBu3MmX(?idZJGHX*f>x5>8HL zmh$y_JNz*}=N=D2{%~_S@2$TRqJCBMMdq~T{>>q&7o_!h)Uw&#IApVYS(4DwNzTw%O`#&N7rpGy2(B-1!jxNzBe( zeD-1nHB1mlcbe<+xaql}zuEric{}mj`GNFD`X-;lW5x)~czdu}4U!Jch@@z!z(Slc zJ~>HJ`<*hSky(?`k9oM@uY;}fB%&lQvV{&Vv}-N-s~{uVq=J#Aw^?U5qHv6&wmiy` z@v_N6Lu*P3+tZ${gq*3LQM$Rw7a?w)1Y8xQ-^W%}flk4q7p4i@)bKC3jpAbUZnlyB zTyx{<($ZU<4kud$-LELEyv%6@hnT**g%@fLLWM?Xf&2>Qa*Ar(0Of!&+kk0t`pgNG z<|2#0ti|WGFk$z;C>S0}1%qiaW)R-az$>2xJP?W(vp9k30=wsQag^|6e4Nb!DQ$^- zgA+0B@Xmdz&5rY?43?>47vWuVHDjq#Xa3pCt!Ms3ptO3>*m?@HG+Jz?C$Jh}FJ}+F zVK)`V$eI8$B^(oGILN7X-v~DFWx;i)ZUcd1*7snq;YtEe!0>5~I|AeT7Rk8s^J@uJ zSW33zyP0%;cfZTXN>3gE2Z);~iCJ&woTVJVo(E?B5l2?$gWK~!^t4ypO_+AvOIx5K z5msm7gX1P90qKc0wMhoEBKy6}vL}p|W>Oe;pS)Hk)*Reuv$g)rxMJRadh`((h`Dy@ zJ|OHll6VUu4<1SC;ENgOilVHi(;qcs8#clk`NGC|mTrzogMQgNOL^tMAgtPsJ_8Eq+{(*>|Z~p*O?CViP zH3HV&II1#jNXyx5m3$V*9#cB>vpmyKDNuuirF18|s1 zj~-cRA+6<|gQuUu0CE)&*vE}qH{VcIJUzi&*Y!IA{d8+mpEZfmP|~aD!f7lcBZ1c< z!lsM`pNL9C47li1V8@&T7d}odtX701Z6uVwzF6)G>&3=8SgtEt-s_giTWt5y2RAct zwqB`rvS8BUyE#Y!E&Ok;>orDgEauDA@iw$({2n8ZejkmVH-4>DWRVH2ru~Q7O(1K% zurIX!^FUB7)DouoU=`(L&sgt>OJ@+5tgp`=2;J`(96s+si?AJIi(v7Qun`JCQ2`Nx z7(^*U@aTv*gY5gQ&B>o$lG|4~RpHc@IlBs2nVuV^KS?z)Dexw>+!H;sll`h}|EQwe zYXW%qu!r+)`=zhB0z&#}>m65%v>6Aicihw?)mtN`5?M~)?{DCp0ePO=J;2o3C*62f z7f|ikPIlJ_x>HVYvK;!_Kod_6GKX^Jy0!$8AVOQks)wPE0|KeNA%9nzZF>~Yk8T@< z*HPji#f594u-dSabt)8Hh*cqFc|wS_ zQcmOrm%)bWf+{c)U9q4;ea_&|WRctqq#56dI02y`$QWuoYbFFIBhsjhfdK)~3eWim&CD zxeVc$-gkN~yFww3!mr5zlDr^JohS9KurVgl*RJFsK)WHhQS0HR2*O`bUzchrNSPd$V|lnY3YshUJazvR&>S4J;9?zlDA0mt3o2c7>-ZnmfMTC#SPQQ>a(ZBut zBO8juFRMJj#oxSgH5W(lmuH$zjisCm0z6j2PwW%vUTKBAFNRHmCDlI)41rb|!$p~H z$aIX~Me4*NzxYOO?PS8K9rv_!kf)8CmD{LG$A!!!$YiI&(~wbXiRtwyKD?3wys|E( zO)pf*cQkQFHr*i~K@j&(UcoEo2|aJ6yQDv`MR9ER)u)We&pg$fRO0lR6KICDU*UZx zvtJ_EZ@E8ni7HTQ_x`bEmF6*pF3FzR7h$Zp{ix>Yg(tRjOQ2yw4d4nQsT%R}L}{@* z{do`QY9{h6YtQevz^k!L@ARz6^R9?pyuLN7`-FLZ&FVBii2t4UW!`H7I3z$olFI)t z3`oJ+z{AAxzvI4H2ijX@`HAnO$9UVA$+{mXhV|sz#admb;(Txp?@cfdX}$ zOo{b_w_&gwDvcnOF8S*h3@I;w73>06eGLj6TOIr_4giG#~*sS2kSj>2d^Z<#K=l5mPVnX-sodZ2f}wrm*fFkS%}KEZ9FwrX+3Q zR3vk12hd?LgiE`TR#3xOoiJ76=4RTLPhNx48OMhzaEHBj2ph5G7}FY2GdAaqN#u)Y zkR|ghrA_ux>|hMP?CS&%paYNc(m58WzK#iLc&f!r^5-dS1K12g-Z;@gahU3Zm;hRO z9hQ?U^?rWKV@)w=t~1c``O19JOV+{D7&3sPDT(wEg6B}~s_&?5maJorGXG;Su5nT3 zX)yq*Qc}`PaU(9NIiK=%#N(0$aFUVP+#&|DyM>&khq8n!via?6p)_@|D1w$mRK`dN zwgiQHDIToa{_mW`GEx^zk{n^tjHc*RX85=!I)vA(E~rFR&M$7bs>Y*p`7OdL!MI@5 zDP^G&B{viMh+>kVXZ^>I%C!v z@oT{QON*H@6(nHmRj8+Bi=O0wB}--5IjHhoofMGPeIFb11;MJACaNU1IgjvDV6Ue1 z1brmvOQt-4CdFKMxp0OA02WzVBNUuaMwAns@sr`%L#UwNI939wpOC_)00PnjN-;67 z$t=k6IPg2RS{ZdnaDXX^YA%bNt30F&Ye_~sV=L%PokCyfg$)~7?p#@?_?@{t1;oD| zL*Cp&94B=`g$)b`f$J3xsyioe!IuaX>O9(hNI=qu#Wviu$x5`dOq9rq`^tXKbYq+a zJ4h9>+g~t_VWCeF@Ax5VYP=#kr+$2dd|zcNA9IlmWt0J&SDIWF1OlDab7MfMJB?9k?$$hbVT^16L2=CKQ+|>9(UZ!D(2`QOMLL9zj2@-|u~w#>ghjux;*M?ERuTy4moMC(PMb{rhNmagiDwGPniVyr0{6@Y2kQLA4WKXOdgII0ak9D z%syOG3cD;6u@d5{kbxQDNsiyr!Ob-=5<+R*ztmHv4dGngk#H`Wb_8{ipMUL%g#^`! zqlTbhU`_5qo?48DbQF{YJSJDQ%JIr*LRsG+tXYd6|V0pUPCCS^oc75l_} z=H=W9ws0fd*K?=>AV^=TRpDw_1!^-9ozpPzr%fQ7Lp&4Dc!cPVI81t#0kk4d_2HNC z0HqNm&2?^d#3B0%@lO{MEOMvk|2XCN*6eesm|P02%sU#pet~zVN!~0lAQSt#a5Ktl zygMz_p0PLlwM#=+RJe&{7#eo8wzd9fN~m0>_?8kWC8LK(VpRK#zPc-Pv}s1FmpYp z(Rt<)%U0ftBi_u_+L;|3wA!|1u@`BW^GJ9G=4V`J?hCWMZXAc@d?PRvP(OroZ+(ZA z@auhu)K)wL!vPDsUAQ7;6UULg2{I^te09(E8N7v1+@%d?dM2I%$$4_)Z#5dw>KAPk z%UQ=iNp85v95e*>?mqdmMw?T%#ehHvaWZ?xI`9ltet1ABlMR(P{8=^xK{ECqE>>tm zF`$KUwiUeW8CQ<>=yF{#P-~1)&+!(wPq{_>pFu51WjlP42wN9)@hc)zX z!{8#soyahS%Ye=2TeG;1H-6f=_V-0E$lxX{kjy+jc#V*$vrPM)A|qOW;3r&V6I~pA z%k}{(NFv3@daDJo%$X42oO`g_p4d0W!3L`;f&1+KmAFbLHEgV8izUuIC}no9nV~ZJ zkTJtIF6gZ1?{5P%6AIr98dD&rLAs2*k9JQC$#6JKju^kSv^AZZS{TTx+`A0-m3Ggx zwC0U)&-{+r-2B!Bzd%!T^fhw$M(>c_Uvk6EG+{HQ9u9mR!oF z&_=uLs?aOfz`M{Vg|yMJP?0!2OcQm!mlHIxWhOP&8L4hYxJDg%yFfG^=u#61Wk7Rmo;uLt9-*qotV1 zbesf7p&U|@=&47d8aU@-oyOO$wqQ~!{WDquuZZTPDCtgCdjjv6f|Kxp0|e*FXIFM} zC8KFss4S}Aa|cdX?r67&N`KG35>TA>>l-1ej5&pZsxqujHKhve1a|#kce+b-|JV7I z0n8|NmyMpVjX|{3?wKhq)>iNe+$DbMt%R8!xn0WAUrlW-9j%@g6>$F~pHk*lCS8`~ zUj*J%6c@3;5x_PLFhCS5-31FPE%sqa!exZ+G$aYxmWFv5XiJNfD`DAP!VVhS5k1w& z+Qv`nl``oG3)>hfjwDW=CRf|N#hTffy0pU*0Mg3Dc=gsSd*2nwfPXF0vvQN&cVR4I ztU_Wa-tX8AD26fq`cK%|2vYl}o-``EI=s*|FN3z~ASUC5=2kGFy=5`TPiK+rTxwpM z+l7la2(+tDGG_hiJFkL<`;i`(i_Cwu1#@qbo#2Ik(cUucBjy~tex5yl3oVKrsa5V* z8gK2u8zZ3{#U%4|^V@S?7+g08GxD8rFG2$j1>`kocn_h_q?>-+N0c2asU8%oM=vdh zA72mtAcFXs3p*Cq@#?^)?0c*uIp1)CR9$@F^B~61m5E39z)`3=|DdfTL9Um;7+a{OA{}ez4^V>_ zH3j(>IlDH|tE=nVS9^V_t8G(fnT7{uBn7opCEw<5zd}@susO50x~9q2{skjDK;c6N zj#hN2O~ZBSLP4?Wz3j=^HQklLlN*5IFxJ}XxKrKh2%ojsrw8%kMxVW;E2&HB1hX-} zF>RNRn^&;c@r1;ZBvn6D5<5mfyIDWcjiT2Q`;iq$b_`0rmGjNZxfd?}MqBVi9p^qa z)SqFOA+2YO#px87_QhUCKT%s218M9F(F3GJe|XuiOk(j@Mr$j0cR+rH@fH=#4R&S7 zmXxk$kKs{1Bo&J3{2Ktlm!d&cbLo%cjwGjKB+*l9L7z2<`WI(!RPJY#vAD*5*7>J& z@urr9T5o%s(8%b0n!Z}v+Flh+xkBA@~?ii+GYwL*T7~IWLQmwm-61k zcTxbof2rSnFK$!(k4?Atlk{?#ZbY_7qdSRwVQ6!Yu*=U;T#3Hl0E-JHuk$xryI5hv zo|OonZS@2ARE&iGE#o&&692)$F*nVdN{=6{JWsvJ^;5ZqPjqHy!he&Dm67-#wv~;#%+eRJ+i2yIBp=oPF)@H@e z!LRWiX)m$64ydAr8u7a+=eb5~eC~+7~WPB{rUbctZZo|JNp35as)=e$A#WtaX8# zmZU-%oRLm<%pnp0^cW+zAcAQ&F!{j+w@~Md^nw=qkn`p!-|k0AQ9{|IO*>WySFhm7 z&SDt!t$wHTN~uWDaxo1 zMv6-I+Y~o3gYMp~FhV!^sZsCqJ5+9u?s70oXMkItEj&loXf$PR&+bb4jTEeD*jo;7L(&J7+Lo6z9i zE~4&|RhyW zq=r7IpC$^Y%fm_3esCrvJQmnO-=~Rh2UKka61GQF)x)kAqa|aqP(uJw_9kkCiNaZy zlVqZ?{$wv=Hg)j4At=dw67d2^hSw?t#$K!{*QlHPsDCH2Iip8-5XmOJlDq3Fp4q`* z=!5<`?k4)Z1SYs&u{wtQK}1Ww=ij3hU5XGZaafDUFQ5elCoFg8 zqnaPJDr-o^exJP$-?)P~T{OO;`9n5N`Jmuu3v}Y6+(icIgA=c)r3%tlMnzWv&Z+sx zAM=iSw+J_ZF_wbBT7kVp9*kyD4`a#A}d)P)-Sjplh|zoC1{9r4#NJX=bJ$CL;dl`KJ3JPBJFdY9M9#(gNeGJ z7>PG8e@%OB4dU77&nJPmblRfjn5H}IFC?EoNQf%3vzw;wB{y%%2xCddK)~SpdLP;! z3*}I1Kb%U!62j~YI$%1?7QLrbw-`U(pFpvn4=m`h@#jN3w5o#UNDB4bE@yM7D#+IA zctbA82}GpV`sUfkI%}pq2d8)}YK>tVefpywD(#Cr;sGLafN7C4MRo`eqd4Wwq`Q zK=R)8XAtiq-_g35B?6IZhZOLP4afF#xF2EBM!H;3=VA(znH9wpw$S8p%m-cOnzU+$ z=9RCeXjWThY)m{+-t5#=C}We+f(vcv5H$NPyBW^MUoqFL^cy;* z@e8u z*?!mX2{srRJ-w@<+XXf;u!?zUoRHSNnxbrbM4MealcL3vhmspEIt1!hX-oINzfm<* zbsO$i&&v80ocn#TgdGB?N@H{-ar=W&k<>;m&dD!d_b!zVo`Lf+*d|DvXr|`2gh|kV zx=&>5EKZMb@`U{Vk@k+!l|Ws#XezdC+cqk;ZQHhO+c~jq+o;&8n3YtV+) zjQXt$YX?^eZ`YK%R4%L>EMYdG$(G>~KUz}H5U#B3J9A6fdICk7Ez&L?)puvIHXiQe zX>PODHHOd5UX_xmcJzcOm4vIImOd(&a^!AG@5Oocxbzkbx~RV*-FGZDvAZYV;o8zS zci4zqB-300*}q;iV%_#xIv^vLtz`Qn19FtL197G7*ffgb~V$yUI2{ zqE&FrjV*rBFb~I?&Aw=~5z=+C-x-O0>a!t*F`wF;X;%!xWLCVWOQ+J13VllCmErQh znU7y6UMN&fQ8~w#BJEu`=+c?{cfol6FJot7uatf;@z?^|1E(+E-yG2&ui(DPi!E@I z;PYT6%{B>DHn*UBmg{>^E;Ic-7zI&f)Uw;^kMjfHy(G&Zmx^_JS0%!>XuZ=yb@&B$ zC~jZf1omBUxIQwrcNP_wPU#zfK2sWtuHP%(lJkR5;kM~bp}^nyU2e;asU7Jrlg`P} zx46DsTbwfw5lg;JbS6&lFO7EfMhp=W=geksZl-rmx zNG*(}Wfv&{xswFl^-AgWbGTeDL;967-J(o!NnF6vC{U%^_S zhzMvC%o0Czp%l+;Cs2)Np$J62FMtQ*&P+qQ3#k*lvL`LC?4p=`$lcxVIfsc z9ROXpR{ZHC%*lPV*=;w9vx5>~mVX|7$MPpuc+Oazx7@h4B*y2uxF_u7qEbt#x=y6K z>?w3Na!*D_EYfC|MaC>Ln!n0afZzOOP`)P}ftw+PQsg*@GVH`KufRcffS3G3(+CBv zk*u^&;WIwutctrB-?tqiyidJ4G32j{-|lvaynx946ZC$b_QFy;qX9qpddYP3i<0^i zaR%``6Kfp1QSUMIk7J#(#WINPai2yvCo$|Z3<{YoE%{*S!)vC^8~cFiLkWjl^5}!D z{sQn7%l-M0^V{l}wJ#2MfJc$gV;dCMmPCttVKQf5sgdSnLS=SHr#{h~!fT$*rzRA- zYv0LfbZGw7^|l~b)f3`*s-ve{n|ti4$EQu4tm)opcuQP-A5f14E<38QR&vV!! zPW4RKYo_t+PFA=J-U&qSaq;)9j`VFxXR_SgJ-ty*b^j^rF-dp-L`HZ%gLuBlZPptc zwfGe0_%cVR2|JDlJ_7^IuxIHr+4K_4Qs}R{w`?144<{GTbF9NGRkTaTr23%~(+b3E zmT0pWw{S+zjvzoDta8xAJnDoUd2yjIjv(>5X9oKLd3hvTenCh3MZucxCk1nAF8G|U zOm^!-@&Wwg9{=_w-32Y^#_iP>)t6(D*fC0}Bi%`~6|;EjHLm>rrhmIZ>ZMI&;RzhZ z_e7~Uv==6!&iIsPnt5mw{42vB*&dQ8o+u+nax-*2mvQ`~sdJ=4*XTe`YBN4w*#ISQ z5!Qj$448DOBo}(=jxgYkp#uv7(Ejvu2Pefc9X356;VWz_8fhx zbHu3`5fITQ=e3C{S{*={``~2s+8?Aw)E6;H?`ZdJ@Ol3Ljo9#ZDmGoq+(9#B+LoYa z!|E(WVH$&eaz(Of;W$9FfGGJexaOe1N+N0ygI-RxlxlgLq?0b}m$oiqbA?{BZS>YT zK6H4~iCZixv9sa+4wt@ttgzDtQXB>U1B9e523=o4-)w+<`yjgE(vip148M$dQsL|n zPgf?nSty6hS7nXVlkhcVGH#O8nT|`TC5a1SU5s+o?~Iezs=hR9U1fiZh?lS<>n{PxK> zB4X~$DyjQXxif#sf}ku|@s!ZU?UlrQp5oFVmgPkTe^lCobM+R|ZZC0bvN)l>@dX@z zh~r+PH+k%jFRzg3vg}@mURI3}6$q1B#lh69DXY9r7;D;uw8IUwBU#Bu2tJxRQLHK9 z?N`xavaXm2>Fe!8OmCu3Q@fT9h;HohMfGX+czUGui0k>6ol|1ej7!#tp(Ee#mId6u zRS~~PBxiHuQ5f4mry5_%k#0`HnlP+>}49ra(9;dbts)MyW<)T%-#RNyhqS#@< zt>f8#iWaWo_E)6kci>8%BGdt|w6(Ei>SFW?16D*hvg#w>*X1kZ1zNKl)Teh$wJ+wE2e0dP~BE z>y+feqU-yX_&5_cCvc4{uqmBe;Rg52YU>A10<}((n(!=Ig6ssC5oeydK<-HjiVZ3k}T8q7lD&zsq=_-zi}-MwnY&mvE+HOI7 zXapwP?(}?khI8MkZ}wMT>{NiCD#kmZ9UHRU3g91!`F5aROhQkAYPFvNE?4-f51~~t zHtMtLf70MtTOsG~CCilCi=dq2M2r+iv>Cn^p+%!8NN2k4n|zjvn*Vv;=j6@?94Kc% z{+0SV_~`>Z%@!R!x3IpUF<i!MRQofgtU@Fqa=?Qmwy_DuWN*2bJdgFBvY~d-46%FDqh`lMv)ZMT&o156)c#y*; z5K^!$4J|PHgUQAlJM8$&)#{HtZv2yG(YwZv2?QZa1mTx>1#|)E<(p#DcU97pvBaD{ zxvpm%7d8Q*w%g=TnZ0Ga*`7jJrnJU7K`j<}BG60eEK#0_&~Z z>4&vG?26pct$SEC4ns06UyRDB9}q2MJz5Q!mj~fJ^mc2JRs)~|(nZWOs@lIajM7e5 zCcT>{%T6~Iv|PR7)t*w^vT$P$TU*tIKhPXLWvSgug|V=XILAWo!mIj73gZ^Yund%p zEpdC}!>eQn)7|n?peK3rkKn(@vElBw%4_Q?F8Y=qF}ox{u4avvfv*YcxAuXYNg|%BB*E5F#Il zBzhVo(p?Y|lF+!QrlKOE5RpzZwL*%9#&NPRbFDFZ7iU-W?t%F;G6RBN0Db{@#J#9igmY z4m-nKItJ~(;7#|CRi#s^2J0w02qfY?qQ`DqfmQ(|lryF#Z;UZCu*!J&%nqpo1=ZT! za_KxGXGKtrxYrvias?&UC^u{|nV2K4VZBM}bxWZ&9(pwpD6p9V`{3jgG&|v2clqnJ zWg?MpBEI}j9L}Gr*jz`lGKGk{<`d4l650DiP+WFOY7fZ8!Xw2_>>X3K=f>C^j#DBS zrC8Tr1mS_-B7hEklOPL9w_)nr50xs??(}`xqj+0vOu^p20Ln3>L;d>_by5O(mKP z>s1cs=`>hZ*|XE7kUL(>wzU@H5%GFSEIdEzezCv!p)kMmx`1e6bSCd7SD$_ zn@JWzm`7BIxFs!sH=m6Gb+TPuklVJd^^eqH8d@0m+=_vW#GifuR|T$9s&lA43UpN` z|4oL9SyC4e%0DuZKDfLY)--X*cdD^Of;G3ajT*N=LSTGCK!PblRU||bMRu&Ji4eCH zHAyRuOXfi~T$n6U*k{2f*tVDAY0Bcm1IfCCy}(X=2m9y07{iJO{tF}!kQ(~`h|aEN zYGG+?WBSkhwp4AOPm^6TdQha^ja@>-gFs-9=P9RC%`t5CJ=NyZF|pl zoaDXb-fZ!d<#Brw1fUl}*+_7IFxA=5Y~UC{;H{oZn!2=6tmK|uuzX~gdi5IO*xAH? z!&GIRF&}1&%*g9la6b)}Lu{H|ynCxCJ`|Uo0y{+M82AbhPJz`Y$<5B(sLeJ&=VrN=*gxQar-~z$uH#=<4Na9wx3}g+ zkDJfK6@wih(K3h?_HQ`&0%OTyV+rQW0s$B&@%<)avs#w!;IP;cGORRb2)%51E=0isHV4gC)mMyT#QT?m+x;I8^~ zX^_+P51-fgJ^Uv==jjvQ?3>{OYWp(tIsgD8G8$|UQL+9&2R+^705 zRbaM{Vi4f?IM*7HzpU{{hw!pI%Q`ZSPy5Y|mdOK209TxQb8XkDb5>ud1al)<$f|}k zMav{8bC2Q6_Z;K%nKj8>mrIRs>xT?U-LsmZSMEhTXJ4~KkmdSBid}21-hJrm*tAv+ zXpHQ6tzdK;^!7?b`ulLJ1^}i)8ca1kSPHN*UYcJ zcTy_o#ql2xszV_o5(`&!wZdB2nO#F6$+((ZVI(i<6!<*`j=;NfkG%kK`M1W9E@aJn z;Q}hHc?Uhll$L-vag3)#sDUA%nT6$TIm~cicgXcnFylnyKuoVh!J0E$c5iRE^!54) zrzBtCyY6#6gBPd>2{k_`J?z2gJ7(grkl}uwt&bSo_R*%ptp%D}DNQ0h(KdR}h%g+@ zmirdg?lkch{U@RC zxZ&nVIyYUl?HK10~Ix-oe-pXV6G zX4XA79|Z;5SUHDu4Z|T#V_Y6_@N1Z930^v6hH8mjf+N9*I^f+{=-bgR^v;!&jWfs| zlD8(HGRz(-q@sS^1vLeWp}^6B-t0lc zfe@9^`Pg&h!>MD3^tIoohr`0Hb0FAZXYD@5bQgYd1%FkBbW97IRfc^0UBl$0!CBre zK%4wV0fqwc=VJ)`b$eGY%#;eKC3ZKH*Dg5swjH`5l`^>oX?NL|T)b*Hd=tFRsy}QMMJydltI57s zUfeXyBe6slSosdBt{UGWK$C-yXO5-pB6viggM85huVbQ z-WRvWO4XHGsFqk^+9zzZjqhlK(&1Y!5^o^xc%rI~&GIpYxQjOn`4m_#BJ#6G$ip7O zkNPY!f={iP*zQDngWrYr$Sxz0EO>C>Z)of3`2p)=kCK>Lq)Z_kTAQw+RLpi3`Dh?6 zgx$#o3iW{2^9P~f>J|*F z+RtErTXQ<0hqLk)?x&XpQZzxh@R{9zIpNPki+x~qW7nC`s^0-Ru^p=oSy%5ZSEt)n zyqM3xx2D&%8xk?{WZNNXJYci$I5%#2Yz8cC(9qV%%?!FSB11A_bI<3!kq$iY{6h6n z5XTZm?QgYZ21z6oZl20ZmvQbO-jT9+FOVGSIq>|6@u@BE=AL2rdc#oP)!%bZ+tJHz z5#Eyxy5SKH^q)BZq|JjV2qm9kPKvdJqtglX(1h%G<+h2DlZj2#RzGJ{>A3?b!~?Yf z;5aE?`+|hGRqUktcb=2O!+02!b<>KU1hbUKm}$pU0s$m#@paQ5D&;A}0A@2H5fQ** zC93NBFDo;Xw0l(b${n@QwvVrWep)q=grfR=S1y14XOn{JriLyS|D}9Uk5t1QL+crV zZN?N6OREMO8BnqjXBjLfL$wwr#%7eD3_vtMC8BU-S%Rg@vir47@{`?s_i09G*2uJ> zi9!t*RPK_q>yUHm<_MP{f2-0iEf8xSayIk2*30#xSG!KH$Llc`Lk~D%z#nX*@w((% z9+$^YHQ$YoC!GOPPmX|2Mn)shPTS9byR=L{Mc3S9RdfmRTMm@se1Q-Yzn3m%x}3m6 zj9mjC8^wY)+Gj7yafULS@3gSG=*3u9zFJLFRT$nkK^BOq<;xLWD!ArM3_R$WFQvr)0 z&}%hPfqN8=LjDB4)JuoqTle$Z;~+HoF7!FgbBq<%cZXlhb`S79|wuwIo1Y zu1-rk*<#oisvFB2^jfR=$|0ADhQWcp#ZAylw`FtL7))Y-#l*rfuWj!WwDK0{1Zpe7 z7FD}J#fvnu%_yHV=^!dChqwypv1JeYFAA&q%V@c+*f?-d0zzR2x-2JQ!o!S#q)cTF zwj=_DBVzbb3LS7ZocBh?9R#dcN3#5Z?f!T;SQZU5+)sJ)vGVPKMebBr;+UzzyP)K* zo_1Ph-MFcHq(--ZuwmcIhYLY?fLsP6Q8tkG%iaH1w)@@6cHj_2G zu63y2m9O}jL61KT)?W*-7)$4h^|`$Q@05Q53>5^M-e)H&49FwJ8|vVN32R8(){txa zgGaQJWHLb2SdMsahRG)EMGS7jeq-aKlqdgb*F@6<=7}!Tyd=Esy36D4hKYxInPi7e=DRw63hRW= zU|;SK!-y+c!D)+AyBT@|gcBf2)J!6q$zj*YlxF5Ip^cyK3FqwGqKT9ncvqHuSu-~e zs@T(5O28_;F!Cq$qdb=jzP5&2K64@nN-f~w(NI6z+L`E%79>+#Q`}X6@(0SZ1Z4aH z{;hgla8-d;dLmv|e{GRk@zwO4vqhMnE;O5xMEU$XAk!T5Rnmt_hFt%*qBe6eJ;5z@ zFLtd$+aUz=D$G_swX+^wJn?GXAr6!e+%By6le6cTj=j>JO2u-rJ9P1UU^Zk6=R1l#%|S+`)zao0uSiI ztuJ)iV}|++4A_#fuXD4JlojFGJEDNp9)w4jqv?1624^C5W|Ne7n^ZT={YLy93f8g! z8fOO*!O+uoH2_>JhuIKjju%egB+|>m{&D*r{w3V zM=g5a_pr#2J7HFu2ytsb3di& z$?e(ln4eTNr?DRyo~ycMszo^3z)w#TX-&3|eYImAVeP`(B`G?wQp98xgR@?Fy8p z!82=8CH_((jVbDQHbZ<7RBIHL^mPJsvd(Kf@etlGGIK^3!gIp;qQ=TT@ZtF*@GqWt zk3P7WBjn4}RKvH}{?>|*+}LcawyH7Mg?H*>vH{))59a88hP^b9zOQdPF z>vMZzfV`Yw$0X17ZBn}d0z9&pKSA8$_hh5}Y}Ea71g)V2Hi!k73kl*y{9Mpq9NF)S zok0XP#?M$k#uFrYHo4ElF+NV|FT~xC{c?tQehLh8(Oa->+9mksKLo2lpnuN_b9b)0 zeK$z<=x3hT7y`;pBRz!Q$=}WEYhK9B&~FE*MXsC-gnjS{vWLvkZ$qg?;Qbw;0P*Dd znIjF)(ev~pzweH4^R)WmpuTMW@cNPKjWVpMdsDfg^kL-;4es&kb>kP(PPL*ma3mDk zVAH}~f7M^Ox>!}K0V2dzh98v@{={_ng*}0lFbV#EpgJplCm4nNa&C`-;tea)U=)ZQ zOKrD>-RVKSBYO{225|j0)ltnp98KEeDBmMU-D^tR5;1R#zFm z*Q4qByF0j>&Y=-&wN-{mCLC<>m-=1XRuFwBCU^J@{9U|7?LYgwevLAVmwx-Z7Qq7n z{rvBvlmB8B_Rqjcs=BSZ$|{;4{2wPnOk%BaqaZoh(H|*_MdXS>#8#wuzn}xvmPV(h z4cljPy(e;c5I4G;BW*WIX1o^-HoDoYkDFxEjQF2HUR!owr8_%2v-nT80)>8)xiswM zbj|V4t-r0mUHU%1`S^+HIsVjJ}$GVI+Ygp zs<3Q%DA8JMa?!LCgv4D(9UPZgc$$vUU&luFD1g82XLBAvU8@R9`5v)LIi_v*%*0Or z%*p06=hoCl_g<{cW|+Z=fWEY62+%fFm6m%gu4S=KTrr;5 z31iRumQd8am)Uj7bzEXXyK;0fG>4UK*WWXcjfvSwt8Cd6Z0lz?%V;rpkv@^x%r{}Q zr6QysYru&_zZ?vfsgy-B*dq!wJ5ED}ahb1j#c4_Tdp$D5^k9%L_%^Bv(qobjX^y*l zPd9PuPT^g*H3ZPol(GyZD|$5cr+IY{-1`Lo?ppy*z&(u9K3*GokXp~aMWqSHlvMD8 z%8_0Ssfo%8+p*pu;l>#vii7_B1)I<-TZNC~p<+;_itC)wD{3tS&IpX4u3L#6{>W?- zf2aW)A4t_&oVO32w-@`zWI)%TT6kiof>UnKgeHs$S)Arun6g|Ee94`{CqA7Q(hQ&n zs`TmxZjSaLf1g(Iz{Qw!7f&YgGp$FGG&~;nQZ|EZmk$v)U3S{bj)*9WiF{sJH4xa~ zx7Fyw95a({{OG5If%m}llpwF|i*pW|@6uf+ApzM^Hnrzi)l6NRqm!lHib7wboQvV_ ztm)fHb>Wz-RzAa*q@$QaA6QGBHRKV|K;MZ5tLdzgxWh&EzgbXX^?Ds_x2!NF$J3p_ zxf9s!F{iA4$U~@plL}K#$XC_*Q@~^t0}81|H>7j>{IXy!O7;ucu(Fk+CLt6IHJW~bwAQN*_ZTK)1%U5oc4|mG!)