From e315df82f8d800afcc3656a2f4dc200ac350152b Mon Sep 17 00:00:00 2001 From: Arubik <102335860+ArubikU@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:47:37 -0500 Subject: [PATCH 001/177] ChangeOverTimeBlockBehavior usefull for features like oxidizing block --- .../block/behavior/BukkitBlockBehaviors.java | 3 +- .../behavior/ChangeOverTimeBlockBehavior.java | 77 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.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 3f1d586b7..6e1b23209 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 @@ -27,7 +27,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key STAIRS_BLOCK = Key.from("craftengine:stairs_block"); public static final Key PRESSURE_PLATE_BLOCK = Key.from("craftengine:pressure_plate_block"); public static final Key DOUBLE_BLOCK = Key.from("craftengine:double_block"); - + public static final Key CHANGE_OVER_TIME_BLOCK = Key.from("craftengine:change_over_time_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); register(FALLING_BLOCK, FallingBlockBehavior.FACTORY); @@ -52,5 +52,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(STAIRS_BLOCK, StairsBlockBehavior.FACTORY); register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY); register(DOUBLE_BLOCK, DoubleBlockBehavior.FACTORY); + register(CHANGE_OVER_TIME_BLOCK,ChangeOverTimeBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java new file mode 100644 index 000000000..c7e4c2707 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java @@ -0,0 +1,77 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +import org.bukkit.GameEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.util.Vector; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +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.util.Key; +import net.momirealms.craftengine.core.util.RandomUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.sparrow.nbt.CompoundTag; + +public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior{ + public static final Factory FACTORY = new Factory(); + private final float delay; + private final Key nextBlock; + + public ChangeOverTimeBlockBehavior(CustomBlock customBlock, float delay, Key nextBlock) { + super(customBlock); + this.delay = delay; + this.nextBlock = nextBlock; + } + + @Override + public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + if (shouldChange(args)) { + Optional optionalNewCustomBlock = BukkitBlockManager.instance().blockById(nextBlock); + if (optionalNewCustomBlock.isPresent()) { + + Object blockState = args[0]; + World level = (World) args[1]; + BlockPos blockPos = (BlockPos) args[2]; + Optional optionalCurrentState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCurrentState.isEmpty()) { + return; + } + CompoundTag compoundTag = optionalCurrentState.get().propertiesNbt(); + ImmutableBlockState newState = optionalNewCustomBlock.get().getBlockState(compoundTag); + BukkitBlockInWorld blockInWorld = (BukkitBlockInWorld) level.getBlockAt(LocationUtils.fromBlockPos(blockPos)); + BlockFormEvent event = new BlockFormEvent(blockInWorld.block(), BlockStateUtils.fromBlockData(newState.customBlockState().handle()).createBlockState()); + if(event.callEvent()){ + + FastNMS.INSTANCE.method$LevelWriter$setBlock(level.serverWorld(), blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags()); + blockInWorld.block().getWorld().sendGameEvent(null, GameEvent.BLOCK_CHANGE, new Vector(blockPos.x(), blockPos.y(), blockPos.z())); + } + } + } + } + + private boolean shouldChange(Object[] args) { + return RandomUtils.generateRandomFloat(0F, 1F) < this.delay; + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + float delay = Float.valueOf(arguments.getOrDefault("delay", 0.05688889F).toString()); + String nextBlock = arguments.getOrDefault("next-block", "minecraft:air").toString(); + return new ChangeOverTimeBlockBehavior(block, delay, Key.from(nextBlock)); + } + } +} From 2dfbdd3a5b18c8c323f0201609ee86ab3ca2b539 Mon Sep 17 00:00:00 2001 From: Arubik <102335860+ArubikU@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:50:43 -0500 Subject: [PATCH 002/177] Update BukkitBlockBehaviors.java --- .../craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java | 2 -- 1 file changed, 2 deletions(-) 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 f1735e400..379a85575 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 @@ -26,7 +26,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SLAB_BLOCK = Key.from("craftengine:slab_block"); public static final Key STAIRS_BLOCK = Key.from("craftengine:stairs_block"); public static final Key PRESSURE_PLATE_BLOCK = Key.from("craftengine:pressure_plate_block"); - public static final Key DOUBLE_BLOCK = Key.from("craftengine:double_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 void init() { @@ -52,7 +51,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SLAB_BLOCK, SlabBlockBehavior.FACTORY); register(STAIRS_BLOCK, StairsBlockBehavior.FACTORY); register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY); - register(DOUBLE_BLOCK, DoubleBlockBehavior.FACTORY); register(DOUBLE_HIGH_BLOCK, DoubleHighBlockBehavior.FACTORY); register(CHANGE_OVER_TIME_BLOCK,ChangeOverTimeBlockBehavior.FACTORY); } From 26b478a9ea97659a7fdbce723a1b8e07cbf6ab07 Mon Sep 17 00:00:00 2001 From: Arubik <102335860+ArubikU@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:37:47 -0500 Subject: [PATCH 003/177] Update ChangeOverTimeBlockBehavior.java --- .../behavior/ChangeOverTimeBlockBehavior.java | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java index c7e4c2707..d79531deb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java @@ -20,11 +20,12 @@ import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.RandomUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.World; import net.momirealms.sparrow.nbt.CompoundTag; -public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior{ +public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final float delay; private final Key nextBlock; @@ -37,27 +38,21 @@ public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior{ @Override public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - if (shouldChange(args)) { - Optional optionalNewCustomBlock = BukkitBlockManager.instance().blockById(nextBlock); - if (optionalNewCustomBlock.isPresent()) { - - Object blockState = args[0]; - World level = (World) args[1]; - BlockPos blockPos = (BlockPos) args[2]; - Optional optionalCurrentState = BlockStateUtils.getOptionalCustomBlockState(blockState); - if (optionalCurrentState.isEmpty()) { - return; - } - CompoundTag compoundTag = optionalCurrentState.get().propertiesNbt(); - ImmutableBlockState newState = optionalNewCustomBlock.get().getBlockState(compoundTag); - BukkitBlockInWorld blockInWorld = (BukkitBlockInWorld) level.getBlockAt(LocationUtils.fromBlockPos(blockPos)); - BlockFormEvent event = new BlockFormEvent(blockInWorld.block(), BlockStateUtils.fromBlockData(newState.customBlockState().handle()).createBlockState()); - if(event.callEvent()){ - - FastNMS.INSTANCE.method$LevelWriter$setBlock(level.serverWorld(), blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags()); - blockInWorld.block().getWorld().sendGameEvent(null, GameEvent.BLOCK_CHANGE, new Vector(blockPos.x(), blockPos.y(), blockPos.z())); - } - } + if(!shouldChange(args)) return; + Optional optionalNewCustomBlock = BukkitBlockManager.instance().blockById(nextBlock); + if (!optionalNewCustomBlock.isPresent()) return; + Object blockState = args[0]; + World level = (World) args[1]; + BlockPos blockPos = (BlockPos) args[2]; + Optional optionalCurrentState = BlockStateUtils .getOptionalCustomBlockState(blockState); + if (optionalCurrentState.isEmpty()) return; + CompoundTag compoundTag = optionalCurrentState.get().propertiesNbt(); + ImmutableBlockState newState = optionalNewCustomBlock.get().getBlockState(compoundTag); + BukkitBlockInWorld blockInWorld = (BukkitBlockInWorld) level.getBlockAt(LocationUtils.fromBlockPos(blockPos)); + BlockFormEvent event = new BlockFormEvent(blockInWorld.block(),BlockStateUtils.fromBlockData(newState.customBlockState().handle()).createBlockState()); + if (event.callEvent()) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level.serverWorld(), blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags()); + blockInWorld.block().getWorld().sendGameEvent(null, GameEvent.BLOCK_CHANGE, new Vector(blockPos.x(), blockPos.y(), blockPos.z())); } } @@ -66,12 +61,11 @@ public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior{ } public static class Factory implements BlockBehaviorFactory { - @Override public BlockBehavior create(CustomBlock block, Map arguments) { float delay = Float.valueOf(arguments.getOrDefault("delay", 0.05688889F).toString()); - String nextBlock = arguments.getOrDefault("next-block", "minecraft:air").toString(); - return new ChangeOverTimeBlockBehavior(block, delay, Key.from(nextBlock)); + Key nextBlock = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time_block_missing_next_block")); + return new ChangeOverTimeBlockBehavior(block, delay, nextBlock); } } } From 4e23f53784dba4d6a3a9dcbafb42f61f9a3f9721 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Mon, 11 Aug 2025 08:26:41 +0800 Subject: [PATCH 004/177] Update BukkitBlockBehaviors.java --- .../craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java | 1 + 1 file changed, 1 insertion(+) 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 379a85575..d914ae7df 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 void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); register(FALLING_BLOCK, FallingBlockBehavior.FACTORY); From 644a9a646684d162503f95aa895e50dec91e549d Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Mon, 11 Aug 2025 09:40:46 +0800 Subject: [PATCH 005/177] =?UTF-8?q?refactor(block):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20ChangeOverTimeBlockBehavior=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/ChangeOverTimeBlockBehavior.java | 71 ++++++------------- .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + 3 files changed, 25 insertions(+), 48 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java index d79531deb..78335c893 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java @@ -1,71 +1,46 @@ package net.momirealms.craftengine.bukkit.block.behavior; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; +import net.momirealms.craftengine.core.block.*; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +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; import java.util.Optional; import java.util.concurrent.Callable; -import org.bukkit.GameEvent; -import org.bukkit.event.block.BlockFormEvent; -import org.bukkit.util.Vector; - -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; -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.util.Key; -import net.momirealms.craftengine.core.util.RandomUtils; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.World; -import net.momirealms.sparrow.nbt.CompoundTag; - public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final float delay; + private final float changeSpeed; private final Key nextBlock; - public ChangeOverTimeBlockBehavior(CustomBlock customBlock, float delay, Key nextBlock) { + public ChangeOverTimeBlockBehavior(CustomBlock customBlock, float changeSpeed, Key nextBlock) { super(customBlock); - this.delay = delay; + this.changeSpeed = changeSpeed; this.nextBlock = nextBlock; } @Override - public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - if(!shouldChange(args)) return; - Optional optionalNewCustomBlock = BukkitBlockManager.instance().blockById(nextBlock); - if (!optionalNewCustomBlock.isPresent()) return; - Object blockState = args[0]; - World level = (World) args[1]; - BlockPos blockPos = (BlockPos) args[2]; - Optional optionalCurrentState = BlockStateUtils .getOptionalCustomBlockState(blockState); - if (optionalCurrentState.isEmpty()) return; - CompoundTag compoundTag = optionalCurrentState.get().propertiesNbt(); - ImmutableBlockState newState = optionalNewCustomBlock.get().getBlockState(compoundTag); - BukkitBlockInWorld blockInWorld = (BukkitBlockInWorld) level.getBlockAt(LocationUtils.fromBlockPos(blockPos)); - BlockFormEvent event = new BlockFormEvent(blockInWorld.block(),BlockStateUtils.fromBlockData(newState.customBlockState().handle()).createBlockState()); - if (event.callEvent()) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level.serverWorld(), blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags()); - blockInWorld.block().getWorld().sendGameEvent(null, GameEvent.BLOCK_CHANGE, new Vector(blockPos.x(), blockPos.y(), blockPos.z())); - } - } - - private boolean shouldChange(Object[] args) { - return RandomUtils.generateRandomFloat(0F, 1F) < this.delay; + public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws ReflectiveOperationException { + if (RandomUtils.generateRandomFloat(0F, 1F) >= this.changeSpeed) return; + Optional nextState = BukkitBlockManager.instance().blockById(this.nextBlock) + .map(CustomBlock::defaultState) + .map(ImmutableBlockState::customBlockState) + .map(BlockStateWrapper::handle); + if (nextState.isEmpty()) return; + CraftBukkitReflections.method$CraftEventFactory$handleBlockFormEvent.invoke(null, args[1], args[2], nextState.get(), UpdateOption.UPDATE_ALL.flags()); } public static class Factory implements BlockBehaviorFactory { + @Override public BlockBehavior create(CustomBlock block, Map arguments) { - float delay = Float.valueOf(arguments.getOrDefault("delay", 0.05688889F).toString()); - Key nextBlock = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time_block_missing_next_block")); - return new ChangeOverTimeBlockBehavior(block, delay, nextBlock); + float changeSpeed = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("change-speed", 0.05688889F), "change-speed"); + Key nextBlock = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time_block.missing_next_block")); + return new ChangeOverTimeBlockBehavior(block, changeSpeed, nextBlock); } } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 8a428d817..93b03f5c1 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -299,6 +299,7 @@ warning.config.block.behavior.stairs.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_block.missing_next_block: "Issue found in file - The block '' is missing the required 'next_block' property for 'change_over_time_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.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" warning.config.model.generation.invalid_gui_light: "Issue found in file - The config '' is using an invalid gui-light option '' in 'generation' section. Allowed gui light options: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 3512ddf1b..b24d301a4 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -299,6 +299,7 @@ warning.config.block.behavior.stairs.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_block.missing_next_block: "在文件 发现问题 - 方块 '' 的 'change_over_time_block' 行为缺少必需的 'next-block' 配置项" 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 6f6bb4c9da081c1ad252e5b6bf21084e20cc8784 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 22 Aug 2025 19:36:52 +0800 Subject: [PATCH 006/177] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E4=B9=A6=E5=8C=B9=E9=85=8D?= 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 +- .../item/recipe/BukkitRecipeManager.java | 21 ++------------ .../item/recipe/CustomIngredientList.java | 29 +++++++++++++++++++ .../item/recipe/CustomIngredientSet.java | 28 ++++++++++++++++++ gradle.properties | 4 +-- 6 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientSet.java diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 29d893949..03b971562 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -47,7 +47,7 @@ bukkit { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") - contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon") + contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8") 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 1d5ce6703..a5a269d3b 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -50,7 +50,7 @@ paper { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") - contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon") + contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8") foliaSupported = true serverDependencies { register("PlaceholderAPI") { 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 519734e35..7cba8d1bc 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 @@ -3,7 +3,6 @@ package net.momirealms.craftengine.bukkit.item.recipe; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.papermc.paper.potion.PotionMix; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; @@ -85,7 +84,6 @@ public class BukkitRecipeManager extends AbstractRecipeManager { } } - private static final List MODIFIED_INGREDIENTS = new ArrayList<>(); private static final Map, Object>> ADD_RECIPE_FOR_MINECRAFT_RECIPE_HOLDER = Map.of( RecipeSerializers.SHAPED, recipe -> { CustomShapedRecipe shapedRecipe = (CustomShapedRecipe) recipe; @@ -245,9 +243,9 @@ public class BukkitRecipeManager extends AbstractRecipeManager { Ingredient actualIngredient = actualIngredients.get(i); List items = getIngredientLooks(actualIngredient.items()); if (VersionHelper.isOrAbove1_21_4()) { - CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (Set) new ObjectOpenHashSet<>(items)); + CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (Set) new CustomIngredientSet(items, actualIngredient)); } else if (VersionHelper.isOrAbove1_21_2()) { - CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (List) items); + CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (List) new CustomIngredientList(items, actualIngredient)); } else { Object itemStackArray = Array.newInstance(CoreReflections.clazz$ItemStack, items.size()); for (int j = 0; j < items.size(); j++) { @@ -255,7 +253,6 @@ public class BukkitRecipeManager extends AbstractRecipeManager { } CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (Object) itemStackArray); } - MODIFIED_INGREDIENTS.add(ingredient); } } @@ -498,20 +495,6 @@ public class BukkitRecipeManager extends AbstractRecipeManager { // send to players CoreReflections.methodHandle$DedicatedPlayerList$reloadRecipes.invokeExact(CraftBukkitReflections.methodHandle$CraftServer$playerListGetter.invokeExact(Bukkit.getServer())); - - // now we need to remove the fake `exact` choices - if (VersionHelper.isOrAbove1_21_4()) { - for (Object ingredient : MODIFIED_INGREDIENTS) { - CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (Set) null); - } - } else if (VersionHelper.isOrAbove1_21_2()) { - for (Object ingredient : MODIFIED_INGREDIENTS) { - CoreReflections.methodHandle$Ingredient$itemStacksSetter.invokeExact(ingredient, (List) null); - } - } - - // clear cache - MODIFIED_INGREDIENTS.clear(); } catch (Throwable e) { this.plugin.logger().warn("Failed to run delayed recipe tasks", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java new file mode 100644 index 000000000..b5c977d3b --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java @@ -0,0 +1,29 @@ +package net.momirealms.craftengine.bukkit.item.recipe; + +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.core.item.recipe.Ingredient; +import net.momirealms.craftengine.core.item.recipe.UniqueIdItem; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +public class CustomIngredientList extends ArrayList { + private final Ingredient ingredient; + + public CustomIngredientList(@NotNull Collection c, Ingredient ingredient) { + super(c); + this.ingredient = ingredient; + } + + @Override + public boolean contains(Object o) { + if (o == null || FastNMS.INSTANCE.method$ItemStack$isEmpty(o)) { + return false; + } + return this.ingredient.test(UniqueIdItem.of(BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(o)))); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientSet.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientSet.java new file mode 100644 index 000000000..c7201607a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientSet.java @@ -0,0 +1,28 @@ +package net.momirealms.craftengine.bukkit.item.recipe; + +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.core.item.recipe.Ingredient; +import net.momirealms.craftengine.core.item.recipe.UniqueIdItem; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.HashSet; + +public class CustomIngredientSet extends HashSet { + private final Ingredient ingredient; + + public CustomIngredientSet(@NotNull Collection c, Ingredient ingredient) { + super(c); + this.ingredient = ingredient; + } + + @Override + public boolean contains(Object o) { + if (o == null || FastNMS.INSTANCE.method$ItemStack$isEmpty(o)) { + return false; + } + return this.ingredient.test(UniqueIdItem.of(BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(o)))); + } +} diff --git a/gradle.properties b/gradle.properties index 3ce89d0df..ac5eb779a 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 +project_version=0.0.62.1 config_version=44 lang_version=24 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.19 -nms_helper_version=1.0.57 +nms_helper_version=1.0.58 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From c8faba938d9687331f462bfbb8eb8332d01d2e91 Mon Sep 17 00:00:00 2001 From: tamashii Date: Fri, 22 Aug 2025 15:23:43 +0200 Subject: [PATCH 007/177] Update German Language file. --- .../src/main/resources/translations/de.yml | 784 +++++++++--------- 1 file changed, 396 insertions(+), 388 deletions(-) diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 3e4619163..3e3ffe8b1 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -1,418 +1,426 @@ -# Nicht ändern +# Bitte nicht ändern lang-version: "${lang_version}" exception.invalid_syntax: "Ungültige Syntax. Korrekte Syntax: " exception.invalid_argument: "Ungültiges Argument. Grund: " -exception.invalid_sender: " ist nicht berechtigt, diesen Befehl auszuführen. Muss vom Typ " -exception.unexpected: "Beim Versuch, diesen Befehl auszuführen, ist ein interner Fehler aufgetreten" -exception.no_permission: "Es tut mir leid, aber Sie haben keine Berechtigung, diesen Befehl auszuführen" +exception.invalid_sender: " darf diesen Befehl nicht ausführen. Muss vom Typ sein" +exception.unexpected: "Ein interner Fehler ist beim Ausführen dieses Befehls aufgetreten" +exception.no_permission: "Entschuldigung, aber du hast keine Berechtigung, diesen Befehl auszuführen" exception.no_such_command: "Unbekannter Befehl." -argument.entity.notfound.player: "Spieler nicht gefunden: " -argument.entity.notfound.entity: "Entität nicht gefunden: " +argument.entity.notfound.player: "" +argument.entity.notfound.entity: "" argument.parse.failure.time: "'' ist kein gültiges Zeitformat" -argument.parse.failure.material: "'' ist kein gültiger Materialname" +argument.parse.failure.material: "'' ist kein gültiger Material-Name" argument.parse.failure.enchantment: "'' ist keine gültige Verzauberung" argument.parse.failure.offlineplayer: "Kein Spieler für die Eingabe '' gefunden" argument.parse.failure.player: "Kein Spieler für die Eingabe '' gefunden" argument.parse.failure.world: "'' ist keine gültige Minecraft-Welt" -argument.parse.failure.location.invalid_format: "'' ist kein gültiger Ort. Erforderliches Format ist ' " -argument.parse.failure.location.mixed_local_absolute: "Lokale und absolute Koordinaten können nicht gemischt werden. (Entweder alle Koordinaten verwenden '^' oder keine)" +argument.parse.failure.location.invalid_format: "'' ist keine gültige Koordinate. Benötigtes Format ist ' " +argument.parse.failure.location.mixed_local_absolute: "Lokale und absolute Koordinaten können nicht gemischt werden. (entweder alle Koordinaten verwenden '^' oder keine)" argument.parse.failure.namespacedkey.namespace: "Ungültiger Namespace ''. Muss [a-z0-9._-] sein" -argument.parse.failure.namespacedkey.key: "Ungültiger Schlüssel ''. Muss [a-z0-9/._-] sein" +argument.parse.failure.namespacedkey.key: "Ungültiger Key ''. Muss [a-z0-9/._-] sein" argument.parse.failure.namespacedkey.need_namespace: "Ungültige Eingabe '', erfordert einen expliziten Namespace" -argument.parse.failure.boolean: "Konntest keinen booleschen Wert aus '' parsen" +argument.parse.failure.boolean: "Boolean konnte aus '' nicht geparst werden" argument.parse.failure.number: "'' ist keine gültige Zahl im Bereich von bis " argument.parse.failure.char: "'' ist kein gültiges Zeichen" argument.parse.failure.string: "'' ist kein gültiger String vom Typ " argument.parse.failure.uuid: "'' ist keine gültige UUID" -argument.parse.failure.enum: "'' ist keiner der folgenden Werte: " +argument.parse.failure.enum: "'' ist nicht einer der folgenden Werte: " argument.parse.failure.regex: "'' stimmt nicht mit '' überein" -argument.parse.failure.flag.unknown: "Unbekanntes Flag ''" -argument.parse.failure.flag.duplicate_flag: "Doppeltes Flag ''" +argument.parse.failure.flag.unknown: "Unbekannter Flag ''" +argument.parse.failure.flag.duplicate_flag: "Doppelter Flag ''" argument.parse.failure.flag.no_flag_started: "Kein Flag gestartet. Weiß nicht, was mit '' zu tun ist" argument.parse.failure.flag.missing_argument: "Fehlendes Argument für ''" -argument.parse.failure.flag.no_permission: "Sie haben keine Berechtigung, '' zu verwenden" +argument.parse.failure.flag.no_permission: "Du hast keine Berechtigung, '' zu verwenden" argument.parse.failure.color: "'' ist keine gültige Farbe" -argument.parse.failure.duration: "'' ist kein gültiges Zeitdauerformat" +argument.parse.failure.duration: "'' ist kein gültiges Zeitdauer-Format" argument.parse.failure.aggregate.missing: "Fehlende Komponente ''" argument.parse.failure.aggregate.failure: "Ungültige Komponente '': " argument.parse.failure.either: "Konnte oder aus '' nicht auflösen" argument.parse.failure.namedtextcolor: "'' ist keine benannte Textfarbe" -command.reload.config.success: "Konfigurationen in ms neu geladen. (Async: ms | Sync: ms)" -command.reload.config.failure: "Neuladen der Konfiguration fehlgeschlagen. Überprüfen Sie die Konsolenprotokolle." -command.reload.pack.success: "Ressourcenpaket in ms neu geladen." -command.reload.pack.failure: "Neuladen des Ressourcenpakets fehlgeschlagen. Überprüfen Sie die Konsolenprotokolle." -command.reload.all.success: "Neu laden in ms abgeschlossen. (Async: ms | Sync: ms | Pack: ms)" -command.reload.all.failure: "Neu laden fehlgeschlagen. Überprüfen Sie die Konsolenprotokolle." -command.item.get.success: "Sie haben erhalten" +command.reload.config.success: "Configs in ms neugeladen. (Async: ms | Sync: ms)" +command.reload.config.failure: "Neuladen der Config fehlgeschlagen. Überprüfe die Konsolen-Logs." +command.reload.pack.success: "Resource Pack in ms neugeladen." +command.reload.pack.failure: "Neuladen des Resource Packs fehlgeschlagen. Überprüfe die Konsolen-Logs." +command.reload.all.success: "Neuladen in ms abgeschlossen. (Async: ms | Sync: ms | Pack: ms)" +command.reload.all.failure: "Neuladen fehlgeschlagen. Überprüfe die Konsolen-Logs." +command.item.get.success: " erhalten" command.item.get.failure.not_exist: "'>" command.item.give.success.single: "':'':''>" command.item.give.success.multiple: "':'':''>" command.item.give.failure.not_exist: "'>" -command.search_recipe.not_found: "Kein Rezept für diesen Item gefunden" +command.search_recipe.not_found: "Kein Rezept für dieses Item gefunden" command.search_usage.not_found: "Keine Verwendung für dieses Item gefunden" -command.search_recipe.no_item: "Bitte halten Sie ein Item, bevor Sie diesen Befehl ausführen" -command.search_usage.no_item: "Bitte halten Sie ein Item, bevor Sie diesen Befehl ausführen" -command.totem_animation.failure.not_totem: "Item '' ist kein minecraft:totem_of_undying" -command.resource.enable.success: "Ressource aktiviert. Führen Sie /ce reload all aus, um die Änderungen anzuwenden" -command.resource.enable.failure.unknown: "Unbekannte Ressource " -command.resource.disable.success: "Ressource deaktiviert. Führen Sie /ce reload all aus, um die Änderungen anzuwenden" -command.resource.disable.failure.unknown: "Unbekannte Ressource " -command.resource.list: "Aktivierte Ressourcen(): Deaktivierte Ressourcen(): " -command.upload.failure.not_supported: "Die aktuelle Hosting-Methode '' unterstützt das Hochladen von Ressourcenpaketen nicht." -command.upload.on_progress: "Upload-Vorgang gestartet. Überprüfen Sie die Konsole für weitere Informationen." -command.send_resource_pack.success.single: "Ressourcenpaket an gesendet." -command.send_resource_pack.success.multiple: "Ressourcenpakete an Spieler gesendet." -warning.config.pack.duplicated_files: "Duplizierte Dateien gefunden. Bitte beheben Sie diese im Abschnitt 'resource-pack.duplicated-files-handler' in config.yml." -warning.config.yaml.duplicated_key: "Problem in Datei gefunden - Duplizierter Schlüssel '' in Zeile gefunden, dies kann zu unerwarteten Ergebnissen führen." -warning.config.yaml.inconsistent_value_type: "Fehler in der Datei - Der duplizierte Schlüssel '' wurde in Zeile mit einem anderen Werttyp gefunden. Dies könnte zu unerwarteten Ergebnissen führen." -warning.config.type.int: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Kann '' nicht in den Integer-Typ für Option '' umwandeln." -warning.config.type.float: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Kann '' nicht in den Float-Typ für Option '' umwandeln." -warning.config.type.double: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Kann '' nicht in den Double-Typ für Option '' umwandeln." -warning.config.type.quaternionf: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Kann '' nicht in den Quaternionf-Typ für Option '' umwandeln." -warning.config.type.vector3f: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Kann '' nicht in den Vector3f-Typ für Option '' umwandeln." -warning.config.type.boolean: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Kann '' nicht in den Booleschen Typ für Option '' umwandeln." -warning.config.type.snbt.invalid_syntax: "Problem in Datei gefunden - Die Konfiguration '' verwendet einen ungültigen SNBT-Syntax ''." -warning.config.number.missing_type: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'type'-Argument für das Zahlenargument." -warning.config.number.invalid_type: "Problem in Datei gefunden - Die Konfiguration '' verwendet einen ungültigen Zahlargumenttyp ''." -warning.config.number.missing_argument: "Problem in Datei gefunden - Der Konfiguration '' fehlt das Argument für 'number'." -warning.config.number.invalid_format: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges Zahlenformat ''." -warning.config.number.fixed.missing_value: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'value'-Argument für die 'constant'-Zahl." -warning.config.number.fixed.invalid_value: "Problem in Datei gefunden - Die Konfiguration '' verwendet das ungültige 'value'-Argument '' für die 'constant'-Zahl." -warning.config.number.expression.missing_expression: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'expression'-Argument für die 'expression'-Zahl." -warning.config.number.uniform.missing_min: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'min'-Argument für die 'uniform'-Zahl." -warning.config.number.uniform.missing_max: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'max'-Argument für die 'uniform'-Zahl." -warning.config.condition.all_of.missing_terms: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'terms'-Argument für die 'all_of'-Bedingung." -warning.config.condition.all_of.invalid_terms_type: "Problem in Datei gefunden - Die Konfiguration '' hat eine falsch konfigurierte 'all_of'-Bedingung, 'terms' sollte eine Kartenliste sein, aktueller Typ: ''." -warning.config.condition.any_of.missing_terms: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'terms'-Argument für die 'any_of'-Bedingung." -warning.config.condition.any_of.invalid_terms_type: "Problem in Datei gefunden - Die Konfiguration '' hat eine falsch konfigurierte 'any_of'-Bedingung, 'terms' sollte eine Kartenliste sein, aktueller Typ: ''." -warning.config.condition.inverted.missing_term: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'term'-Argument für die 'inverted'-Bedingung." -warning.config.condition.inverted.invalid_term_type: "Problem in Datei gefunden - Die Konfiguration '' hat eine falsch konfigurierte 'inverted'-Bedingung, 'term' sollte ein Konfigurationsabschnitt sein, aktueller Typ: ''." -warning.config.condition.enchantment.missing_predicate: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'predicate'-Argument für die 'enchantment'-Bedingung." -warning.config.condition.enchantment.invalid_predicate: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges Verzauberungs-'predicate'-Argument ''." -warning.config.condition.match_block_property.missing_properties: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'properties'-Argument für die 'match_block_property'-Bedingung." -warning.config.condition.match_item.missing_id: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'id'-Argument für die 'match_item'-Bedingung." -warning.config.condition.table_bonus.missing_enchantment: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'enchantment'-Argument für die 'table_bonus'-Bedingung." -warning.config.condition.table_bonus.missing_chances: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'chances'-Argument für die 'table_bonus'-Bedingung." -warning.config.condition.permission.missing_permission: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'permission'-Argument für die 'permission'-Bedingung." -warning.config.condition.string_equals.missing_value1: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'value1'-Argument für die 'string_equals'-Bedingung." -warning.config.condition.string_equals.missing_value2: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'value2'-Argument für die 'string_equals'-Bedingung." -warning.config.condition.string_contains.missing_value1: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'value1'-Argument für die 'string_contains'-Bedingung." -warning.config.condition.string_contains.missing_value2: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'value2'-Argument für die 'string_contains'-Bedingung." -warning.config.condition.string_regex.missing_value: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'value'-Argument für die 'string_regex'-Bedingung." -warning.config.condition.string_regex.missing_regex: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'regex'-Argument für die 'string_regex'-Bedingung." -warning.config.condition.expression.missing_expression: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'expression'-Argument für die 'expression'-Bedingung." -warning.config.condition.is_null.missing_argument: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'argument'-Argument für die 'is_null'-Bedingung." -warning.config.condition.hand.missing_hand: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'hand'-Argument für die 'hand'-Bedingung." -warning.config.condition.hand.invalid_hand: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges 'hand'-Argument '' für die 'hand'-Bedingung. Erlaubte Handtypen: []" -warning.config.condition.on_cooldown.missing_id: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'id'-Argument für die 'on_cooldown'-Bedingung." -warning.config.structure.not_section: "Problem in Datei gefunden - Die Konfiguration '' wird als Konfigurationsabschnitt erwartet, ist aber tatsächlich ein(e) ''." -warning.config.image.duplicate: "Problem in Datei gefunden - Dupliziertes Bild ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.image.missing_height: "Problem in Datei gefunden - Dem Bild '' fehlt das erforderliche 'height'-Argument." -warning.config.image.height_ascent_conflict: "Problem in Datei gefunden - Das Bild '' verletzt die Bitmap-Bildregel: 'height'-Argument '' sollte nicht niedriger als 'ascent'-Argument '' sein." -warning.config.image.missing_file: "Problem in Datei gefunden - Dem Bild '' fehlt das erforderliche 'file'-Argument." -warning.config.image.invalid_file_chars: "Problem in Datei gefunden - Das Bild '' hat ein 'file'-Argument '', das illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.image.invalid_font_chars: "Problem in Datei gefunden - Das Bild '' hat ein 'font'-Argument '', das illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.image.missing_char: "Problem in Datei gefunden - Dem Bild '' fehlt das erforderliche 'char'-Argument." -warning.config.image.codepoint_conflict: "Problem in Datei gefunden - Das Bild '' verwendet ein Zeichen '()' in Schriftart , das von einem anderen Bild '' verwendet wurde." -warning.config.image.invalid_codepoint_grid: "Problem in Datei gefunden - Bild '' hat ein ungültiges 'chars'-Codepunktgitter." -warning.config.image.invalid_char: "Problem in Datei gefunden - Bild '' hat einen Zeichenparameter, der kombinierende Zeichen enthält, was zu einer Bildaufteilung führen kann." -warning.config.image.invalid_hex_value: "Problem in Datei gefunden - Das Bild '' verwendet ein Unicode-Zeichen '', das kein gültiger Hexadezimalwert (Basis 16) ist." -warning.config.recipe.duplicate: "Problem in Datei gefunden - Dupliziertes Rezept ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.recipe.missing_type: "Problem in Datei gefunden - Dem Rezept '' fehlt das erforderliche 'type'-Argument." -warning.config.recipe.invalid_type: "Problem in Datei gefunden - Das Rezept '' verwendet einen ungültigen Rezepttyp ''." -warning.config.recipe.invalid_ingredient: "Issue found in file - The recipe '' is using an invalid ingredient ''." -warning.config.recipe.invalid_result: "Issue found in file - The recipe '' is using an invalid result ''." -warning.config.recipe.missing_ingredient: "Problem in Datei gefunden - Dem Kochrezept '' fehlt das erforderliche 'ingredient'-Argument." -warning.config.recipe.missing_result: "Problem in Datei gefunden - Dem Rezept '' fehlt das erforderliche 'result'-Argument." -warning.config.recipe.result.missing_id: "Problem in Datei gefunden - Dem Rezept '' fehlt das erforderliche Argument 'id' für das Rezeptresultat." -warning.config.recipe.crafting.invalid_category: "Problem in Datei gefunden - Das Herstellungsrezept '' verwendet eine ungültige Kategorie ''. Erlaubte Kategorien: []." -warning.config.recipe.cooking.invalid_category: "Problem in Datei gefunden - Das Kochrezept '' verwendet eine ungültige Kategorie ''. Erlaubte Kategorien: []." -warning.config.recipe.shaped.missing_pattern: "Problem in Datei gefunden - Dem geformten Rezept '' fehlt das erforderliche Argument 'pattern'." -warning.config.recipe.shaped.invalid_pattern: "Problem in Datei gefunden - Das geformte Rezept '' verwendet ein ungültiges Muster ''." -warning.config.recipe.shaped.invalid_symbol: "Problem in Datei gefunden - Das geformte Rezept '' verwendet ein ungültiges Symbol '' im Muster." -warning.config.recipe.smithing_transform.post_processor.missing_type: "Problem in Datei gefunden - Dem Schmiedetransformationsrezept '' fehlt das erforderliche Argument 'type' für einen der Post-Prozessoren." -warning.config.recipe.smithing_transform.post_processor.invalid_type: "Problem in Datei gefunden - Das Schmiedetransformationsrezept '' verwendet einen ungültigen Post-Prozessor-Typ ''." -warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components: "Problem in Datei gefunden - Dem Schmiedetransformationsrezept '' fehlt das erforderliche Argument 'components' für die Post-Prozessoren 'keep_components'." -warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "Problem in Datei gefunden - Dem Schmiedetransformationsrezept '' fehlt das erforderliche Argument 'tags' für die Post-Prozessoren 'keep_tags'." -warning.config.i18n.unknown_locale: "Problem in Datei gefunden - Unbekanntes Gebietsschema ''." -warning.config.template.duplicate: "Problem in Datei gefunden - Dupliziertes Template ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.template.invalid: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges Template ''." -warning.config.template.argument.self_increase_int.invalid_range: "Problem in Datei gefunden - Das Template '' verwendet einen 'from'-Wert '', der größer ist als der 'to'-Wert '' im 'self_increase_int'-Argument." -warning.config.template.argument.list.invalid_type: "Problem in Datei gefunden - Das Template '' verwendet ein 'list'-Argument, das eine 'List' als Argument erwartet, während das Eingabeargument ein(e) '' ist." -warning.config.template.argument.default_value.invalid_syntax: "Problem in Datei gefunden - Das Template '' verwendet einen ungültigen Standardwert '' für das Argument ''." -warning.config.vanilla_loot.missing_type: "Problem in Datei gefunden - Dem Vanilla-Beute '' fehlt das erforderliche 'type'-Argument." -warning.config.vanilla_loot.invalid_type: "Problem in Datei gefunden - Der Vanilla-Beute '' verwendet einen ungültigen Typ ''. Erlaubte Typen: []." -warning.config.vanilla_loot.block.invalid_target: "Problem in Datei gefunden - Ungültiges Blockziel '' in Vanilla-Beute ''." -warning.config.sound.duplicate: "Problem in Datei gefunden - Duplizierter Sound ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.sound.missing_sounds: "Problem in Datei gefunden - Dem Sound '' fehlt das erforderliche 'sounds'-Argument." -warning.config.sound.missing_name: "Problem in Datei gefunden - Dem Sound '' fehlt das erforderliche 'name'-Argument." -warning.config.jukebox_song.duplicate: "Problem in Datei gefunden - Dupliziertes Jukebox-Lied ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.jukebox_song.missing_sound: "Problem in Datei gefunden - Dem Jukebox-Lied '' fehlt das erforderliche 'sound'-Argument." -warning.config.furniture.duplicate: "Problem in Datei gefunden - Dupliziertes Möbelstück ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.furniture.missing_placement: "Problem in Datei gefunden - Dem Möbelstück '' fehlt das erforderliche 'placement'-Argument." -warning.config.furniture.element.missing_item: "Problem in Datei gefunden - Dem Möbelstück '' fehlt das erforderliche 'item'-Argument für eines seiner Elemente." -warning.config.furniture.settings.unknown: "Problem in Datei gefunden - Das Möbelstück '' verwendet einen unbekannten Einstellungstyp ''." -warning.config.furniture.hitbox.invalid_type: "Problem in Datei gefunden - Das Möbelstück '' verwendet einen ungültigen Hitbox-Typ ''." -warning.config.furniture.hitbox.custom.invalid_entity: "Problem in Datei gefunden - Das Möbelstück '' verwendet eine benutzerdefinierte Hitbox mit ungültigem Entitätstyp ''." -warning.config.item.duplicate: "Problem in Datei gefunden - Dupliziertes Item ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.item.settings.unknown: "Problem in Datei gefunden - Das Item '' verwendet einen unbekannten Einstellungstyp ''." -warning.config.item.settings.invulnerable.invalid_damage_source: "Problem in Datei gefunden - Das Item '' verwendet eine unbekannte Schadensquelle ''. Erlaubte Quellen: []." -warning.config.item.settings.equippable.missing_slot: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'slot'-Argument für die 'equippable'-Einstellung." -warning.config.item.missing_material: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'material'-Argument." -warning.config.item.invalid_material: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Materialtyp ''." -warning.config.item.invalid_custom_model_data: "Problem in Datei gefunden - Das Item '' verwendet negative benutzerdefinierte Modelldaten '', die ungültig sind." -warning.config.item.bad_custom_model_data: "Problem in Datei gefunden - Das Item '' verwendet benutzerdefinierte Modelldaten '', die zu groß sind. Es wird empfohlen, einen Wert unter 16.777.216 zu verwenden." -warning.config.item.custom_model_data_conflict: "Problem in Datei gefunden - Das Item '' verwendet benutzerdefinierte Modelldaten '', die bereits von Item '' belegt sind." -warning.config.item.invalid_component: "Problem in Datei gefunden - Das Item '' verwendet einen nicht existierenden Komponententyp ''." -warning.config.item.missing_model_id: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'custom-model-data' oder 'item-model'-Argument." -warning.config.item.missing_model: "Problem in Datei gefunden - Das Item '' fehlt der erforderliche 'model'-Abschnitt für die Unterstützung von 1.21.4+-Ressourcenpaketen." -warning.config.item.behavior.missing_type: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'type'-Argument für sein Itemsverhalten." -warning.config.item.behavior.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Itemsverhaltenstyp ''." -warning.config.item.behavior.block.missing_block: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'block'-Argument für das 'block_item'-Verhalten." -warning.config.item.behavior.furniture.missing_furniture: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'furniture'-Argument für das 'furniture_item'-Verhalten." -warning.config.item.behavior.liquid_collision.missing_block: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'block'-Argument für das 'liquid_collision_block_item'-Verhalten." -warning.config.item.legacy_model.missing_path: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'path'-Argument für das Legacy-Modell." -warning.config.item.legacy_model.overrides.missing_path: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'path'-Argument für Legacy-Modell-Überschreibungen." -warning.config.item.legacy_model.overrides.missing_predicate: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'predicate'-Argument für Legacy-Modell-Überschreibungen." -warning.config.item.legacy_model.cannot_convert: "Problem in Datei gefunden - Kann 1.21.4+-Gegenstände für Item '' nicht in das Legacy-Format konvertieren. Bitte erstellen Sie den Abschnitt 'legacy-model' für diesen Item manuell." -warning.config.item.model.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Modelltyp ''." -warning.config.item.model.tint.missing_type: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'type'-Argument für die Tönung." -warning.config.item.model.tint.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Tönungstyp ''." -warning.config.item.model.tint.constant.missing_value: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'value'-Argument für die konstante Tönung." -warning.config.item.model.tint.grass.invalid_temp: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Temperatur '' für die Grastönung, die zwischen 0 und 1 liegen sollte." -warning.config.item.model.tint.grass.invalid_downfall: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Niederschlag '' für die Grastönung, der zwischen 0 und 1 liegen sollte." -warning.config.item.model.tint.invalid_value: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Tönung ''." -warning.config.item.model.base.missing_path: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'path'-Argument für das Modell 'minecraft:model'." -warning.config.item.model.base.invalid_path: "Problem in Datei gefunden - Das Item '' hat ein ungültiges 'path'-Argument '' für das Modell 'minecraft:model', das illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.item.model.condition.missing_property: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'property'-Argument für das Modell 'minecraft:condition'." -warning.config.item.model.condition.invalid_property: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Eigenschaft '' für das Modell 'minecraft:condition'." -warning.config.item.model.condition.missing_on_true: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'on-true'-Argument für das Modell 'minecraft:condition'." -warning.config.item.model.condition.missing_on_false: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'on-false'-Argument für das Modell 'minecraft:condition'." -warning.config.item.model.condition.keybind.missing_keybind: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'keybind'-Argument für die Eigenschaft 'minecraft:keybind_down'." -warning.config.item.model.condition.component.missing_predicate: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'predicate'-Argument für die Eigenschaft 'minecraft:has_component'." -warning.config.item.model.condition.component.missing_value: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'value'-Argument für die Eigenschaft 'minecraft:has_component'." -warning.config.item.model.condition.has_component.missing_component: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'component'-Argument für die Eigenschaft 'minecraft:has_component'." -warning.config.item.model.composite.missing_models: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'models'-Argument für das Modell 'minecraft:composite'." -warning.config.item.model.range_dispatch.missing_property: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'property'-Argument für das Modell 'minecraft:range_dispatch'." -warning.config.item.model.range_dispatch.invalid_property: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Eigenschaft '' für das Modell 'minecraft:range_dispatch'." -warning.config.item.model.range_dispatch.missing_entries: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'entries'-Argument für das Modell 'minecraft:composite'." -warning.config.item.model.range_dispatch.entry.missing_model: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'model'-Argument für einen der Einträge im Modell 'minecraft:composite'." -warning.config.item.model.range_dispatch.compass.missing_target: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'target'-Argument für die Eigenschaft 'minecraft:compass'." -warning.config.item.model.range_dispatch.time.missing_source: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'source'-Argument für die Eigenschaft 'minecraft:time'." -warning.config.item.model.select.missing_property: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'property'-Argument für das Modell 'minecraft:select'." -warning.config.item.model.select.invalid_property: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Eigenschaft '' für das Modell 'minecraft:select'." -warning.config.item.model.select.missing_cases: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'cases'-Argument für das Modell 'minecraft:select'." -warning.config.item.model.select.case.missing_when: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'when'-Argument für einen der Fälle im Modell 'minecraft:select'." -warning.config.item.model.select.case.missing_model: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'model'-Argument für einen der Fälle im Modell 'minecraft:select'." -warning.config.item.model.select.component.missing_component: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'component'-Argument für die Eigenschaft 'minecraft:component'." -warning.config.item.model.select.block_state.missing_property: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'block-state-property'-Argument für die Eigenschaft 'minecraft:block_state'." -warning.config.item.model.select.local_time.missing_pattern: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'pattern'-Argument für die Eigenschaft 'minecraft:local_time'." -warning.config.item.model.special.missing_type: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'type'-Argument für das Modell 'minecraft:special'." -warning.config.item.model.special.missing_path: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'path'-Argument für das Modell 'minecraft:special'." -warning.config.item.model.special.invalid_path: "Problem in Datei gefunden - Das Item '' hat ein ungültiges 'path'-Argument '' für das Modell 'minecraft:special', das illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.item.model.special.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Typ '' für das Modell 'minecraft:special'." -warning.config.item.model.special.banner.missing_color: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'color'-Argument für das Spezialmodell 'minecraft:banner'." -warning.config.item.model.special.bed.missing_texture: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'texture'-Argument für das Spezialmodell 'minecraft:bed'." -warning.config.item.model.special.sign.missing_wood_type: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'wood-type'-Argument für das Spezialmodell 'minecraft:hanging_sign'/'minecraft:standing_sign'." -warning.config.item.model.special.sign.missing_texture: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'texture'-Argument für das Spezialmodell 'minecraft:hanging_sign'/'minecraft:standing_sign'." -warning.config.item.model.special.chest.missing_texture: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'texture'-Argument für das Spezialmodell 'minecraft:chest'." -warning.config.item.model.special.chest.invalid_openness: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen 'openness'-Wert '' für das Spezialmodell 'minecraft:chest'. Gültiger Bereich '0~1.'" -warning.config.item.model.special.shulker_box.missing_texture: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'texture'-Argument für das Spezialmodell 'minecraft:shulker_box'." -warning.config.item.model.special.shulker_box.invalid_openness: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen 'openness'-Wert '' für das Spezialmodell 'minecraft:shulker_box'. Gültiger Bereich '0~1.'" -warning.config.item.model.special.head.missing_kind: "Problem in Datei gefunden - Das Item '' fehlt das erforderliche 'kind'-Argument für das Spezialmodell 'minecraft:head'." -warning.config.block.duplicate: "Problem in Datei gefunden - Duplizierter Block ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.block.missing_state: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'state'-Argument." -warning.config.block.state.property.missing_type: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'type'-Argument für die Eigenschaft ''." -warning.config.block.state.property.invalid_type: "Problem in Datei gefunden - Der Block '' verwendet das ungültige 'type'-Argument '' für die Eigenschaft ''." -warning.config.block.state.property.integer.invalid_range: "Problem in Datei gefunden - Der Block '' verwendet das ungültige 'range'-Argument '' für die Integer-Eigenschaft ''. Korrekte Syntax: 1~2." -warning.config.block.state.property.invalid_format: "Problem in Datei gefunden - Der Block '' verwendet ein ungültiges Blockzustandsformat ''." -warning.config.block.state.missing_real_id: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'id'-Argument für 'state'. 'id' ist die serverseitige Block-ID, die für jeden Blockzustandstyp eindeutig ist. Wenn Sie einen serverseitigen Block mit 'note_block' und ID 30 erstellen, wäre die echte Block-ID 'craftengine:note_block_30'." -warning.config.block.state.missing_state: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'state'-Argument für 'state'." -warning.config.block.state.missing_properties: "Problem in Datei gefunden - Dem Block '' fehlt der erforderliche 'properties'-Abschnitt für 'states'." -warning.config.block.state.missing_appearances: "Problem in Datei gefunden - Dem Block '' fehlt der erforderliche 'appearances'-Abschnitt für 'states'." -warning.config.block.state.missing_variants: "Problem in Datei gefunden - Dem Block '' fehlt der erforderliche 'variants'-Abschnitt für 'states'." -warning.config.block.state.variant.missing_appearance: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'appearance'-Argument für Variante ''." -warning.config.block.state.variant.invalid_appearance: "Problem in Datei gefunden - Der Block '' hat einen Fehler, dass die Variante '' ein nicht existierendes Erscheinungsbild '' verwendet." -warning.config.block.state.invalid_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen ungültigen Vanilla-Blockzustand ''." -warning.config.block.state.unavailable_vanilla: "Problem in Datei gefunden - Der Block '' verwendet einen nicht verfügbaren Vanilla-Blockzustand ''. Bitte geben Sie diesen Zustand in mappings.yml frei." -warning.config.block.state.invalid_vanilla_id: "Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Blockzustand '', der den verfügbaren Slot-Bereich '0~' überschreitet." -warning.config.block.state.conflict: "Problem in Datei gefunden - Der Block '' verwendet einen Vanilla-Blockzustand '', der bereits von '' belegt ist." -warning.config.block.state.bind_failed: "Problem in Datei gefunden - Der Block '' konnte den echten Blockzustand für '' nicht binden, da der Zustand von '' belegt ist." -warning.config.block.state.invalid_real_id: "Problem in Datei gefunden - Der Block '' verwendet einen echten Blockzustand '', der den verfügbaren Slot-Bereich '0~' überschreitet. Erwägen Sie, weitere echte Zustände in 'additional-real-blocks.yml' hinzuzufügen, wenn die Slots aufgebraucht sind." -warning.config.block.state.model.missing_path: "Problem in Datei gefunden - Dem 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 illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.block.settings.unknown: "Problem in Datei gefunden - Der Block '' verwendet einen unbekannten Einstellungstyp ''." -warning.config.block.behavior.missing_type: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'type'-Argument für sein Blockverhalten." -warning.config.block.behavior.invalid_type: "Problem in Datei gefunden - Der Block '' verwendet einen ungültigen Blockverhaltenstyp ''." -warning.config.block.behavior.concrete.missing_solid: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'solid-block'-Option für das 'concrete_block'-Verhalten." -warning.config.block.behavior.crop.missing_age: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'age'-Eigenschaft für das 'crop_block'-Verhalten." -warning.config.block.behavior.sugar_cane.missing_age: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'age'-Eigenschaft für das 'sugar_cane_block'-Verhalten." -warning.config.block.behavior.leaves.missing_persistent: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'persistent'-Eigenschaft für das 'leaves_block'-Verhalten." -warning.config.block.behavior.leaves.missing_distance: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'distance'-Eigenschaft für das 'leaves_block'-Verhalten." -warning.config.block.behavior.lamp.missing_lit: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'lit'-Eigenschaft für das 'lamp_block'-Verhalten." -warning.config.block.behavior.sapling.missing_stage: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'stage'-Eigenschaft für das 'sapling_block'-Verhalten." -warning.config.block.behavior.sapling.missing_feature: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'feature'-Argument für das 'sapling_block'-Verhalten." -warning.config.block.behavior.strippable.missing_stripped: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'stripped'-Argument für das 'strippable_block'-Verhalten." -warning.config.block.behavior.door.missing_half: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'half'-Eigenschaft für das 'door_block'-Verhalten." -warning.config.block.behavior.door.missing_facing: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'facing'-Eigenschaft für das 'door_block'-Verhalten." -warning.config.block.behavior.door.missing_hinge: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'hinge'-Eigenschaft für das 'door_block'-Verhalten." -warning.config.block.behavior.door.missing_open: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'open'-Eigenschaft für das 'door_block'-Verhalten." -warning.config.block.behavior.door.missing_powered: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'powered'-Eigenschaft für das 'door_block'-Verhalten." -warning.config.block.behavior.trapdoor.missing_half: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'half'-Eigenschaft für das 'trapdoor_block'-Verhalten." -warning.config.block.behavior.trapdoor.missing_facing: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'facing'-Eigenschaft für das 'trapdoor_block'-Verhalten." -warning.config.block.behavior.trapdoor.missing_open: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'open'-Eigenschaft für das 'trapdoor_block'-Verhalten." -warning.config.block.behavior.trapdoor.missing_powered: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'powered'-Eigenschaft für das 'trapdoor_block'-Verhalten." -warning.config.block.behavior.stackable.missing_property: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche ''-Eigenschaft für das 'stackable_block'-Verhalten." -warning.config.block.behavior.stackable.missing_items: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'items'-Argument für das 'stackable_block'-Verhalten." -warning.config.block.behavior.fence_gate.missing_facing: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'facing'-Argument für das 'fence_gate_block'-Verhalten." -warning.config.block.behavior.fence_gate.missing_in_wall: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'in_wall'-Argument für das 'fence_gate_block'-Verhalten." -warning.config.block.behavior.fence_gate.missing_open: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'powered'-Argument für das 'fence_gate_block'-Verhalten." -warning.config.block.behavior.fence_gate.missing_powered: "Problem in Datei gefunden - Dem Block '' fehlt das erforderliche 'open'-Argument für das 'fence_gate_block'-Verhalten." -warning.config.block.behavior.trapdoor.missing_type: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'type'-Eigenschaft für das 'slab_block'-Verhalten." -warning.config.block.behavior.stairs.missing_facing: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'facing'-Eigenschaft für das 'stairs_block'-Verhalten." -warning.config.block.behavior.stairs.missing_half: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'half'-Eigenschaft für das 'stairs_block'-Verhalten." -warning.config.block.behavior.stairs.missing_shape: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'shape'-Eigenschaft für das 'stairs_block'-Verhalten." -warning.config.block.behavior.pressure_plate.missing_powered: "Problem in Datei gefunden - Dem Block '' fehlt die erforderliche 'powered'-Eigenschaft für das 'pressure_plate_block'-Verhalten." -warning.config.model.generation.missing_parent: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'parent'-Argument im Abschnitt 'generation'." -warning.config.model.generation.invalid_display_position: "Problem in Datei gefunden - Die Konfiguration '' verwendet eine ungültige Anzeigeposition '' im Abschnitt 'generation.display'. Erlaubte Anzeigepositionen: []" -warning.config.model.generation.invalid_gui_light: "Problem in Datei gefunden - Die Konfiguration '' verwendet eine ungültige GUI-Lichtoption '' im Abschnitt 'generation'. Erlaubte GUI-Lichtoptionen: []" -warning.config.model.generation.conflict: "Problem in Datei gefunden - Fehler beim Generieren des Modells für '', da zwei oder mehr Konfigurationen versuchen, verschiedene JSON-Modelle mit demselben Pfad zu generieren: ''." -warning.config.model.generation.texture.invalid: "Problem in Datei gefunden - Die Konfiguration '' hat eine Textur '' mit Pfad '', die illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.model.generation.parent.invalid: "Problem in Datei gefunden - Die Konfiguration '' hat ein 'parent'-Argument '', das illegale Zeichen enthält. Bitte lesen Sie https://minecraft.wiki/w/Resource_location#Legal_characters." -warning.config.emoji.missing_keywords: "Problem in Datei gefunden - Dem Emoji '' fehlt das erforderliche 'keywords'-Argument." -warning.config.emoji.duplicate: "Problem in Datei gefunden - Dupliziertes Emoji ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +command.search_recipe.no_item: "Bitte halte ein Item in der Hand, bevor du diesen Befehl ausführst" +command.search_usage.no_item: "Bitte halte ein Item in der Hand, bevor du diesen Befehl ausführst" +command.totem_animation.failure.not_totem: "Item '' ist nicht minecraft:totem_of_undying" +command.resource.enable.success: "Resource aktiviert. Führe /ce reload all aus, um die Änderungen zu übernehmen" +command.resource.enable.failure.unknown: "Unbekannte Resource " +command.resource.disable.success: "Resource deaktiviert. Führe /ce reload all aus, um die Änderungen zu übernehmen" +command.resource.disable.failure.unknown: "Unbekannte Resource " +command.resource.list: "Aktivierte Resources (): Deaktivierte Resources (): " +command.upload.failure.not_supported: "Die aktuelle Hosting-Methode '' unterstützt das Hochladen von Resource Packs nicht." +command.upload.on_progress: "Upload-Fortschritt gestartet. Überprüfe die Konsole für weitere Informationen." +command.send_resource_pack.success.single: "Resource Pack an gesendet." +command.send_resource_pack.success.multiple: "Resource Packs an Spieler gesendet." +warning.config.pack.duplicated_files: "Doppelte Dateien gefunden. Bitte löse dies über den 'resource-pack.duplicated-files-handler' Abschnitt in der config.yml." +warning.config.yaml.duplicated_key: "Problem in Datei gefunden - Doppelter Key '' in Zeile gefunden, dies könnte zu unerwarteten Ergebnissen führen." +warning.config.yaml.inconsistent_value_type: "Problem in Datei gefunden - Doppelter Key '' in Zeile mit unterschiedlichen Wertetypen gefunden, dies könnte zu unerwarteten Ergebnissen führen." +warning.config.type.int: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Integer-Typ für Option '' umgewandelt werden." +warning.config.type.boolean: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Boolean-Typ für Option '' umgewandelt werden." +warning.config.type.float: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Float-Typ für Option '' umgewandelt werden." +warning.config.type.double: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Double-Typ für Option '' umgewandelt werden." +warning.config.type.quaternionf: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Quaternionf-Typ für Option '' umgewandelt werden." +warning.config.type.vector3f: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Vector3f-Typ für Option '' umgewandelt werden." +warning.config.type.map: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: '' kann nicht in den Map-Typ für Option '' umgewandelt werden." +warning.config.type.snbt.invalid_syntax: "Problem in Datei gefunden - Laden von '' fehlgeschlagen: Ungültige SNBT-Syntax ''." +warning.config.number.missing_type: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'type'-Argument für das Zahlenargument." +warning.config.number.invalid_type: "Problem in Datei gefunden - Die Config '' verwendet einen ungültigen Zahlenargument-Typ ''." +warning.config.number.missing_argument: "Problem in Datei gefunden - Bei der Config '' fehlt das Argument für 'number'." +warning.config.number.invalid_format: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges Zahlenformat ''." +warning.config.number.fixed.missing_value: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'value'-Argument für die 'constant' Zahl." +warning.config.number.fixed.invalid_value: "Problem in Datei gefunden - Die Config '' verwendet das ungültige 'value'-Argument '' für die 'constant' Zahl." +warning.config.number.expression.missing_expression: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'expression'-Argument für die 'expression' Zahl." +warning.config.number.uniform.missing_min: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'min'-Argument für die 'uniform' Zahl." +warning.config.number.uniform.missing_max: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'max'-Argument für die 'uniform' Zahl." +warning.config.condition.all_of.missing_terms: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'terms'-Argument für die 'all_of'-Bedingung." +warning.config.condition.all_of.invalid_terms_type: "Problem in Datei gefunden - Die Config '' hat eine falsch konfigurierte 'all_of'-Bedingung, 'terms' sollte eine Map-Liste sein, aktueller Typ: ''." +warning.config.condition.any_of.missing_terms: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'terms'-Argument für die 'any_of'-Bedingung." +warning.config.condition.any_of.invalid_terms_type: "Problem in Datei gefunden - Die Config '' hat eine falsch konfigurierte 'any_of'-Bedingung, 'terms' sollte eine Map-Liste sein, aktueller Typ: ''." +warning.config.condition.inverted.missing_term: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'term'-Argument für die 'inverted'-Bedingung." +warning.config.condition.inverted.invalid_term_type: "Problem in Datei gefunden - Die Config '' hat eine falsch konfigurierte 'inverted'-Bedingung, 'term' sollte ein Config-Abschnitt sein, aktueller Typ: ''." +warning.config.condition.enchantment.missing_predicate: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'predicate'-Argument für die 'enchantment'-Bedingung." +warning.config.condition.enchantment.invalid_predicate: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges 'predicate'-Argument '' für die 'enchantment'-Bedingung." +warning.config.condition.match_block_property.missing_properties: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'properties'-Argument für die 'match_block_property'-Bedingung." +warning.config.condition.match_item.missing_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'id'-Argument für die 'match_item'-Bedingung." +warning.config.condition.table_bonus.missing_enchantment: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'enchantment'-Argument für die 'table_bonus'-Bedingung." +warning.config.condition.table_bonus.missing_chances: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'chances'-Argument für die 'table_bonus'-Bedingung." +warning.config.condition.permission.missing_permission: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'permission'-Argument für die 'permission'-Bedingung." +warning.config.condition.string_equals.missing_value1: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'value1'-Argument für die 'string_equals'-Bedingung." +warning.config.condition.string_equals.missing_value2: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'value2'-Argument für die 'string_equals'-Bedingung." +warning.config.condition.string_contains.missing_value1: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'value1'-Argument für die 'string_contains'-Bedingung." +warning.config.condition.string_contains.missing_value2: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'value2'-Argument für die 'string_contains'-Bedingung." +warning.config.condition.string_regex.missing_value: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'value'-Argument für die 'string_regex'-Bedingung." +warning.config.condition.string_regex.missing_regex: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'regex'-Argument für die 'string_regex'-Bedingung." +warning.config.condition.expression.missing_expression: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'expression'-Argument für die 'expression'-Bedingung." +warning.config.condition.is_null.missing_argument: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'argument'-Argument für die 'is_null'-Bedingung." +warning.config.condition.hand.missing_hand: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'hand'-Argument für die 'hand'-Bedingung." +warning.config.condition.hand.invalid_hand: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges 'hand'-Argument '' für die 'hand'-Bedingung. Erlaubte Hand-Typen: []" +warning.config.condition.on_cooldown.missing_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'id'-Argument für die 'on_cooldown'-Bedingung." +warning.config.structure.not_section: "Problem in Datei gefunden - Die Config '' wird als Config-Abschnitt erwartet, ist aber tatsächlich ein(e) ''." +warning.config.image.duplicate: "Problem in Datei gefunden - Doppeltes Image ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.image.missing_height: "Problem in Datei gefunden - Beim Image '' fehlt das erforderliche 'height'-Argument." +warning.config.image.height_ascent_conflict: "Problem in Datei gefunden - Das Image '' verletzt die Bitmap-Image-Regel: 'height'-Argument '' sollte nicht niedriger sein als das 'ascent'-Argument ''." +warning.config.image.missing_file: "Problem in Datei gefunden - Beim Image '' fehlt das erforderliche 'file'-Argument." +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.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." +warning.config.recipe.duplicate: "Problem in Datei gefunden - Doppeltes Recipe ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.recipe.missing_type: "Problem in Datei gefunden - Beim Recipe '' fehlt das erforderliche 'type'-Argument." +warning.config.recipe.invalid_type: "Problem in Datei gefunden - Das Recipe '' verwendet einen ungültigen Recipe-Typ ''." +warning.config.recipe.invalid_ingredient: "Problem in Datei gefunden - Das Recipe '' verwendet eine ungültige Zutat (ingredient) ''." +warning.config.recipe.invalid_result: "Problem in Datei gefunden - Das Recipe '' verwendet ein ungültiges Ergebnis (result) ''." +warning.config.recipe.missing_ingredient: "Problem in Datei gefunden - Beim Cooking-Recipe '' fehlt das erforderliche 'ingredient'-Argument." +warning.config.recipe.missing_result: "Problem in Datei gefunden - Beim Recipe '' fehlt das erforderliche 'result'-Argument." +warning.config.recipe.result.missing_id: "Problem in Datei gefunden - Beim Recipe '' fehlt das erforderliche Argument 'id' für das Recipe-Ergebnis." +warning.config.recipe.crafting.invalid_category: "Problem in Datei gefunden - Das Crafting-Recipe '' verwendet eine ungültige Kategorie ''. Erlaubte Kategorien: []." +warning.config.recipe.cooking.invalid_category: "Problem in Datei gefunden - Das Cooking-Recipe '' verwendet eine ungültige Kategorie ''. Erlaubte Kategorien: []." +warning.config.recipe.shaped.missing_pattern: "Problem in Datei gefunden - Beim Shaped-Recipe '' fehlt das erforderliche Argument 'pattern'." +warning.config.recipe.shaped.invalid_pattern: "Problem in Datei gefunden - Das Shaped-Recipe '' verwendet ein ungültiges Pattern ''." +warning.config.recipe.shaped.invalid_symbol: "Problem in Datei gefunden - Das Shaped-Recipe '' verwendet ein ungültiges Symbol '' im Pattern." +warning.config.recipe.smithing_transform.post_processor.missing_type: "Problem in Datei gefunden - Beim Smithing-Transform-Recipe '' fehlt das erforderliche Argument 'type' für einen der Post-Processors." +warning.config.recipe.smithing_transform.post_processor.invalid_type: "Problem in Datei gefunden - Das Smithing-Transform-Recipe '' verwendet einen ungültigen Post-Processor-Typ ''." +warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components: "Problem in Datei gefunden - Beim Smithing-Transform-Recipe '' fehlt das erforderliche Argument 'components' für Post-Processors 'keep_components'." +warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "Problem in Datei gefunden - Beim Smithing-Transform-Recipe '' fehlt das erforderliche Argument 'tags' für Post-Processors 'keep_tags'." +warning.config.recipe.smithing_transform.missing_base: "Problem in Datei gefunden - Beim Smithing-Transform-Recipe '' fehlt das erforderliche 'base'-Argument." +warning.config.recipe.smithing_trim.missing_base: "Problem in Datei gefunden - Beim Smithing-Trim-Recipe '' fehlt das erforderliche 'base'-Argument." +warning.config.recipe.smithing_trim.missing_template_type: "Problem in Datei gefunden - Beim Smithing-Trim-Recipe '' fehlt das erforderliche 'template-type'-Argument." +warning.config.recipe.smithing_trim.missing_addition: "Problem in Datei gefunden - Beim Smithing-Trim-Recipe '' fehlt das erforderliche 'addition'-Argument." +warning.config.recipe.smithing_trim.missing_pattern: "Problem in Datei gefunden - Beim Smithing-Trim-Recipe '' fehlt das erforderliche 'pattern'-Argument." +warning.config.recipe.brewing.missing_container: "Problem in Datei gefunden - Beim Brewing-Recipe '' fehlt das erforderliche 'container'-Argument." +warning.config.recipe.brewing.missing_ingredient: "Problem in Datei gefunden - Beim Brewing-Recipe '' fehlt das erforderliche 'ingredient'-Argument." +warning.config.recipe.result.post_processor.missing_type: "Problem in Datei gefunden - Beim Recipe '' fehlt das erforderliche 'type'-Argument für Result-Post-Processors." +warning.config.recipe.result.post_processor.invalid_type: "Problem in Datei gefunden - Das Recipe '' verwendet einen ungültigen Result-Post-Processor-Typ ''." +warning.config.i18n.unknown_locale: "Problem in Datei gefunden - Unbekannte Locale ''." +warning.config.template.duplicate: "Problem in Datei gefunden - Doppeltes Template ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.template.invalid: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges Template ''." +warning.config.template.argument.self_increase_int.invalid_range: "Problem in Datei gefunden - Das Template '' verwendet ein 'from' '', das größer ist als 'to' '' im 'self_increase_int'-Argument." +warning.config.template.argument.list.invalid_type: "Problem in Datei gefunden - Das Template '' verwendet ein 'list'-Argument, das eine 'List' als Argument erwartet, während das eingegebene Argument ein(e) '' ist." +warning.config.template.argument.missing_value: "Problem in Datei gefunden - Bei der Config '' fehlt das Template-Argument für ''. Bitte verwende die 'arguments'-Option zur Konfiguration oder setze einen Standardwert für diesen Parameter." +warning.config.vanilla_loot.missing_type: "Problem in Datei gefunden - Beim Vanilla-Loot '' fehlt das erforderliche 'type'-Argument." +warning.config.vanilla_loot.invalid_type: "Problem in Datei gefunden - Der Vanilla-Loot '' verwendet einen ungültigen Typ ''. Erlaubte Typen: []." +warning.config.vanilla_loot.block.invalid_target: "Problem in Datei gefunden - Ungültiges Block-Target '' im Vanilla-Loot ''." +warning.config.sound.duplicate: "Problem in Datei gefunden - Doppelter Sound ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.sound.missing_sounds: "Problem in Datei gefunden - Beim Sound '' fehlt das erforderliche 'sounds'-Argument." +warning.config.sound.missing_name: "Problem in Datei gefunden - Beim Sound '' fehlt das erforderliche 'name'-Argument." +warning.config.jukebox_song.duplicate: "Problem in Datei gefunden - Doppelter Jukebox-Song ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.jukebox_song.missing_sound: "Problem in Datei gefunden - Beim Jukebox-Song '' fehlt das erforderliche 'sound'-Argument." +warning.config.furniture.duplicate: "Problem in Datei gefunden - Doppeltes Furniture ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.furniture.missing_placement: "Problem in Datei gefunden - Beim Furniture '' fehlt das erforderliche 'placement'-Argument." +warning.config.furniture.element.missing_item: "Problem in Datei gefunden - Beim Furniture '' fehlt das erforderliche 'item'-Argument für eines seiner Elemente." +warning.config.furniture.settings.unknown: "Problem in Datei gefunden - Das Furniture '' verwendet einen unbekannten Einstellungs-Typ ''." +warning.config.furniture.hitbox.invalid_type: "Problem in Datei gefunden - Das Furniture '' verwendet einen ungültigen Hitbox-Typ ''." +warning.config.furniture.hitbox.custom.invalid_entity: "Problem in Datei gefunden - Das Furniture '' verwendet eine benutzerdefinierte Hitbox mit ungültigem Entity-Typ ''." +warning.config.item.duplicate: "Problem in Datei gefunden - Doppeltes Item ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.item.settings.unknown: "Problem in Datei gefunden - Das Item '' verwendet einen unbekannten Einstellungs-Typ ''." +warning.config.item.settings.invulnerable.invalid_damage_source: "Problem in Datei gefunden - Das Item '' verwendet eine unbekannte Schadensquelle (damage source) ''. Erlaubte Quellen: []." +warning.config.item.settings.equipment.missing_asset_id: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'asset-id'-Argument für 'equipment'-Einstellungen." +warning.config.item.settings.equipment.invalid_asset_id: "Problem in Datei gefunden - Das Item '' verwendet ein ungültiges 'asset-id'-Argument für 'equipment'-Einstellungen. Dies könnte daran liegen, dass du diese Equipment-Konfiguration nicht erstellt oder die Asset-ID falsch geschrieben hast." +warning.config.item.settings.projectile.missing_item: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'item'-Argument für 'projectile'-Einstellungen." +warning.config.item.data.attribute_modifiers.missing_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'type'-Argument für 'attribute-modifiers'-Daten." +warning.config.item.data.attribute_modifiers.missing_amount: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'amount'-Argument für 'attribute-modifiers'-Daten." +warning.config.item.data.attribute_modifiers.missing_operation: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'operation'-Argument für 'attribute-modifiers'-Daten." +warning.config.item.data.attribute_modifiers.display.missing_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'type'-Argument für 'attribute-modifiers' Display-Daten." +warning.config.item.data.attribute_modifiers.display.missing_value: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'value'-Argument für 'attribute-modifiers' Display-Daten." +warning.config.item.data.external.missing_source: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'source'-Argument für 'external'-Daten." +warning.config.item.data.external.missing_id: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'id'-Argument für 'external'-Daten." +warning.config.item.data.external.invalid_source: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Item-Quelle '' für 'external'-Daten." +warning.config.item.missing_material: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'material'-Argument." +warning.config.item.invalid_material: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Material-Typ ''." +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.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+." +warning.config.item.behavior.missing_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'type'-Argument für sein Item-Behavior." +warning.config.item.behavior.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Item-Behavior-Typ ''." +warning.config.item.behavior.block.missing_block: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'block'-Argument für das 'block_item'-Behavior." +warning.config.item.behavior.furniture.missing_furniture: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'furniture'-Argument für das 'furniture_item'-Behavior." +warning.config.item.behavior.liquid_collision.missing_block: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'block'-Argument für das 'liquid_collision_block_item'-Behavior." +warning.config.item.behavior.double_high.missing_block: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'block'-Argument für das 'double_high_block_item'-Behavior." +warning.config.item.legacy_model.missing_path: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'path'-Argument für legacy-model." +warning.config.item.legacy_model.overrides.missing_path: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'path'-Argument für legacy-model Overrides." +warning.config.item.legacy_model.overrides.missing_predicate: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'predicate'-Argument für legacy-model Overrides." +warning.config.item.legacy_model.cannot_convert: "Problem in Datei gefunden - Items ab 1.21.4+ können für Item '' nicht in das Legacy-Format konvertiert werden. Bitte erstelle manuell einen 'legacy-model'-Abschnitt für dieses Item." +warning.config.item.model.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Model-Typ ''." +warning.config.item.model.tint.missing_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'type'-Argument für Tint." +warning.config.item.model.tint.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Tint-Typ ''." +warning.config.item.model.tint.constant.missing_value: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'value'-Argument für den konstanten Tint." +warning.config.item.model.tint.grass.invalid_temp: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Temperatur '' für den Gras-Tint, die zwischen 0 und 1 liegen sollte." +warning.config.item.model.tint.grass.invalid_downfall: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Niederschlag (downfall) '' für den Gras-Tint, der zwischen 0 und 1 liegen sollte." +warning.config.item.model.tint.invalid_value: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Tint ''." +warning.config.item.model.base.missing_path: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'path'-Argument für das Model 'minecraft:model'." +warning.config.item.model.base.invalid_path: "Problem in Datei gefunden - Das Item '' hat ein ungültiges 'path'-Argument '' für das Model 'minecraft:model', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." +warning.config.item.model.condition.missing_property: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'property'-Argument für das Model 'minecraft:condition'." +warning.config.item.model.condition.invalid_property: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Property '' für das Model 'minecraft:condition'." +warning.config.item.model.condition.missing_on_true: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'on-true'-Argument für das Model 'minecraft:condition'." +warning.config.item.model.condition.missing_on_false: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'on-false'-Argument für das Model 'minecraft:condition'." +warning.config.item.model.condition.keybind.missing_keybind: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'keybind'-Argument für die Property 'minecraft:keybind_down'." +warning.config.item.model.condition.has_component.missing_component: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'component'-Argument für die Property 'minecraft:has_component'." +warning.config.item.model.condition.component.missing_predicate: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'predicate'-Argument für die Property 'minecraft:component'." +warning.config.item.model.condition.component.missing_value: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'value'-Argument für die Property 'minecraft:component'." +warning.config.item.model.composite.missing_models: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'models'-Argument für das 'minecraft:composite'-Model." +warning.config.item.model.range_dispatch.missing_property: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'property'-Argument für das Model 'minecraft:range_dispatch'." +warning.config.item.model.range_dispatch.invalid_property: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Property '' für das Model 'minecraft:range_dispatch'." +warning.config.item.model.range_dispatch.missing_entries: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'entries'-Argument für das Model 'minecraft:composite'." +warning.config.item.model.range_dispatch.entry.missing_model: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'model'-Argument für einen der Einträge im Model 'minecraft:composite'." +warning.config.item.model.range_dispatch.compass.missing_target: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'target'-Argument für die Property 'minecraft:compass'." +warning.config.item.model.range_dispatch.time.missing_source: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'source'-Argument für die Property 'minecraft:time'." +warning.config.item.model.select.missing_property: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'property'-Argument für das Model 'minecraft:select'." +warning.config.item.model.select.invalid_property: "Problem in Datei gefunden - Das Item '' verwendet eine ungültige Property '' für das Model 'minecraft:select'." +warning.config.item.model.select.missing_cases: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'cases'-Argument für das Model 'minecraft:select'." +warning.config.item.model.select.case.missing_when: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'when'-Argument für einen der Fälle im Model 'minecraft:select'." +warning.config.item.model.select.case.missing_model: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'model'-Argument für einen der Fälle im Model 'minecraft:select'." +warning.config.item.model.select.block_state.missing_property: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'block-state-property'-Argument für die Property 'minecraft:block_state'." +warning.config.item.model.select.local_time.missing_pattern: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'pattern'-Argument für die Property 'minecraft:local_time'." +warning.config.item.model.select.component.missing_component: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'component'-Argument für die Property 'minecraft:component'." +warning.config.item.model.special.missing_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'type'-Argument für das Model 'minecraft:special'." +warning.config.item.model.special.missing_path: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'path'-Argument für das Model 'minecraft:special'." +warning.config.item.model.special.invalid_path: "Problem in Datei gefunden - Das Item '' hat ein ungültiges 'path'-Argument '' für das Model 'minecraft:special', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." +warning.config.item.model.special.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen Typ '' für das Model 'minecraft:special'." +warning.config.item.model.special.banner.missing_color: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'color'-Argument für das Special-Model 'minecraft:banner'." +warning.config.item.model.special.bed.missing_texture: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'texture'-Argument für das Special-Model 'minecraft:bed'." +warning.config.item.model.special.sign.missing_wood_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'wood-type'-Argument für das Special-Model 'minecraft:hanging_sign'/'minecraft:standing_sign'." +warning.config.item.model.special.sign.missing_texture: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'texture'-Argument für das Special-Model 'minecraft:hanging_sign'/'minecraft:standing_sign'." +warning.config.item.model.special.chest.missing_texture: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'texture'-Argument für das Special-Model 'minecraft:chest'." +warning.config.item.model.special.chest.invalid_openness: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen 'openness'-Wert '' für das Special-Model 'minecraft:chest'. Gültiger Bereich '0~1.'" +warning.config.item.model.special.shulker_box.missing_texture: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'texture'-Argument für das Special-Model 'minecraft:shulker_box'." +warning.config.item.model.special.shulker_box.invalid_openness: "Problem in Datei gefunden - Das Item '' verwendet einen ungültigen 'openness'-Wert '' für das Special-Model 'minecraft:shulker_box'. Gültiger Bereich '0~1.'" +warning.config.item.model.special.head.missing_kind: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'kind'-Argument für das Special-Model 'minecraft:head'." +warning.config.item.updater.missing_type: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'type'-Argument für den Item-Updater." +warning.config.item.updater.invalid_type: "Problem in Datei gefunden - Das Item '' verwendet ein ungültiges 'type'-Argument '' für den Item-Updater." +warning.config.item.updater.transmute.missing_material: "Problem in Datei gefunden - Beim Item '' fehlt das erforderliche 'material'-Argument für den 'transmute'-Item-Updater." +warning.config.block.duplicate: "Problem in Datei gefunden - Doppelter Block ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.block.missing_state: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'state'-Argument." +warning.config.block.state.property.missing_type: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'type'-Argument für die Property ''." +warning.config.block.state.property.invalid_type: "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.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." +warning.config.block.settings.unknown: "Problem in Datei gefunden - Der Block '' verwendet einen unbekannten Einstellungs-Typ ''." +warning.config.block.behavior.missing_type: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'type'-Argument für sein Block-Behavior." +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.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." +warning.config.block.behavior.sapling.missing_stage: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'stage'-Property für das 'sapling_block'-Behavior." +warning.config.block.behavior.sapling.missing_feature: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'feature'-Argument für das 'sapling_block'-Behavior." +warning.config.block.behavior.strippable.missing_stripped: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'stripped'-Argument für das 'strippable_block'-Behavior." +warning.config.block.behavior.door.missing_half: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'half'-Property für das 'door_block'-Behavior." +warning.config.block.behavior.door.missing_facing: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'facing'-Property für das 'door_block'-Behavior." +warning.config.block.behavior.door.missing_hinge: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'hinge'-Property für das 'door_block'-Behavior." +warning.config.block.behavior.door.missing_open: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'open'-Property für das 'door_block'-Behavior." +warning.config.block.behavior.door.missing_powered: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'powered'-Property für das 'door_block'-Behavior." +warning.config.block.behavior.trapdoor.missing_half: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'half'-Property für das 'trapdoor_block'-Behavior." +warning.config.block.behavior.trapdoor.missing_facing: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'facing'-Property für das 'trapdoor_block'-Behavior." +warning.config.block.behavior.trapdoor.missing_open: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'open'-Property für das 'trapdoor_block'-Behavior." +warning.config.block.behavior.trapdoor.missing_powered: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'powered'-Property für das 'trapdoor_block'-Behavior." +warning.config.block.behavior.stackable.missing_property: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche ''-Property für das 'stackable_block'-Behavior." +warning.config.block.behavior.stackable.missing_items: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'items'-Argument für das 'stackable_block'-Behavior." +warning.config.block.behavior.fence_gate.missing_facing: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'facing'-Argument für das 'fence_gate_block'-Behavior." +warning.config.block.behavior.fence_gate.missing_in_wall: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'in_wall'-Argument für das 'fence_gate_block'-Behavior." +warning.config.block.behavior.fence_gate.missing_open: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'powered'-Argument für das 'fence_gate_block'-Behavior." +warning.config.block.behavior.fence_gate.missing_powered: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'open'-Argument für das 'fence_gate_block'-Behavior." +warning.config.block.behavior.trapdoor.missing_type: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'type'-Property für das 'slab_block'-Behavior." +warning.config.block.behavior.stairs.missing_facing: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'facing'-Property für das 'stairs_block'-Behavior." +warning.config.block.behavior.stairs.missing_half: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'half'-Property für das 'stairs_block'-Behavior." +warning.config.block.behavior.stairs.missing_shape: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'shape'-Property für das 'stairs_block'-Behavior." +warning.config.block.behavior.pressure_plate.missing_powered: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'powered'-Property für das 'pressure_plate_block'-Behavior." +warning.config.block.behavior.grass.missing_feature: "Problem in Datei gefunden - Beim Block '' fehlt das erforderliche 'feature'-Argument für das 'grass_block'-Behavior." +warning.config.block.behavior.double_high.missing_half: "Problem in Datei gefunden - Beim Block '' fehlt die erforderliche 'half'-Property für das 'double_block'-Behavior." +warning.config.model.generation.missing_parent: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'parent'-Argument im 'generation'-Abschnitt." +warning.config.model.generation.invalid_display_position: "Problem in Datei gefunden - Die Config '' verwendet eine ungültige Display-Position '' im 'generation.display'-Abschnitt. Erlaubte Display-Positionen: []" +warning.config.model.generation.invalid_gui_light: "Problem in Datei gefunden - Die Config '' verwendet eine ungültige gui-light Option '' im 'generation'-Abschnitt. Erlaubte gui-light Optionen: []" +warning.config.model.generation.conflict: "Problem in Datei gefunden - Generierung des Models für '' fehlgeschlagen, da zwei oder mehr Konfigurationen versuchen, verschiedene JSON-Models mit demselben Pfad zu generieren: ''." +warning.config.model.generation.texture.invalid: "Problem in Datei gefunden - Die Config '' hat eine Textur '' mit dem Pfad '', der ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." +warning.config.model.generation.parent.invalid: "Problem in Datei gefunden - Die Config '' hat ein parent-Argument '', das ungültige Zeichen enthält. Bitte lies https://minecraft.wiki/w/Resource_location#Legal_characters." +warning.config.emoji.missing_keywords: "Problem in Datei gefunden - Beim Emoji '' fehlt das erforderliche 'keywords'-Argument." +warning.config.emoji.duplicate: "Problem in Datei gefunden - Doppeltes Emoji ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." warning.config.emoji.invalid_image: "Problem in Datei gefunden - Das Emoji '' hat ein ungültiges 'image'-Argument ''." -warning.config.advancement.duplicate: "Problem in Datei gefunden - Duplizierter Fortschritt ''. Bitte überprüfen Sie, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.loot_table.missing_pools: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, der das erforderliche 'pools'-Argument fehlt." -warning.config.loot_table.invalid_pools_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, 'pools' sollte eine String-/Kartenliste sein, aktueller Typ: ''." -warning.config.loot_table.invalid_conditions_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, 'conditions' sollte eine Kartenliste sein, aktueller Typ: ''." -warning.config.loot_table.invalid_functions_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, 'functions' sollte eine Kartenliste sein, aktueller Typ: ''." -warning.config.loot_table.invalid_entries_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, 'entries' sollte eine Kartenliste sein, aktueller Typ: ''." -warning.config.loot_table.function.missing_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, einer der Funktionen fehlt das erforderliche 'type'-Argument." -warning.config.loot_table.function.invalid_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, einer der Funktionen verwendet einen ungültigen Funktionstyp ''." -warning.config.loot_table.function.apply_bonus.missing_enchantment: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, der Funktion 'apply_bonus' fehlt das erforderliche 'enchantment'-Argument." -warning.config.loot_table.function.apply_bonus.missing_formula: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, der Funktion 'apply_bonus' fehlt das erforderliche 'formula'-Argument." -warning.config.loot_table.function.drop_exp.missing_count: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, der Funktion 'drop_exp' fehlt das erforderliche 'count'-Argument." -warning.config.loot_table.function.set_count.missing_count: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, der Funktion 'set_count' fehlt das erforderliche 'count'-Argument." -warning.config.loot_table.entry.missing_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, einem der Einträge fehlt das erforderliche 'type'-Argument." -warning.config.loot_table.entry.invalid_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, einem der Einträge verwendet einen ungültigen Eintragstyp ''." -warning.config.loot_table.entry.exp.missing_count: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, dem Eintrag 'exp' fehlt das erforderliche 'count'-Argument." -warning.config.loot_table.entry.item.missing_item: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, dem Eintrag 'item' fehlt das erforderliche 'item'-Argument." -warning.config.loot_table.condition.missing_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, einer der Bedingungen fehlt das erforderliche 'type'-Argument." -warning.config.loot_table.condition.invalid_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Beutetabelle, einer der Bedingungen verwendet einen ungültigen Bedingungstyp ''." -warning.config.host.missing_type: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'type'-Argument für den Host." -warning.config.host.invalid_type: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Host-Typ '' ist ungültig. Bitte lesen Sie https://xiao-momi.github.io/craft-engine-wiki/getting_start/set_up_host." -warning.config.host.external.missing_url: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'url'-Argument für den externen Host." -warning.config.host.alist.missing_api_url: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'api-url'-Argument für den AList-Host." -warning.config.host.alist.missing_username: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'username'-Argument oder Umgebungsvariable 'CE_ALIST_USERNAME' für den AList-Host." -warning.config.host.alist.missing_password: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'password'-Argument oder Umgebungsvariable 'CE_ALIST_PASSWORD' für den AList-Host." -warning.config.host.alist.missing_upload_path: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für den AList-Host." -warning.config.host.dropbox.missing_app_key: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'app-key'-Argument oder Umgebungsvariable 'CE_DROPBOX_APP_KEY' für den Dropbox-Host." -warning.config.host.dropbox.missing_app_secret: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'app-secret'-Argument oder Umgebungsvariable 'CE_DROPBOX_APP_SECRET' für den Dropbox-Host." -warning.config.host.dropbox.missing_refresh_token: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'refresh-token'-Argument oder Umgebungsvariable 'CE_DROPBOX_REFRESH_TOKEN' für den Dropbox-Host." -warning.config.host.dropbox.missing_upload_path: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für den Dropbox-Host." -warning.config.host.lobfile.missing_api_key: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'api-key'-Argument für den Lobfile-Host." -warning.config.host.onedrive.missing_client_id: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'client-id'-Argument oder Umgebungsvariable 'CE_ONEDRIVE_CLIENT_ID' für den OneDrive-Host." -warning.config.host.onedrive.missing_client_secret: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'client-secret'-Argument oder Umgebungsvariable 'CE_ONEDRIVE_CLIENT_SECRET' für den OneDrive-Host." -warning.config.host.onedrive.missing_refresh_token: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'refresh-token'-Argument oder Umgebungsvariable 'CE_ONEDRIVE_REFRESH_TOKEN' für den OneDrive-Host." -warning.config.host.onedrive.missing_upload_path: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für den OneDrive-Host." -warning.config.host.s3.missing_endpoint: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'endpoint'-Argument für den S3-Host." -warning.config.host.s3.missing_bucket: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'bucket'-Argument für den S3-Host." -warning.config.host.s3.missing_access_key: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'access-key-id'-Argument oder Umgebungsvariable 'CE_S3_ACCESS_KEY_ID' für den S3-Host." -warning.config.host.s3.missing_secret: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'access-key-secret'-Argument oder Umgebungsvariable 'CE_S3_ACCESS_KEY_SECRET' für den S3-Host." -warning.config.host.s3.missing_upload_path: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für den S3-Host." -warning.config.host.self.missing_ip: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'ip'-Argument für den Selbst-Host." -warning.config.host.self.invalid_port: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Ungültiger Port '' für den Selbst-Host." -warning.config.host.self.invalid_url: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Ungültige URL '' für den Selbst-Host." -warning.config.host.gitlab.missing_url: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'gitlab-url'-Argument für den GitLab-Host." -warning.config.host.gitlab.missing_token: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'access-token'-Argument für den GitLab-Host." -warning.config.host.gitlab.missing_project: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'project-id'-Argument für den GitLab-Host." -warning.config.host.proxy.missing_host: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'host'-Argument für den Proxy." -warning.config.host.proxy.missing_port: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'port'-Argument für den Proxy." -warning.config.host.proxy.missing_scheme: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'scheme'-Argument für den Proxy." -warning.config.host.proxy.invalid: "Problem in config.yml im Abschnitt 'resource-pack.delivery.hosting' gefunden - Ungültiger Proxy ''." -warning.config.conflict_matcher.missing_type: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'type'-Argument für einen der Handler." -warning.config.conflict_matcher.invalid_type: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Einer der Begriffe verwendet den ungültigen Typ ''." -warning.config.conflict_matcher.exact.missing_path: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'path'-Argument für den 'exact'-Matcher." -warning.config.conflict_matcher.contains.missing_path: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'path'-Argument für den 'contains'-Matcher." -warning.config.conflict_matcher.filename.missing_name: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'path'-Argument für den 'filename'-Matcher." -warning.config.conflict_matcher.pattern.missing_pattern: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'pattern'-Argument für den 'pattern'-Matcher." -warning.config.conflict_matcher.parent_prefix.missing_prefix: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'prefix'-Argument für den 'parent_path_prefix'-Matcher." -warning.config.conflict_matcher.parent_suffix.missing_suffix: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'suffix'-Argument für den 'parent_path_suffix'-Matcher." -warning.config.conflict_matcher.inverted.missing_term: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'term'-Argument für den 'inverted'-Matcher." -warning.config.conflict_matcher.all_of.missing_terms: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'terms'-Argument für den 'all_of'-Matcher." -warning.config.conflict_matcher.any_of.missing_terms: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'terms'-Argument für den 'any_of'-Matcher." -warning.config.conflict_resolution.missing_type: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'type'-Argument für eine der Auflösungen." -warning.config.conflict_resolution.invalid_type: "Problem in config.yml im Abschnitt 'resource-pack.duplicated-files-handler' gefunden - Eine der Auflösungen verwendet den ungültigen Typ ''." -warning.config.event.missing_trigger: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'on'-Argument für Ereignisauslöser." -warning.config.event.invalid_trigger: "Problem in Datei gefunden - Die Konfiguration '' verwendet einen ungültigen Ereignisauslöser ''." -warning.config.event.condition.missing_type: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'type'-Argument für die Ereignisbedingung." -warning.config.event.condition.invalid_type: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges 'type'-Argument '' für die Ereignisbedingung." -warning.config.function.missing_type: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'type'-Argument für die Funktion." -warning.config.function.invalid_type: "Problem in Datei gefunden - Die Konfiguration '' verwendet einen ungültigen Funktionstyp ''." -warning.config.function.command.missing_command: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'command'-Argument für die 'command'-Funktion." -warning.config.function.actionbar.missing_actionbar: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'actionbar'-Argument für die 'actionbar'-Funktion." -warning.config.function.message.missing_message: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'message'-Argument für die 'message'-Funktion." -warning.config.function.open_window.missing_gui_type: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'gui-type'-Argument für die 'open_window'-Funktion." -warning.config.function.open_window.invalid_gui_type: "Problem in Datei gefunden - Die Konfiguration '' verwendet einen ungültigen GUI-Typ für die 'open_window'-Funktion. Erlaubte Typen: []." -warning.config.function.run.missing_functions: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'functions'-Argument für die 'run'-Funktion." -warning.config.function.place_block.missing_block_state: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'block-state'-Argument für die 'place_block'-Funktion." -warning.config.function.set_food.missing_food: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'food'-Argument für die 'set_food'-Funktion." -warning.config.function.set_saturation.missing_saturation: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'saturation'-Argument für die 'set_saturation'-Funktion." -warning.config.function.play_sound.missing_sound: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'sound'-Argument für die 'play_sound'-Funktion." -warning.config.function.particle.missing_particle: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'particle'-Argument für die 'particle'-Funktion." -warning.config.function.particle.missing_color: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'color'-Argument für die 'particle'-Funktion." -warning.config.function.particle.missing_from: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'from'-Argument für die 'particle'-Funktion." -warning.config.function.particle.missing_to: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'to'-Argument für die 'particle'-Funktion." -warning.config.function.particle.missing_item: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'item'-Argument für die 'particle'-Funktion." -warning.config.function.particle.missing_block_state: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'block-state'-Argument für die 'particle'-Funktion." -warning.config.function.leveler_exp.missing_count: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'count'-Argument für die 'leveler_exp'-Funktion." -warning.config.function.leveler_exp.missing_leveler: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'leveler'-Argument für die 'leveler_exp'-Funktion." -warning.config.function.leveler_exp.missing_plugin: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'plugin'-Argument für die 'leveler_exp'-Funktion." -warning.config.function.remove_potion_effect.missing_potion_effect: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'potion-effect'-Argument für die 'remove_potion_effect'-Funktion." -warning.config.function.potion_effect.missing_potion_effect: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'potion-effect'-Argument für die 'potion_effect'-Funktion." -warning.config.function.set_cooldown.missing_time: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'time'-Argument für die 'set_cooldown'-Funktion." -warning.config.function.set_cooldown.missing_id: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'id'-Argument für die 'set_cooldown'-Funktion." -warning.config.function.remove_cooldown.missing_id: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'id'-Argument für die 'remove_cooldown'-Funktion." -warning.config.selector.missing_type: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'type'-Argument für den Selektor." -warning.config.selector.invalid_type: "Problem in Datei gefunden - Die Konfiguration '' verwendet einen ungültigen Selektortyp ''." -warning.config.selector.invalid_target: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges Selektorziel ''." -warning.config.resource_pack.item_model.already_exist: "Fehler beim Generieren des Itemsmodells für '' da die Datei '' bereits existiert." -warning.config.resource_pack.model.generation.already_exist: "Fehler beim Generieren des Modells, da die Modelldatei '' bereits existiert." -warning.config.resource_pack.generation.missing_font_texture: "Schriftart '' fehlt die erforderliche Textur: ''" -warning.config.resource_pack.generation.missing_model_texture: "Modell '' fehlt Textur ''" -warning.config.resource_pack.generation.missing_item_model: "Item '' fehlt Modelldatei: ''" -warning.config.resource_pack.generation.missing_block_model: "Block '' fehlt Modelldatei: ''" -warning.config.resource_pack.generation.missing_parent_model: "Modell '' kann das Elternmodell nicht finden: ''" -warning.config.resource_pack.generation.malformatted_json: "Json-Datei '' ist fehlerhaft formatiert." -warning.config.resource_pack.invalid_overlay_format: "Problem in config.yml gefunden - Ungültiges Overlay-Format '' im Abschnitt 'resource-pack'. Unterstützte Overlays: []" -warning.config.type.map: "Fehler in der Datei - Das Laden von '' ist fehlgeschlagen: Kann den Typ '' nicht in den Typ 'Map' für die Option '' umwandeln." -warning.config.recipe.missing_pattern: "Fehler in der Datei - Dem geformten Rezept '' fehlt das erforderliche Argument 'pattern'." -warning.config.recipe.too_large_pattern: "Fehler in der Datei - Das geformte Rezept '' hat ein Muster mit mehr als 3 Zeilen oder Spalten." -warning.config.recipe.mismatched_shaped_pattern: "Fehler in der Datei - Das geformte Rezept '' hat ein fehlerhaftes Muster und Zutaten." -warning.config.recipe.mismatched_shulker_box: "Fehler in der Datei - Das Shulker-Box-Rezept '' hat ein fehlerhaftes Muster und Zutaten." -warning.config.recipe.mismatched_unshaped_pattern: "Fehler in der Datei - Das ungeformte Rezept '' hat ein fehlerhaftes Muster und Zutaten." -warning.config.recipe.missing_category: "Fehler in der Datei - Dem Rezept '' fehlt das erforderliche 'category'-Argument." -warning.config.recipe.missing_experience: "Fehler in der Datei - Dem Kochrezept '' fehlt das erforderliche 'experience'-Argument." -warning.config.recipe.missing_cooking_time: "Fehler in der Datei - Dem Kochrezept '' fehlt das erforderliche 'cooking-time'-Argument." -warning.config.recipe.missing_group: "Fehler in der Datei - Dem Rezept '' fehlt das erforderliche 'group'-Argument." -warning.config.recipe.missing_book: "Fehler in der Datei - Dem Rezept '' fehlt das erforderliche 'book'-Argument." -warning.config.loot_table.duplicate: "Fehler in der Datei - Doppelte Loot-Tabelle ''. Bitte prüfen Sie, ob in anderen Dateien die gleiche Konfiguration vorhanden ist." -warning.config.loot_table.missing_type: "Fehler in der Datei - Der Loot-Tabelle '' fehlt das erforderliche 'type'-Argument." -warning.config.loot_table.invalid_type: "Fehler in der Datei - Die Loot-Tabelle '' verwendet einen ungültigen Loot-Tabellen-Typ ''." -warning.config.loot_table.missing_rolls: "Fehler in der Datei - Der Loot-Tabelle '' fehlt das erforderliche 'rolls'-Argument." -warning.config.loot_table.missing_entries: "Fehler in der Datei - Der Loot-Tabelle '' fehlt das erforderliche 'entries'-Argument." -warning.config.loot_table.missing_entry_type: "Fehler in der Datei - Einer der Loot-Tabellen-Einträge in '' hat kein erforderliches 'type'-Argument." -warning.config.loot_table.invalid_entry_type: "Fehler in der Datei - Einer der Loot-Tabellen-Einträge in '' verwendet einen ungültigen Typ ''." -warning.config.loot_table.missing_entry_name: "Fehler in der Datei - Einer der Loot-Tabellen-Einträge in '' hat kein erforderliches 'name'-Argument." -warning.config.loot_table.missing_entry_weight: "Fehler in der Datei - Einer der Loot-Tabellen-Einträge in '' hat kein erforderliches 'weight'-Argument." -warning.config.loot_table.missing_function_type: "Fehler in der Datei - Eine der Loot-Tabellen-Funktionen in '' hat kein erforderliches 'type'-Argument." -warning.config.loot_table.invalid_function_type: "Fehler in der Datei - Eine der Loot-Tabellen-Funktionen in '' verwendet einen ungültigen Typ ''." -warning.config.loot_table.missing_condition_type: "Fehler in der Datei - Eine der Loot-Tabellen-Bedingungen in '' hat kein erforderliches 'type'-Argument." -warning.config.loot_table.invalid_condition_type: "Fehler in der Datei - Eine der Loot-Tabellen-Bedingungen in '' verwendet einen ungültigen Typ ''." -warning.config.resource_pack.generation.missing_equipment_texture: "Ausrüstung '' hat keine Textur ''" -warning.config.equipment.duplicate: "Problem in der Datei gefunden – Duplizierte Ausrüstung ''. Bitte prüfe, ob es dieselbe Konfiguration in anderen Dateien gibt." -warning.config.equipment.missing_type: "Problem in der Datei gefunden – Der Ausrüstung '' fehlt das erforderliche Argument 'type'." -warning.config.equipment.invalid_type: "Problem in der Datei gefunden – Die Ausrüstung '' verwendet ein ungültiges Argument 'type'." -warning.config.equipment.invalid_sacrificed_armor: "Problem in config.yml bei 'equipment.sacrificed-vanilla-armor' gefunden – Ungültiger Vanille-Rüstungstyp ''." \ No newline at end of file +warning.config.advancement.duplicate: "Problem in Datei gefunden - Doppelter Advancement ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.loot_table.missing_pools: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei der das erforderliche 'pools'-Argument fehlt." +warning.config.loot_table.invalid_pools_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, 'pools' sollte eine String/Map-Liste sein, aktueller Typ: ''." +warning.config.loot_table.invalid_conditions_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, 'conditions' sollte eine Map-Liste sein, aktueller Typ: ''." +warning.config.loot_table.invalid_functions_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, 'functions' sollte eine Map-Liste sein, aktueller Typ: ''." +warning.config.loot_table.invalid_entries_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, 'entries' sollte eine Map-Liste sein, aktueller Typ: ''." +warning.config.loot_table.function.missing_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei einer der Funktionen fehlt das erforderliche 'type'-Argument." +warning.config.loot_table.function.invalid_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, eine der Funktionen verwendet einen ungültigen Funktionstyp ''." +warning.config.loot_table.function.apply_bonus.missing_enchantment: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei der Funktion 'apply_bonus' fehlt das erforderliche 'enchantment'-Argument." +warning.config.loot_table.function.apply_bonus.missing_formula: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei der Funktion 'apply_bonus' fehlt das erforderliche 'formula'-Argument." +warning.config.loot_table.function.drop_exp.missing_count: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei der Funktion 'drop_exp' fehlt das erforderliche 'count'-Argument." +warning.config.loot_table.function.set_count.missing_count: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei der Funktion 'set_count' fehlt das erforderliche 'count'-Argument." +warning.config.loot_table.entry.missing_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei einem der Einträge fehlt das erforderliche 'type'-Argument." +warning.config.loot_table.entry.invalid_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, einer der Einträge verwendet einen ungültigen Eintragstyp ''." +warning.config.loot_table.entry.exp.missing_count: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, beim Eintrag 'exp' fehlt das erforderliche 'count'-Argument." +warning.config.loot_table.entry.item.missing_item: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, beim Eintrag 'item' fehlt das erforderliche 'item'-Argument." +warning.config.loot_table.condition.missing_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, bei einer der Bedingungen fehlt das erforderliche 'type'-Argument." +warning.config.loot_table.condition.invalid_type: "Problem in Datei gefunden - '' hat eine falsch konfigurierte Loot-Table, eine der Bedingungen verwendet einen ungültigen Bedingungstyp ''." +warning.config.host.missing_type: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'type'-Argument für Host." +warning.config.host.invalid_type: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Host-Typ '' ist ungültig. Bitte lies https://xiao-momi.github.io/craft-engine-wiki/getting_start/set_up_host." +warning.config.host.external.missing_url: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'url'-Argument für externen Host." +warning.config.host.alist.missing_api_url: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'api-url'-Argument für alist Host." +warning.config.host.alist.missing_username: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'username'-Argument oder Umgebungsvariable 'CE_ALIST_USERNAME' für alist Host." +warning.config.host.alist.missing_password: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'password'-Argument oder Umgebungsvariable 'CE_ALIST_PASSWORD' für alist Host." +warning.config.host.alist.missing_upload_path: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für alist Host." +warning.config.host.dropbox.missing_app_key: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'app-key'-Argument oder Umgebungsvariable 'CE_DROPBOX_APP_KEY' für dropbox Host." +warning.config.host.dropbox.missing_app_secret: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'app-secret'-Argument oder Umgebungsvariable 'CE_DROPBOX_APP_SECRET' für dropbox Host." +warning.config.host.dropbox.missing_refresh_token: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'refresh-token'-Argument oder Umgebungsvariable 'CE_DROPBOX_REFRESH_TOKEN' für dropbox Host." +warning.config.host.dropbox.missing_upload_path: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für dropbox Host." +warning.config.host.lobfile.missing_api_key: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'api-key'-Argument für lobfile Host." +warning.config.host.onedrive.missing_client_id: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'client-id'-Argument oder Umgebungsvariable 'CE_ONEDRIVE_CLIENT_ID' für onedrive Host." +warning.config.host.onedrive.missing_client_secret: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'client-secret'-Argument oder Umgebungsvariable 'CE_ONEDRIVE_CLIENT_SECRET' für onedrive Host." +warning.config.host.onedrive.missing_refresh_token: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'refresh-token'-Argument oder Umgebungsvariable 'CE_ONEDRIVE_REFRESH_TOKEN' für onedrive Host." +warning.config.host.onedrive.missing_upload_path: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für onedrive Host." +warning.config.host.s3.missing_endpoint: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'endpoint'-Argument für s3 Host." +warning.config.host.s3.missing_bucket: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'bucket'-Argument für s3 Host." +warning.config.host.s3.missing_access_key: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'access-key-id'-Argument oder Umgebungsvariable 'CE_S3_ACCESS_KEY_ID' für s3 Host." +warning.config.host.s3.missing_secret: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'access-key-secret'-Argument oder Umgebungsvariable 'CE_S3_ACCESS_KEY_SECRET' für s3 Host." +warning.config.host.s3.missing_upload_path: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'upload-path'-Argument für s3 Host." +warning.config.host.self.missing_ip: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'ip'-Argument für self Host." +warning.config.host.self.invalid_port: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Ungültiger Port '' für self Host." +warning.config.host.self.invalid_url: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Ungültige URL '' für self Host." +warning.config.host.gitlab.missing_url: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'gitlab-url'-Argument für gitlab Host." +warning.config.host.gitlab.missing_token: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'access-token'-Argument für gitlab Host." +warning.config.host.gitlab.missing_project: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'project-id'-Argument für gitlab Host." +warning.config.host.proxy.missing_host: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'host'-Argument für Proxy." +warning.config.host.proxy.missing_port: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'port'-Argument für Proxy." +warning.config.host.proxy.missing_scheme: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Fehlendes erforderliches 'scheme'-Argument für Proxy." +warning.config.host.proxy.invalid: "Problem in config.yml bei 'resource-pack.delivery.hosting' gefunden - Ungültiger Proxy ''." +warning.config.conflict_matcher.missing_type: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'type'-Argument für einen der Handler." +warning.config.conflict_matcher.invalid_type: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Einer der Begriffe verwendet den ungültigen Typ ''." +warning.config.conflict_matcher.exact.missing_path: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'path'-Argument für 'exact'-Matcher." +warning.config.conflict_matcher.contains.missing_path: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'path'-Argument für 'contains'-Matcher." +warning.config.conflict_matcher.filename.missing_name: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'path'-Argument für 'filename'-Matcher." +warning.config.conflict_matcher.pattern.missing_pattern: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'pattern'-Argument für 'pattern'-Matcher." +warning.config.conflict_matcher.parent_prefix.missing_prefix: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'prefix'-Argument für 'parent_path_prefix'-Matcher." +warning.config.conflict_matcher.parent_suffix.missing_suffix: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'suffix'-Argument für 'parent_path_suffix'-Matcher." +warning.config.conflict_matcher.inverted.missing_term: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'term'-Argument für 'inverted'-Matcher." +warning.config.conflict_matcher.all_of.missing_terms: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'terms'-Argument für 'all_of'-Matcher." +warning.config.conflict_matcher.any_of.missing_terms: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'terms'-Argument für 'any_of'-Matcher." +warning.config.conflict_resolution.missing_type: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Fehlendes erforderliches 'type'-Argument für eine der Auflösungen." +warning.config.conflict_resolution.invalid_type: "Problem in config.yml bei 'resource-pack.duplicated-files-handler' gefunden - Eine der Auflösungen verwendet den ungültigen Typ ''." +warning.config.event.missing_trigger: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'on'-Argument für Event-Trigger." +warning.config.event.invalid_trigger: "Problem in Datei gefunden - Die Config '' verwendet einen ungültigen Event-Trigger ''." +warning.config.event.condition.missing_type: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'type'-Argument für die Event-Bedingung." +warning.config.event.condition.invalid_type: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges 'type'-Argument '' für die Event-Bedingung." +warning.config.function.missing_type: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'type'-Argument für die Funktion." +warning.config.function.invalid_type: "Problem in Datei gefunden - Die Config '' verwendet einen ungültigen Funktionstyp ''." +warning.config.function.command.missing_command: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'command'-Argument für die 'command'-Funktion." +warning.config.function.actionbar.missing_actionbar: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'actionbar'-Argument für die 'actionbar'-Funktion." +warning.config.function.message.missing_message: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'message'-Argument für die 'message'-Funktion." +warning.config.function.open_window.missing_gui_type: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'gui-type'-Argument für die 'open_window'-Funktion." +warning.config.function.open_window.invalid_gui_type: "Problem in Datei gefunden - Die Config '' verwendet einen ungültigen GUI-Typ für die 'open_window'-Funktion. Erlaubte Typen: []." +warning.config.function.run.missing_functions: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'functions'-Argument für die 'run'-Funktion." +warning.config.function.place_block.missing_block_state: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'block-state'-Argument für die 'place_block'-Funktion." +warning.config.function.set_food.missing_food: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'food'-Argument für die 'set_food'-Funktion." +warning.config.function.set_saturation.missing_saturation: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'saturation'-Argument für die 'set_saturation'-Funktion." +warning.config.function.play_sound.missing_sound: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'sound'-Argument für die 'play_sound'-Funktion." +warning.config.function.particle.missing_particle: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'particle'-Argument für die 'particle'-Funktion." +warning.config.function.particle.missing_color: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'color'-Argument für die 'particle'-Funktion." +warning.config.function.particle.missing_from: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'from'-Argument für die 'particle'-Funktion." +warning.config.function.particle.missing_to: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'to'-Argument für die 'particle'-Funktion." +warning.config.function.particle.missing_item: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'item'-Argument für die 'particle'-Funktion." +warning.config.function.particle.missing_block_state: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'block-state'-Argument für die 'particle'-Funktion." +warning.config.function.leveler_exp.missing_count: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'count'-Argument für die 'leveler_exp'-Funktion." +warning.config.function.leveler_exp.missing_leveler: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'leveler'-Argument für die 'leveler_exp'-Funktion." +warning.config.function.leveler_exp.missing_plugin: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'plugin'-Argument für die 'leveler_exp'-Funktion." +warning.config.function.remove_potion_effect.missing_potion_effect: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'potion-effect'-Argument für die 'remove_potion_effect'-Funktion." +warning.config.function.potion_effect.missing_potion_effect: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'potion-effect'-Argument für die 'potion_effect'-Funktion." +warning.config.function.set_cooldown.missing_time: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'time'-Argument für die 'set_cooldown'-Funktion." +warning.config.function.set_cooldown.missing_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'id'-Argument für die 'set_cooldown'-Funktion." +warning.config.function.remove_cooldown.missing_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'id'-Argument für die 'remove_cooldown'-Funktion." +warning.config.function.mythic_mobs_skill.missing_skill: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'skill'-Argument für die 'mythic_mobs_skill'-Funktion." +warning.config.function.spawn_furniture.missing_furniture_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'furniture-id'-Argument für die 'spawn_furniture'-Funktion." +warning.config.function.replace_furniture.missing_furniture_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'furniture-id'-Argument für die 'replace_furniture'-Funktion." +warning.config.selector.missing_type: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'type'-Argument für den Selector." +warning.config.selector.invalid_type: "Problem in Datei gefunden - Die Config '' verwendet einen ungültigen Selector-Typ ''." +warning.config.selector.invalid_target: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges Selector-Target ''." +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.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: ''" +warning.config.resource_pack.generation.missing_parent_model: "Model '' kann das Parent-Model nicht finden: ''" +warning.config.resource_pack.generation.malformatted_json: "JSON-Datei '' ist fehlerhaft formatiert." +warning.config.resource_pack.generation.missing_equipment_texture: "Beim Equipment '' fehlt die Textur ''" +warning.config.resource_pack.invalid_overlay_format: "Problem in config.yml bei 'resource-pack.overlay-format' gefunden - Ungültiges Overlay-Format ''. Das Overlay-Format muss den Platzhalter '{version}' enthalten." +warning.config.equipment.duplicate: "Problem in Datei gefunden - Doppeltes Equipment ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." +warning.config.equipment.missing_type: "Problem in Datei gefunden - Beim Equipment '' fehlt das erforderliche 'type'-Argument." +warning.config.equipment.invalid_type: "Problem in Datei gefunden - Das Equipment '' verwendet ein ungültiges 'type'-Argument." +warning.config.equipment.invalid_sacrificed_armor: "Problem in config.yml bei 'equipment.sacrificed-vanilla-armor' gefunden - Ungültiger Vanilla-Rüstungstyp ''." \ No newline at end of file From 606121662895fd954787f30f1c6b8003f6724cd4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 22 Aug 2025 21:50:07 +0800 Subject: [PATCH 008/177] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=91=A9=E6=93=A6?= =?UTF-8?q?=E5=8A=9B=EF=BC=8C=E8=B7=B3=E8=B7=83=E3=80=81=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E7=B3=BB=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitCustomBlock.java | 3 ++ .../item/recipe/BukkitRecipeManager.java | 32 ++++++------- .../reflection/minecraft/CoreReflections.java | 12 +++++ .../craftengine/core/block/BlockSettings.java | 45 +++++++++++++++++++ 4 files changed, 76 insertions(+), 16 deletions(-) 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 14ae935fc..7f0506438 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 @@ -128,6 +128,9 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { 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); 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 7cba8d1bc..cb691ab5c 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 @@ -68,22 +68,6 @@ public class BukkitRecipeManager extends AbstractRecipeManager { return recipe; }; - static { - try { - Key dyeRecipeId = Key.from("armor_dye"); - MINECRAFT_RECIPE_REMOVER.accept(dyeRecipeId); - MINECRAFT_RECIPE_ADDER.apply(dyeRecipeId, RecipeInjector.createCustomDyeRecipe(dyeRecipeId)); - Key repairRecipeId = Key.from("repair_item"); - MINECRAFT_RECIPE_REMOVER.accept(repairRecipeId); - MINECRAFT_RECIPE_ADDER.apply(repairRecipeId, RecipeInjector.createRepairItemRecipe(repairRecipeId)); - Key fireworkStarFadeRecipeId = Key.from("firework_star_fade"); - MINECRAFT_RECIPE_REMOVER.accept(fireworkStarFadeRecipeId); - MINECRAFT_RECIPE_ADDER.apply(fireworkStarFadeRecipeId, RecipeInjector.createFireworkStarFadeRecipe(fireworkStarFadeRecipeId)); - } catch (ReflectiveOperationException e) { - throw new ReflectionInitException("Failed to inject special recipes", e); - } - } - private static final Map, Object>> ADD_RECIPE_FOR_MINECRAFT_RECIPE_HOLDER = Map.of( RecipeSerializers.SHAPED, recipe -> { CustomShapedRecipe shapedRecipe = (CustomShapedRecipe) recipe; @@ -481,6 +465,22 @@ public class BukkitRecipeManager extends AbstractRecipeManager { this.plugin.logger().warn("Failed to register recipe " + recipe.id().toString(), e); } } + + // 重新注入特殊配方 + try { + Key dyeRecipeId = Key.from("armor_dye"); + MINECRAFT_RECIPE_REMOVER.accept(dyeRecipeId); + MINECRAFT_RECIPE_ADDER.apply(dyeRecipeId, RecipeInjector.createCustomDyeRecipe(dyeRecipeId)); + Key repairRecipeId = Key.from("repair_item"); + MINECRAFT_RECIPE_REMOVER.accept(repairRecipeId); + MINECRAFT_RECIPE_ADDER.apply(repairRecipeId, RecipeInjector.createRepairItemRecipe(repairRecipeId)); + Key fireworkStarFadeRecipeId = Key.from("firework_star_fade"); + MINECRAFT_RECIPE_REMOVER.accept(fireworkStarFadeRecipeId); + MINECRAFT_RECIPE_ADDER.apply(fireworkStarFadeRecipeId, RecipeInjector.createFireworkStarFadeRecipe(fireworkStarFadeRecipeId)); + } catch (ReflectiveOperationException e) { + throw new ReflectionInitException("Failed to inject special recipes", e); + } + try { // give flags back on 1.21.2+ if (VersionHelper.isOrAbove1_21_2() && this.stolenFeatureFlagSet != null) { 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..b018df4d1 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 @@ -1451,6 +1451,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) ); 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 8abe2a871..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 @@ -38,6 +38,9 @@ public class BlockSettings { LazyReference> correctTools = LazyReference.lazyReference(Set::of); String name; String supportShapeBlockState; + float friction = 0.6f; + float speedFactor = 1f; + float jumpFactor = 1f; private BlockSettings() {} @@ -101,6 +104,9 @@ public class BlockSettings { newSettings.supportShapeBlockState = settings.supportShapeBlockState; newSettings.propagatesSkylightDown = settings.propagatesSkylightDown; newSettings.useShapeForLightOcclusion = settings.useShapeForLightOcclusion; + newSettings.speedFactor = settings.speedFactor; + newSettings.jumpFactor = settings.jumpFactor; + newSettings.friction = settings.friction; return newSettings; } @@ -140,6 +146,18 @@ public class BlockSettings { return hardness; } + public float friction() { + return friction; + } + + public float jumpFactor() { + return jumpFactor; + } + + public float speedFactor() { + return speedFactor; + } + public Tristate canOcclude() { return canOcclude; } @@ -236,6 +254,21 @@ public class BlockSettings { return this; } + public BlockSettings friction(float friction) { + this.friction = friction; + return this; + } + + public BlockSettings speedFactor(float speedFactor) { + this.speedFactor = speedFactor; + return this; + } + + public BlockSettings jumpFactor(float jumpFactor) { + this.jumpFactor = jumpFactor; + return this; + } + public BlockSettings tags(Set tags) { this.tags = tags; return this; @@ -384,6 +417,18 @@ public class BlockSettings { float floatValue = ResourceConfigUtils.getAsFloat(value, "hardness"); return settings -> settings.hardness(floatValue); })); + registerFactory("friction", (value -> { + float floatValue = ResourceConfigUtils.getAsFloat(value, "friction"); + return settings -> settings.friction(floatValue); + })); + registerFactory("speed-factor", (value -> { + float floatValue = ResourceConfigUtils.getAsFloat(value, "speed-factor"); + return settings -> settings.speedFactor(floatValue); + })); + registerFactory("jump-factor", (value -> { + float floatValue = ResourceConfigUtils.getAsFloat(value, "jump-factor"); + return settings -> settings.jumpFactor(floatValue); + })); registerFactory("resistance", (value -> { float floatValue = ResourceConfigUtils.getAsFloat(value, "resistance"); return settings -> settings.resistance(floatValue); From 671169ba2c4a0439bda7527857bbdfe8024d97fd Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 23 Aug 2025 02:59:26 +0800 Subject: [PATCH 009/177] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dkeep=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/item/recipe/CustomSmithingTransformRecipe.java | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 bbb5aed75..9ebe583a7 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 @@ -214,7 +214,7 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip for (Key component : this.components) { Object componentObj = item1.getExactComponent(component); if (componentObj != null) { - item3.setComponent(component, componentObj); + item3.setExactComponent(component, componentObj); } } } diff --git a/gradle.properties b/gradle.properties index ac5eb779a..210e6b524 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.1 +project_version=0.0.62.2 config_version=44 lang_version=24 project_group=net.momirealms From f8bbe9011afc5d8167f9cd9cc7acebc2f34e2d05 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 23 Aug 2025 03:24:10 +0800 Subject: [PATCH 010/177] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/recipe/CustomIngredientList.java | 1 - common-files/src/main/resources/config.yml | 38 ++++---- .../core/plugin/config/Config.java | 93 +++++++++++-------- .../DefaultSectionSerializer.java | 16 +++- gradle.properties | 2 +- 5 files changed, 88 insertions(+), 62 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java index b5c977d3b..6b7332486 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/CustomIngredientList.java @@ -9,7 +9,6 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; public class CustomIngredientList extends ArrayList { private final Ingredient ingredient; diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 27ead33b2..6a8a03355 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -190,16 +190,6 @@ block: extended-interaction-range: 0.5 furniture: - # Automatically remove outdated furniture entities when a chunk is loaded. - handle-invalid-furniture-on-chunk-load: - # Enable/disable the cleanup system - enable: false - # Removes the specified invalid furniture - remove: - - "xxx:invalid_furniture" - # Converts the invalid furniture to a valid one - convert: - "namespace:furniture_a": "namespace:furniture_b" # Hide technical entities used for storing furniture metadata. # NOTE: # - These are INVISIBLE entities used internally for tracking furniture states @@ -211,10 +201,14 @@ furniture: collision-entity-type: interaction emoji: - chat: true - book: true - anvil: true - sign: true + # Contexts where emoji parsing is enabled + contexts: + chat: true + book: true + anvil: true + sign: true + # Prevent lag or oversized packet when processing emoji-heavy content + max-emojis-per-parse: 16 image: # Block image tags using minecraft:default font in these interfaces @@ -366,12 +360,6 @@ gui: brewing: title: "" -performance: - # Maximum chain update depth when fixing client visuals - max-note-block-chain-update-limit: 48 - # Prevent lag or oversized packet when processing emoji-heavy content - max-emojis-per-parse: 16 - light-system: # Required for custom light-emitting blocks enable: true @@ -420,6 +408,16 @@ chunk-system: # server's CraftEngine internal data. Enabling this option will synchronize the data when the chunk is loaded. # (This option only slightly impacts performance, which has been fully optimized, so you don't need to worry too much.) sync-custom-blocks-on-chunk-load: false + # This system processes any invalid block when a chunk is loaded. + process-invalid-blocks: + enable: false + remove: [] + convert: {} + # This system processes any invalid furniture when a chunk is loaded. + process-invalid-furniture: + enable: false + remove: [] + convert: {} # Enables or disables debug mode debug: 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 6c1fe4b11..18e0bf089 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 @@ -96,10 +96,6 @@ public class Config { protected Path resource_pack$delivery$file_to_upload; protected Component resource_pack$send$prompt; - protected int performance$max_note_block_chain_update_limit; - protected int performance$max_tripwire_chain_update_limit; - protected int performance$max_emojis_per_parse; - protected boolean light_system$force_update_light; protected boolean light_system$enable; @@ -110,9 +106,11 @@ public class Config { protected boolean chunk_system$cache_system; protected boolean chunk_system$injection$use_fast_method; protected boolean chunk_system$injection$target; + protected boolean chunk_system$process_invalid_furniture$enable; + protected Map chunk_system$process_invalid_furniture$mapping; + protected boolean chunk_system$process_invalid_blocks$enable; + protected Map chunk_system$process_invalid_blocks$mapping; - protected boolean furniture$handle_invalid_furniture_on_chunk_load$enable; - protected Map furniture$handle_invalid_furniture_on_chunk_load$mapping; protected boolean furniture$hide_base_entity; protected ColliderType furniture$collision_entity_type; @@ -161,10 +159,11 @@ public class Config { protected Key equipment$sacrificed_vanilla_armor$humanoid; protected Key equipment$sacrificed_vanilla_armor$humanoid_leggings; - protected boolean emoji$chat; - protected boolean emoji$book; - protected boolean emoji$anvil; - protected boolean emoji$sign; + protected boolean emoji$contexts$chat; + protected boolean emoji$contexts$book; + protected boolean emoji$contexts$anvil; + protected boolean emoji$contexts$sign; + protected int emoji$max_emojis_per_parse; public Config(CraftEngine plugin) { this.plugin = plugin; @@ -310,11 +309,6 @@ public class Config { resource_pack$duplicated_files_handler = List.of(); } - // performance - performance$max_note_block_chain_update_limit = config.getInt("performance.max-note-block-chain-update-limit", 64); - performance$max_tripwire_chain_update_limit = config.getInt("performance.max-tripwire-chain-update-limit", 128); - performance$max_emojis_per_parse = config.getInt("performance.max-emojis-per-parse", 32); - // light light_system$force_update_light = config.getBoolean("light-system.force-update-light", false); light_system$enable = config.getBoolean("light-system.enable", true); @@ -330,22 +324,37 @@ public class Config { chunk_system$injection$target = config.getEnum("chunk-system.injection.target", InjectionTarget.class, InjectionTarget.PALETTE) == InjectionTarget.PALETTE; } - // furniture - furniture$handle_invalid_furniture_on_chunk_load$enable = config.getBoolean("furniture.handle-invalid-furniture-on-chunk-load.enable", false); - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (String furniture : config.getStringList("furniture.handle-invalid-furniture-on-chunk-load.remove")) { - builder.put(furniture, ""); + chunk_system$process_invalid_furniture$enable = config.getBoolean("chunk-system.process-invalid-furniture.enable", false); + ImmutableMap.Builder furnitureBuilder = ImmutableMap.builder(); + for (String furniture : config.getStringList("chunk-system.process-invalid-furniture.remove")) { + furnitureBuilder.put(furniture, ""); } - if (config.contains("furniture.handle-invalid-furniture-on-chunk-load.convert")) { - Section section = config.getSection("furniture.handle-invalid-furniture-on-chunk-load.convert"); + if (config.contains("chunk-system.process-invalid-furniture.convert")) { + Section section = config.getSection("chunk-system.process-invalid-furniture.convert"); if (section != null) { for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { - builder.put(entry.getKey(), entry.getValue().toString()); + furnitureBuilder.put(entry.getKey(), entry.getValue().toString()); } } } + chunk_system$process_invalid_furniture$mapping = furnitureBuilder.build(); - furniture$handle_invalid_furniture_on_chunk_load$mapping = builder.build(); + chunk_system$process_invalid_blocks$enable = config.getBoolean("chunk-system.process-invalid-blocks.enable", false); + ImmutableMap.Builder blockBuilder = ImmutableMap.builder(); + for (String furniture : config.getStringList("chunk-system.process-invalid-blocks.remove")) { + blockBuilder.put(furniture, ""); + } + if (config.contains("chunk-system.process-invalid-blocks.convert")) { + Section section = config.getSection("chunk-system.process-invalid-blocks.convert"); + if (section != null) { + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + blockBuilder.put(entry.getKey(), entry.getValue().toString()); + } + } + } + chunk_system$process_invalid_blocks$mapping = blockBuilder.build(); + + // furniture furniture$hide_base_entity = config.getBoolean("furniture.hide-base-entity", true); furniture$collision_entity_type = ColliderType.valueOf(config.getString("furniture.collision-entity-type", "interaction").toUpperCase(Locale.ENGLISH)); @@ -405,10 +414,12 @@ public class Config { image$intercept_packets$advancement = config.getBoolean("image.intercept-packets.advancement", true); // emoji - emoji$chat = config.getBoolean("emoji.chat", true); - emoji$anvil = config.getBoolean("emoji.anvil", true); - emoji$book = config.getBoolean("emoji.book", true); - emoji$sign = config.getBoolean("emoji.sign", true); + emoji$contexts$chat = config.getBoolean("emoji.contexts.chat", true); + emoji$contexts$anvil = config.getBoolean("emoji.contexts.anvil", true); + emoji$contexts$book = config.getBoolean("emoji.contexts.book", true); + emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true); + emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); + firstTime = false; } @@ -464,24 +475,28 @@ public class Config { } public static int maxNoteBlockChainUpdate() { - return instance.performance$max_note_block_chain_update_limit; + return 64; } public static int maxEmojisPerParse() { - return instance.performance$max_emojis_per_parse; + return instance.emoji$max_emojis_per_parse; } public static boolean handleInvalidFurniture() { - return instance.furniture$handle_invalid_furniture_on_chunk_load$enable; + return instance.chunk_system$process_invalid_furniture$enable; + } + + public static boolean handleInvalidBlock() { + return instance.chunk_system$process_invalid_blocks$enable; } public static Map furnitureMappings() { - return instance.furniture$handle_invalid_furniture_on_chunk_load$mapping; + return instance.chunk_system$process_invalid_furniture$mapping; } -// public static boolean forceUpdateLight() { -// return instance.light_system$force_update_light; -// } + public static Map blockMappings() { + return instance.chunk_system$process_invalid_blocks$mapping; + } public static boolean enableLightSystem() { return instance.light_system$enable; @@ -772,19 +787,19 @@ public class Config { } public static boolean allowEmojiSign() { - return instance.emoji$sign; + return instance.emoji$contexts$sign; } public static boolean allowEmojiChat() { - return instance.emoji$chat; + return instance.emoji$contexts$chat; } public static boolean allowEmojiAnvil() { - return instance.emoji$anvil; + return instance.emoji$contexts$anvil; } public static boolean allowEmojiBook() { - return instance.emoji$book; + return instance.emoji$contexts$book; } public static ColliderType colliderType() { 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 e8947d094..36d420260 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 @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.EmptyBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.InactiveCustomBlock; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; @@ -58,7 +59,20 @@ public final class DefaultSectionSerializer { CompoundTag palette = (CompoundTag) tag; String id = palette.getString("id"); CompoundTag data = palette.getCompound("properties"); - Key key = Key.of(id); + Key key; + if (Config.handleInvalidBlock()) { + String converted = Config.blockMappings().get(id); + if (converted == null) { + key = Key.of(id); + } else if (converted.isEmpty()) { + paletteEntries.add(EmptyBlock.STATE); + continue; + } else { + key = Key.of(converted); + } + } else { + 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)); diff --git a/gradle.properties b/gradle.properties index 210e6b524..be90ad6ab 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.62.2 -config_version=44 +config_version=45 lang_version=24 project_group=net.momirealms latest_supported_version=1.21.8 From db003d6c679008490318f753f2a2691383ea12a9 Mon Sep 17 00:00:00 2001 From: iqtester <1835ww@gmail.com> Date: Fri, 22 Aug 2025 22:18:05 -0400 Subject: [PATCH 011/177] fix: interceptor never work --- .../bukkit/block/behavior/PressurePlateBlockBehavior.java | 2 +- .../craftengine/bukkit/plugin/injector/BlockGenerator.java | 4 ++-- .../bukkit/plugin/reflection/minecraft/CoreReflections.java | 5 ++--- gradle.properties | 2 +- 4 files changed, 6 insertions(+), 7 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 9f3e513c7..ce4f40b47 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 @@ -189,7 +189,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { Object pos = args[2]; Object newState = args[3]; boolean movedByPiston = (boolean) args[4]; - if (!movedByPiston && !FastNMS.INSTANCE.method$BlockStateBase$is(state, FastNMS.INSTANCE.method$BlockState$getBlock(newState))) { + if (!movedByPiston && !FastNMS.INSTANCE.method$BlockStateBase$isBlock(state, FastNMS.INSTANCE.method$BlockState$getBlock(newState))) { if (this.getSignalForState(state) > 0) { this.updateNeighbours(level, pos, thisBlock); } 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..b42ab2ccd 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 @@ -185,11 +185,11 @@ public final class BlockGenerator { .method(ElementMatchers.is(CoreReflections.method$BlockBehaviour$spawnAfterBreak)) .intercept(MethodDelegation.to(SpawnAfterBreakInterceptor.INSTANCE)); 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)); } 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)); } 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 b018df4d1..b7672a23c 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 @@ -4011,12 +4011,11 @@ public final class CoreReflections { ); // 1.20~1.21.4 - public static final Method method$BlockBehaviour$onRemove = MiscUtils.requireNonNullIf( + public static final Method method$BlockBehaviour$onRemove = VersionHelper.isOrAbove1_21_5() ? null : ReflectionUtils.getDeclaredMethod( clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$BlockState, boolean.class - ), - !VersionHelper.isOrAbove1_21_5() ); + public static final Object instance$CollisionContext$empty; static { diff --git a/gradle.properties b/gradle.properties index be90ad6ab..fca0f2432 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.19 -nms_helper_version=1.0.58 +nms_helper_version=1.0.59 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From be7e9f6a096f081cfda34b90ad133a5e6bdad3ac Mon Sep 17 00:00:00 2001 From: iqtester <1835ww@gmail.com> Date: Fri, 22 Aug 2025 22:21:08 -0400 Subject: [PATCH 012/177] iqtesterr --- bukkit/loader/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 03b971562..c7ef218b6 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -47,7 +47,7 @@ bukkit { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") - contributors = listOf("jhqwqmc", "iqtesterrr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8") + contributors = listOf("jhqwqmc", "iqtesterr", "WhiteProject1", "Catnies", "xiaozhangup", "TamashiiMon", "Halogly", "ArubikU", "Maxsh001", "Sasha2294", "MrPanda8") softDepend = listOf("PlaceholderAPI", "WorldEdit", "FastAsyncWorldEdit", "Skript") foliaSupported = true } From cdad8190f10b3ae5b5a5b6348ed7744cbf88dc8f Mon Sep 17 00:00:00 2001 From: halogly Date: Sat, 23 Aug 2025 11:27:07 +0800 Subject: [PATCH 013/177] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E8=BF=9B=E5=91=BD=E4=BB=A4=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/listener/ItemEventListener.java | 6 ++- .../plugin/user/BukkitServerPlayer.java | 36 ++++++++++--- .../craftengine/bukkit/util/SoundUtils.java | 1 + .../bukkit/world/BukkitBlockInWorld.java | 7 +++ .../src/main/resources/translations/de.yml | 2 + .../src/main/resources/translations/en.yml | 12 +++-- .../src/main/resources/translations/ru_ru.yml | 2 + .../src/main/resources/translations/zh_cn.yml | 8 ++- .../core/entity/player/Player.java | 12 ++++- .../context/condition/CommonConditions.java | 18 +++++++ .../condition/MatchBlockTypeCondition.java | 50 +++++++++++++++++++ .../condition/MatchEntityTypeCondition.java | 48 ++++++++++++++++++ .../context/condition/MatchItemCondition.java | 12 +---- .../plugin/context/event/EventConditions.java | 2 + .../context/function/CommandFunction.java | 31 +++++++----- .../parameter/DirectContextParameters.java | 5 +- .../parameter/PlayerParameterProvider.java | 5 +- .../craftengine/core/sound/SoundSource.java | 3 +- .../craftengine/core/world/BlockInWorld.java | 3 ++ 19 files changed, 221 insertions(+), 42 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java 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 db9aff35a..e5906ab91 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 @@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.item.listener; import io.papermc.paper.event.block.CompostItemEvent; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.event.CustomBlockInteractEvent; +import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.item.BukkitCustomItem; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; @@ -96,9 +97,10 @@ public class ItemEventListener implements Listener { Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) - .withParameter(DirectContextParameters.EVENT, cancellable) - .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(event.getRightClicked().getLocation())) .withParameter(DirectContextParameters.HAND, hand) + .withParameter(DirectContextParameters.EVENT, cancellable) + .withParameter(DirectContextParameters.ENTITY, new BukkitEntity(entity)) + .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(event.getRightClicked().getLocation())) ); CustomItem customItem = optionalCustomItem.get(); customItem.execute(context, EventTrigger.RIGHT_CLICK); 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 bf6afab4b..51f382f78 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 @@ -40,6 +40,7 @@ import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -177,6 +178,26 @@ public class BukkitServerPlayer extends Player { return platformPlayer().isSneaking(); } + @Override + public boolean isSwimming() { + return platformPlayer().isSwimming(); + } + + @Override + public boolean isClimbing() { + return platformPlayer().isClimbing(); + } + + @Override + public boolean isGliding() { + return platformPlayer().isGliding(); + } + + @Override + public boolean isFlying() { + return platformPlayer().isFlying(); + } + @Override public GameMode gameMode() { return switch (platformPlayer().getGameMode()) { @@ -475,7 +496,7 @@ public class BukkitServerPlayer extends Player { ImmutableBlockState customState = optionalCustomState.get(); Item tool = getItemInHand(InteractionHand.MAIN_HAND); boolean isCorrectTool = FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(tool.getLiteralObject(), blockState); - // 如果自定义方块在服务端侧未使用正确地工具,那么需要还原挖掘速度 + // 如果自定义方块在服务端侧未使用正确的工具,那么需要还原挖掘速度 if (!isCorrectTool) { progress *= (10f / 3f); } @@ -939,6 +960,14 @@ public class BukkitServerPlayer extends Player { platformPlayer().performCommand(command); } + @Override + @SuppressWarnings("UnstableApiUsage") + public void performCommandAsEvent(String command) { + String formattedCommand = command.startsWith("/") ? command : "/" + command; + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(platformPlayer(), formattedCommand); + Bukkit.getPluginManager().callEvent(event); + } + @Override public double luck() { if (VersionHelper.isOrAbove1_21_3()) { @@ -948,11 +977,6 @@ public class BukkitServerPlayer extends Player { } } - @Override - public boolean isFlying() { - return platformPlayer().isFlying(); - } - @Override public int foodLevel() { return platformPlayer().getFoodLevel(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java index 9f21529df..64ff47e55 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java @@ -37,6 +37,7 @@ public final class SoundUtils { case HOSTILE -> SoundCategory.HOSTILE; case NEUTRAL -> SoundCategory.NEUTRAL; case WEATHER -> SoundCategory.WEATHER; + case UI -> SoundCategory.UI; }; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java index 6a9ce89a4..2be28902a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java @@ -6,10 +6,12 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect 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.KeyUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockInWorld; import net.momirealms.craftengine.core.world.World; import org.bukkit.Location; @@ -59,6 +61,11 @@ public class BukkitBlockInWorld implements BlockInWorld { return this.block.getZ(); } + @Override + public Key type() { + return KeyUtils.namespacedKey2Key(this.block.getType().getKey()); + } + @Override public World world() { return new BukkitWorld(this.block.getWorld()); diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 3e3ffe8b1..99b377def 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -106,6 +106,8 @@ warning.config.condition.expression.missing_expression: "Problem in Date warning.config.condition.is_null.missing_argument: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'argument'-Argument für die 'is_null'-Bedingung." warning.config.condition.hand.missing_hand: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'hand'-Argument für die 'hand'-Bedingung." warning.config.condition.hand.invalid_hand: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges 'hand'-Argument '' für die 'hand'-Bedingung. Erlaubte Hand-Typen: []" +warning.config.condition.gamemode.missing_gamemode: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'gamemode'-Argument für die 'gamemode'-Bedingung." +warning.config.condition.gamemode.invalid_gamemode: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges 'gamemode'-Argument '' für die 'gamemode'-Bedingung. Erlaubte Handtypen: []" warning.config.condition.on_cooldown.missing_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'id'-Argument für die 'on_cooldown'-Bedingung." warning.config.structure.not_section: "Problem in Datei gefunden - Die Config '' wird als Config-Abschnitt erwartet, ist aber tatsächlich ein(e) ''." warning.config.image.duplicate: "Problem in Datei gefunden - Doppeltes Image ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index c15db1c61..3a73bd5d4 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -92,6 +92,8 @@ warning.config.condition.inverted.invalid_term_type: "Issue found in fil warning.config.condition.enchantment.missing_predicate: "Issue found in file - The config '' is missing the required 'predicate' argument for 'enchantment' condition." warning.config.condition.enchantment.invalid_predicate: "Issue found in file - The config '' is using an invalid enchantment 'predicate' argument ''." warning.config.condition.match_block_property.missing_properties: "Issue found in file - The config '' is missing the required 'properties' argument for 'match_block_property' condition." +warning.config.condition.match_block_type.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_block_type' condition." +warning.config.condition.match_entity_type.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_entity_type' condition." warning.config.condition.match_item.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_item' condition." warning.config.condition.table_bonus.missing_enchantment: "Issue found in file - The config '' is missing the required 'enchantment' argument for 'table_bonus' condition." warning.config.condition.table_bonus.missing_chances: "Issue found in file - The config '' is missing the required 'chances' argument for 'table_bonus' condition." @@ -106,6 +108,8 @@ warning.config.condition.expression.missing_expression: "Issue found in warning.config.condition.is_null.missing_argument: "Issue found in file - The config '' is missing the required 'argument' argument for 'is_null' condition." warning.config.condition.hand.missing_hand: "Issue found in file - The config '' is missing the required 'hand' argument for 'hand' condition." warning.config.condition.hand.invalid_hand: "Issue found in file - The config '' is using an invalid 'hand' argument '' for 'hand' condition. Allowed hand types: []" +warning.config.condition.gamemode.missing_gamemode: "Issue found in file - The config '' is missing the required 'gamemode' argument for 'gamemode' condition." +warning.config.condition.gamemode.invalid_gamemode: "Issue found in file - The config '' is using an invalid 'gamemode' argument '' for 'gamemode' condition. Allowed gamemode types: []" warning.config.condition.on_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'on_cooldown' condition." warning.config.structure.not_section: "Issue found in file - The config '' is expected to be a config section while it's actually a(n) ''." warning.config.image.duplicate: "Issue found in file - Duplicated image ''. Please check if there is the same configuration in other files." @@ -295,7 +299,7 @@ warning.config.block.behavior.fence_gate.missing_facing: "Issue found in 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.trapdoor.missing_type: "Issue found in file - The block '' is missing the required 'type' property for 'slab_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." warning.config.block.behavior.stairs.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'stairs_block' behavior." @@ -303,9 +307,9 @@ 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.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: []" warning.config.model.generation.invalid_gui_light: "Issue found in file - The config '' is using an invalid gui-light option '' in 'generation' section. Allowed gui light options: []" -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.texture.invalid: "Issue found in file - The config '' has a texture '' with path '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.model.generation.parent.invalid: "Issue found in file - The config '' has a parent argument '' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters." warning.config.emoji.missing_keywords: "Issue found in file - The emoji '' is missing the required 'keywords' argument." @@ -411,14 +415,14 @@ warning.config.selector.invalid_type: "Issue found in file - The warning.config.selector.invalid_target: "Issue found in file - The config '' is using an invalid selector target ''." warning.config.resource_pack.item_model.already_exist: "Failed to generate item model for '' because the file '' already exists." warning.config.resource_pack.model.generation.already_exist: "Failed to generate model because the model file '' already exists." +warning.config.resource_pack.generation.malformatted_json: "Json file '' is malformatted." warning.config.resource_pack.generation.missing_font_texture: "Font '' is missing required 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.missing_model_texture: "Model '' is missing texture ''" warning.config.resource_pack.generation.missing_item_model: "Item '' is missing model file: ''" warning.config.resource_pack.generation.missing_block_model: "Block state '' is missing model file: ''" warning.config.resource_pack.generation.missing_parent_model: "Model '' cannot find parent model: ''" -warning.config.resource_pack.generation.malformatted_json: "Json file '' is malformatted." 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.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 60036cb99..120af2f0e 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -104,6 +104,8 @@ warning.config.condition.expression.missing_expression: "Проблем warning.config.condition.is_null.missing_argument: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'argument' аргумент для 'is_null' состояния." warning.config.condition.hand.missing_hand: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'hand' аргумент для 'hand' состояния." warning.config.condition.hand.invalid_hand: "Проблема найдена в файле - В конфигурации '' использует недействительный 'hand' аргумент '' для 'hand' состояния. Допустимые типы рук: []" +warning.config.condition.gamemode.missing_gamemode: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'gamemode' аргумент для 'gamemode' состояния." +warning.config.condition.gamemode.invalid_gamemode: "Проблема найдена в файле - В конфигурации '' использует недействительный 'gamemode' аргумент '' для 'gamemode' состояния. Допустимые типы режимов игры: []" warning.config.condition.on_cooldown.missing_id: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'id' аргумент для 'on_cooldown' состояния." warning.config.structure.not_section: "Проблема найдена в файле - В конфигурации '' ожидается, что это будет раздел конфигурации, хотя на самом деле это a(n) ''." warning.config.image.duplicate: "Проблема найдена в файле - Дублированное изображение ''. Проверьте, есть ли такая же конфигурация в других файлах." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 348ab6b22..918c383b0 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -92,6 +92,8 @@ warning.config.condition.inverted.invalid_term_type: "在文件 warning.config.condition.enchantment.missing_predicate: "在文件 发现问题 - 配置项 '' 缺少 'enchantment' 条件所需的 'predicate' 参数" warning.config.condition.enchantment.invalid_predicate: "在文件 发现问题 - 配置项 '' 使用了无效的附魔 'predicate' 参数 ''" warning.config.condition.match_block_property.missing_properties: "在文件 发现问题 - 配置项 '' 缺少 'match_block_property' 条件所需的 'properties' 参数" +warning.config.condition.match_block_type.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_block_type' 条件所需的 'id' 参数" +warning.config.condition.match_entity_type.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_entity_type' 条件所需的 'id' 参数" warning.config.condition.match_item.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_item' 条件所需的 'id' 参数" warning.config.condition.table_bonus.missing_enchantment: "在文件 发现问题 - 配置项 '' 缺少 'table_bonus' 条件所需的 'enchantment' 参数" warning.config.condition.table_bonus.missing_chances: "在文件 发现问题 - 配置项 '' 缺少 'table_bonus' 条件所需的 'chances' 参数" @@ -106,6 +108,8 @@ warning.config.condition.expression.missing_expression: "在文件 在文件 发现问题 - 配置项 '' 缺少 'is_null' 条件的必需的 'argument' 参数" warning.config.condition.hand.missing_hand: "在文件 发现问题 - 配置项 '' 缺少 'hand' 条件必需的 'hand' 参数" warning.config.condition.hand.invalid_hand: "在文件 发现问题 - 配置项 '' 使用了无效的 'hand' 参数 ''('hand' 条件)。允许的手部类型: []" +warning.config.condition.gamemode.missing_gamemode: "在文件 发现问题 - 配置项 '' 缺少 'gamemode' 条件必需的 'gamemode' 参数" +warning.config.condition.gamemode.invalid_gamemode: "在文件 发现问题 - 配置项 '' 使用了无效的 'gamemode' 参数 ''('gamemode' 条件)。允许的游戏模式类型: []" warning.config.condition.on_cooldown.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'on_cooldown' 条件必需的 'id' 参数" warning.config.structure.not_section: "在文件 发现问题 - 配置项 '' 应为配置段落 但实际类型为 ''" warning.config.image.duplicate: "在文件 发现问题 - 重复的图片配置 '' 请检查其他文件中是否存在相同配置" @@ -411,14 +415,14 @@ warning.config.selector.invalid_type: "在文件 中发现问题 warning.config.selector.invalid_target: "在文件 中发现问题 - 配置项 '' 使用了无效的选择器目标 ''" warning.config.resource_pack.item_model.already_exist: "无法为 '' 生成物品模型,因为文件 '' 已存在" warning.config.resource_pack.model.generation.already_exist: "无法生成模型,因为模型文件 '' 已存在" +warning.config.resource_pack.generation.malformatted_json: "Json文件 '' 格式错误" warning.config.resource_pack.generation.missing_font_texture: "字体''缺少必要纹理: ''" warning.config.resource_pack.generation.missing_model_texture: "模型''缺少纹理''" -warning.config.resource_pack.generation.texture_not_in_atlas: "纹理''不在图集内. 你需要将纹理路径或文件夹前缀添加到图集内,或者启用 config.yml 中的 'obfuscation' 选项" warning.config.resource_pack.generation.missing_item_model: "物品''缺少模型文件: ''" warning.config.resource_pack.generation.missing_block_model: "方块状态''缺少模型文件: ''" warning.config.resource_pack.generation.missing_parent_model: "模型''找不到父级模型文件: ''" -warning.config.resource_pack.generation.malformatted_json: "Json文件 '' 格式错误" warning.config.resource_pack.generation.missing_equipment_texture: "装备 '' 缺少纹理 ''" +warning.config.resource_pack.generation.texture_not_in_atlas: "纹理''不在图集内. 你需要将纹理路径或文件夹前缀添加到图集内,或者启用 config.yml 中的 'obfuscation' 选项" 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/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 956a55571..8bbcbb73b 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 @@ -52,6 +52,14 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract boolean isSneaking(); + public abstract boolean isSwimming(); + + public abstract boolean isClimbing(); + + public abstract boolean isGliding(); + + public abstract boolean isFlying(); + public abstract GameMode gameMode(); public abstract void setGameMode(GameMode gameMode); @@ -106,9 +114,9 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void performCommand(String command); - public abstract double luck(); + public abstract void performCommandAsEvent(String command); - public abstract boolean isFlying(); + public abstract double luck(); @Override public Key type() { 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 a5521169c..05ddbf39b 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 @@ -2,6 +2,8 @@ package net.momirealms.craftengine.core.plugin.context.condition; import net.momirealms.craftengine.core.util.Key; +import java.util.Set; + public final class CommonConditions { private CommonConditions() {} @@ -10,6 +12,8 @@ public final class CommonConditions { public static final Key ANY_OF = Key.of("craftengine:any_of"); public static final Key INVERTED = Key.of("craftengine:inverted"); public static final Key MATCH_ITEM = Key.of("craftengine:match_item"); + public static final Key MATCH_ENTITY_TYPE = Key.of("craftengine:match_entity_type"); + public static final Key MATCH_BLOCK_TYPE = Key.of("craftengine:match_block_type"); public static final Key MATCH_BLOCK_PROPERTY = Key.from("craftengine:match_block_property"); public static final Key TABLE_BONUS = Key.from("craftengine:table_bonus"); public static final Key SURVIVES_EXPLOSION = Key.from("craftengine:survives_explosion"); @@ -26,4 +30,18 @@ public final class CommonConditions { public static final Key EXPRESSION = Key.from("craftengine:expression"); public static final Key IS_NULL = Key.from("craftengine:is_null"); public static final Key HAND = Key.from("craftengine:hand"); + + public static boolean matchObject(Key key, boolean regexMatch, Set ids) { + String id = key.toString(); + if (regexMatch) { + for (String regex : ids) { + if (id.matches(regex)) { + return true; + } + } + } else { + return ids.contains(id); + } + return false; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java new file mode 100644 index 000000000..be8e8036e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -0,0 +1,50 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.block.ImmutableBlockState; +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; +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.world.BlockInWorld; + +import java.util.*; + +public class MatchBlockTypeCondition implements Condition { + private final Set ids; + private final boolean regexMatch; + + public MatchBlockTypeCondition(Collection ids, boolean regexMatch) { + this.ids = new HashSet<>(ids); + this.regexMatch = regexMatch; + } + + @Override + public Key type() { + return CommonConditions.MATCH_ENTITY_TYPE; + } + + @Override + public boolean test(CTX ctx) { + Optional block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); + if (block.isEmpty()) return false; + Optional customBlock = ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE); + Key key = customBlock.isPresent() ? customBlock.get().owner().value().id() : block.get().type(); + return CommonConditions.matchObject(key, this.regexMatch, this.ids); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + List ids = MiscUtils.getAsStringList(arguments.get("id")); + if (ids.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.condition.match_block_type.missing_id"); + } + boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); + return new MatchBlockTypeCondition<>(ids, regex); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java new file mode 100644 index 000000000..708bb4b41 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java @@ -0,0 +1,48 @@ + package net.momirealms.craftengine.core.plugin.context.condition; + + import net.momirealms.craftengine.core.entity.Entity; + 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; + 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 java.util.*; + + public class MatchEntityTypeCondition implements Condition { + private final Set ids; + private final boolean regexMatch; + + public MatchEntityTypeCondition(Collection ids, boolean regexMatch) { + this.ids = new HashSet<>(ids); + this.regexMatch = regexMatch; + } + + @Override + public Key type() { + return CommonConditions.MATCH_ENTITY_TYPE; + } + + @Override + public boolean test(CTX ctx) { + Optional entity = ctx.getOptionalParameter(DirectContextParameters.ENTITY); + if (entity.isEmpty()) return false; + Key key = entity.get().type(); + return CommonConditions.matchObject(key, this.regexMatch, this.ids); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + List ids = MiscUtils.getAsStringList(arguments.get("id")); + if (ids.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.condition.match_entity_type.missing_id"); + } + boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); + return new MatchEntityTypeCondition<>(ids, regex); + } + } + } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java index 45d14beac..41b14231a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java @@ -30,17 +30,7 @@ public class MatchItemCondition implements Condition { Optional> item = ctx.getOptionalParameter(DirectContextParameters.ITEM_IN_HAND); if (item.isEmpty()) return false; Key key = item.get().id(); - String itemId = key.toString(); - if (this.regexMatch) { - for (String regex : ids) { - if (itemId.matches(regex)) { - return true; - } - } - } else { - return this.ids.contains(itemId); - } - return false; + return CommonConditions.matchObject(key, this.regexMatch, this.ids); } public static class FactoryImpl implements ConditionFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java index e5fc50979..0e3eb288a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java @@ -17,6 +17,8 @@ public class EventConditions { static { register(CommonConditions.MATCH_ITEM, new MatchItemCondition.FactoryImpl<>()); + register(CommonConditions.MATCH_ENTITY_TYPE, new MatchEntityTypeCondition.FactoryImpl<>()); + register(CommonConditions.MATCH_BLOCK_TYPE, new MatchBlockTypeCondition.FactoryImpl<>()); register(CommonConditions.MATCH_BLOCK_PROPERTY, new MatchBlockPropertyCondition.FactoryImpl<>()); register(CommonConditions.TABLE_BONUS, new TableBonusCondition.FactoryImpl<>()); register(CommonConditions.SURVIVES_EXPLOSION, new SurvivesExplosionCondition.FactoryImpl<>()); 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 c27b2177f..ce444cd36 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 @@ -16,34 +16,34 @@ import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; +import java.util.function.Consumer; public class CommandFunction extends AbstractConditionalFunction { private final List command; - private final boolean asPlayer; private final PlayerSelector selector; + private final boolean asPlayer; + private final boolean asEvent; - public CommandFunction(List> predicates, @Nullable PlayerSelector selector, List command, boolean asPlayer) { + public CommandFunction(List> predicates, @Nullable PlayerSelector selector, List command, boolean asPlayer, boolean asEvent) { super(predicates); - this.asPlayer = asPlayer; this.command = command; this.selector = selector; + this.asPlayer = asPlayer; + this.asEvent = asEvent; } @Override public void runInternal(CTX ctx) { if (this.asPlayer) { if (this.selector == null) { - ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { - for (TextProvider c : this.command) { - it.performCommand(c.get(ctx)); - } - }); + ctx.getOptionalParameter(DirectContextParameters.PLAYER) + .ifPresent(player -> executeCommands( + ctx, this.asEvent ? player::performCommandAsEvent : player::performCommand + )); } else { for (Player viewer : this.selector.get(ctx)) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); - for (TextProvider c : this.command) { - viewer.performCommand(c.get(relationalContext)); - } + executeCommands(relationalContext, this.asEvent ? viewer::performCommandAsEvent : viewer::performCommand); } } } else { @@ -54,6 +54,12 @@ public class CommandFunction extends AbstractConditionalFun } } + private void executeCommands(Context ctx, Consumer executor) { + for (TextProvider c : this.command) { + executor.accept(c.get(ctx)); + } + } + @Override public Key type() { return CommonFunctions.COMMAND; @@ -70,7 +76,8 @@ public class CommandFunction extends AbstractConditionalFun Object command = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(arguments, "command", "commands"), "warning.config.function.command.missing_command"); List commands = MiscUtils.getAsStringList(command).stream().map(TextProviders::fromString).toList(); boolean asPlayer = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("as-player", false), "as-player"); - return new CommandFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), commands, asPlayer); + boolean asEvent = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("as-event", false), "as-event"); + return new CommandFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), commands, asPlayer, asEvent); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java index cf98e3774..92b3982c9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java @@ -57,8 +57,11 @@ public final class DirectContextParameters { public static final ContextKey ANCHOR_TYPE = ContextKey.direct("anchor_type"); public static final ContextKey HAND = ContextKey.direct("hand"); public static final ContextKey EVENT = ContextKey.direct("event"); - public static final ContextKey IS_FLYING = ContextKey.direct("is_flying"); public static final ContextKey IS_SNEAKING = ContextKey.direct("is_sneaking"); + public static final ContextKey IS_SWIMMING = ContextKey.direct("is_swimming"); + public static final ContextKey IS_CLIMBING = ContextKey.direct("is_climbing"); + public static final ContextKey IS_GLIDING = ContextKey.direct("is_gliding"); + public static final ContextKey IS_FLYING = ContextKey.direct("is_flying"); public static final ContextKey IS_CUSTOM = ContextKey.direct("is_custom"); public static final ContextKey IS_BLOCK_ITEM = ContextKey.direct("is_block_item"); public static final ContextKey GAMEMODE = ContextKey.direct("gamemode"); 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 7ed66aff2..25c3df127 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 @@ -29,8 +29,11 @@ public class PlayerParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Player::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); - CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_FLYING, Player::isFlying); CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_SNEAKING, Player::isSneaking); + CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_SWIMMING, Player::isSwimming); + CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_CLIMBING, Player::isClimbing); + CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_GLIDING, Player::isGliding); + CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_FLYING, Player::isFlying); CONTEXT_FUNCTIONS.put(DirectContextParameters.GAMEMODE, Player::gameMode); CONTEXT_FUNCTIONS.put(DirectContextParameters.MAIN_HAND_ITEM, p -> p.getItemInHand(InteractionHand.MAIN_HAND)); CONTEXT_FUNCTIONS.put(DirectContextParameters.OFF_HAND_ITEM, p -> p.getItemInHand(InteractionHand.OFF_HAND)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java index e48a6c429..a3174292d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java @@ -12,7 +12,8 @@ public enum SoundSource { NEUTRAL("neutral"), PLAYER("player"), AMBIENT("ambient"), - VOICE("voice"); + VOICE("voice"), + UI("ui"); private final String id; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java index 5dac8cf13..9ac55192d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.Nullable; public interface BlockInWorld { @@ -27,6 +28,8 @@ public interface BlockInWorld { World world(); + Key type(); + int x(); int y(); From 0bdfdcd40a667d5daf6f0ced4dc2e8897638e954 Mon Sep 17 00:00:00 2001 From: halogly Date: Sat, 23 Aug 2025 16:00:01 +0800 Subject: [PATCH 014/177] =?UTF-8?q?=E7=A7=BB=E5=8A=A8matchObject=E5=88=B0M?= =?UTF-8?q?iscUtil=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../context/condition/CommonConditions.java | 14 -------------- .../condition/MatchBlockTypeCondition.java | 2 +- .../condition/MatchEntityTypeCondition.java | 2 +- .../context/condition/MatchItemCondition.java | 2 +- .../craftengine/core/util/MiscUtils.java | 19 +++++++++++++++---- 5 files changed, 18 insertions(+), 21 deletions(-) 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 05ddbf39b..1283a89da 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 @@ -30,18 +30,4 @@ public final class CommonConditions { public static final Key EXPRESSION = Key.from("craftengine:expression"); public static final Key IS_NULL = Key.from("craftengine:is_null"); public static final Key HAND = Key.from("craftengine:hand"); - - public static boolean matchObject(Key key, boolean regexMatch, Set ids) { - String id = key.toString(); - if (regexMatch) { - for (String regex : ids) { - if (id.matches(regex)) { - return true; - } - } - } else { - return ids.contains(id); - } - return false; - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java index be8e8036e..b8d825437 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -32,7 +32,7 @@ public class MatchBlockTypeCondition implements Condition customBlock = ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE); Key key = customBlock.isPresent() ? customBlock.get().owner().value().id() : block.get().type(); - return CommonConditions.matchObject(key, this.regexMatch, this.ids); + return MiscUtils.matchObject(key, this.regexMatch, this.ids); } public static class FactoryImpl implements ConditionFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java index 708bb4b41..5f51872ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java @@ -30,7 +30,7 @@ Optional entity = ctx.getOptionalParameter(DirectContextParameters.ENTITY); if (entity.isEmpty()) return false; Key key = entity.get().type(); - return CommonConditions.matchObject(key, this.regexMatch, this.ids); + return MiscUtils.matchObject(key, this.regexMatch, this.ids); } public static class FactoryImpl implements ConditionFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java index 41b14231a..4ba82709f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java @@ -30,7 +30,7 @@ public class MatchItemCondition implements Condition { Optional> item = ctx.getOptionalParameter(DirectContextParameters.ITEM_IN_HAND); if (item.isEmpty()) return false; Key key = item.get().id(); - return CommonConditions.matchObject(key, this.regexMatch, this.ids); + return MiscUtils.matchObject(key, this.regexMatch, this.ids); } public static class FactoryImpl implements ConditionFactory { 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 54195dff8..77853bb18 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 @@ -4,10 +4,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce import org.joml.Quaternionf; import org.joml.Vector3f; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; public class MiscUtils { @@ -153,4 +150,18 @@ public class MiscUtils { return o; } } + + public static boolean matchObject(Key key, boolean regexMatch, Set ids) { + String id = key.toString(); + if (regexMatch) { + for (String regex : ids) { + if (id.matches(regex)) { + return true; + } + } + } else { + return ids.contains(id); + } + return false; + } } From bc3170738b05c211e5a68c550ac0d881eefee3b0 Mon Sep 17 00:00:00 2001 From: halogly Date: Sat, 23 Aug 2025 16:00:57 +0800 Subject: [PATCH 015/177] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8imp?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/plugin/context/condition/CommonConditions.java | 2 -- 1 file changed, 2 deletions(-) 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 1283a89da..445406954 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 @@ -2,8 +2,6 @@ package net.momirealms.craftengine.core.plugin.context.condition; import net.momirealms.craftengine.core.util.Key; -import java.util.Set; - public final class CommonConditions { private CommonConditions() {} From 719c2cd92e9193908046dd27cee113199870f29f Mon Sep 17 00:00:00 2001 From: halogly Date: Sat, 23 Aug 2025 17:15:33 +0800 Subject: [PATCH 016/177] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/translations/de.yml | 2 -- common-files/src/main/resources/translations/en.yml | 2 -- common-files/src/main/resources/translations/ru_ru.yml | 2 -- common-files/src/main/resources/translations/zh_cn.yml | 2 -- 4 files changed, 8 deletions(-) diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 99b377def..3e3ffe8b1 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -106,8 +106,6 @@ warning.config.condition.expression.missing_expression: "Problem in Date warning.config.condition.is_null.missing_argument: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'argument'-Argument für die 'is_null'-Bedingung." warning.config.condition.hand.missing_hand: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'hand'-Argument für die 'hand'-Bedingung." warning.config.condition.hand.invalid_hand: "Problem in Datei gefunden - Die Config '' verwendet ein ungültiges 'hand'-Argument '' für die 'hand'-Bedingung. Erlaubte Hand-Typen: []" -warning.config.condition.gamemode.missing_gamemode: "Problem in Datei gefunden - Der Konfiguration '' fehlt das erforderliche 'gamemode'-Argument für die 'gamemode'-Bedingung." -warning.config.condition.gamemode.invalid_gamemode: "Problem in Datei gefunden - Die Konfiguration '' verwendet ein ungültiges 'gamemode'-Argument '' für die 'gamemode'-Bedingung. Erlaubte Handtypen: []" warning.config.condition.on_cooldown.missing_id: "Problem in Datei gefunden - Bei der Config '' fehlt das erforderliche 'id'-Argument für die 'on_cooldown'-Bedingung." warning.config.structure.not_section: "Problem in Datei gefunden - Die Config '' wird als Config-Abschnitt erwartet, ist aber tatsächlich ein(e) ''." warning.config.image.duplicate: "Problem in Datei gefunden - Doppeltes Image ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 3a73bd5d4..eab94b9de 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -108,8 +108,6 @@ warning.config.condition.expression.missing_expression: "Issue found in warning.config.condition.is_null.missing_argument: "Issue found in file - The config '' is missing the required 'argument' argument for 'is_null' condition." warning.config.condition.hand.missing_hand: "Issue found in file - The config '' is missing the required 'hand' argument for 'hand' condition." warning.config.condition.hand.invalid_hand: "Issue found in file - The config '' is using an invalid 'hand' argument '' for 'hand' condition. Allowed hand types: []" -warning.config.condition.gamemode.missing_gamemode: "Issue found in file - The config '' is missing the required 'gamemode' argument for 'gamemode' condition." -warning.config.condition.gamemode.invalid_gamemode: "Issue found in file - The config '' is using an invalid 'gamemode' argument '' for 'gamemode' condition. Allowed gamemode types: []" warning.config.condition.on_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'on_cooldown' condition." warning.config.structure.not_section: "Issue found in file - The config '' is expected to be a config section while it's actually a(n) ''." warning.config.image.duplicate: "Issue found in file - Duplicated image ''. Please check if there is the same configuration in other files." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index 120af2f0e..60036cb99 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -104,8 +104,6 @@ warning.config.condition.expression.missing_expression: "Проблем warning.config.condition.is_null.missing_argument: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'argument' аргумент для 'is_null' состояния." warning.config.condition.hand.missing_hand: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'hand' аргумент для 'hand' состояния." warning.config.condition.hand.invalid_hand: "Проблема найдена в файле - В конфигурации '' использует недействительный 'hand' аргумент '' для 'hand' состояния. Допустимые типы рук: []" -warning.config.condition.gamemode.missing_gamemode: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'gamemode' аргумент для 'gamemode' состояния." -warning.config.condition.gamemode.invalid_gamemode: "Проблема найдена в файле - В конфигурации '' использует недействительный 'gamemode' аргумент '' для 'gamemode' состояния. Допустимые типы режимов игры: []" warning.config.condition.on_cooldown.missing_id: "Проблема найдена в файле - В конфигурации '' отсутствует необходимый 'id' аргумент для 'on_cooldown' состояния." warning.config.structure.not_section: "Проблема найдена в файле - В конфигурации '' ожидается, что это будет раздел конфигурации, хотя на самом деле это a(n) ''." warning.config.image.duplicate: "Проблема найдена в файле - Дублированное изображение ''. Проверьте, есть ли такая же конфигурация в других файлах." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 918c383b0..eeaff9665 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -108,8 +108,6 @@ warning.config.condition.expression.missing_expression: "在文件 在文件 发现问题 - 配置项 '' 缺少 'is_null' 条件的必需的 'argument' 参数" warning.config.condition.hand.missing_hand: "在文件 发现问题 - 配置项 '' 缺少 'hand' 条件必需的 'hand' 参数" warning.config.condition.hand.invalid_hand: "在文件 发现问题 - 配置项 '' 使用了无效的 'hand' 参数 ''('hand' 条件)。允许的手部类型: []" -warning.config.condition.gamemode.missing_gamemode: "在文件 发现问题 - 配置项 '' 缺少 'gamemode' 条件必需的 'gamemode' 参数" -warning.config.condition.gamemode.invalid_gamemode: "在文件 发现问题 - 配置项 '' 使用了无效的 'gamemode' 参数 ''('gamemode' 条件)。允许的游戏模式类型: []" warning.config.condition.on_cooldown.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'on_cooldown' 条件必需的 'id' 参数" warning.config.structure.not_section: "在文件 发现问题 - 配置项 '' 应为配置段落 但实际类型为 ''" warning.config.image.duplicate: "在文件 发现问题 - 重复的图片配置 '' 请检查其他文件中是否存在相同配置" From c8a6b3b8d5f9acd5866cb7a825c93701127de18e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 23 Aug 2025 18:28:07 +0800 Subject: [PATCH 017/177] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?= =?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/api/event/CraftEngineReloadEvent.java | 8 ++++++++ .../craftengine/bukkit/entity/BukkitEntity.java | 4 ++-- .../craftengine/bukkit/world/BukkitBlockInWorld.java | 7 +++++-- common-files/src/main/resources/config.yml | 2 +- .../craftengine/core/pack/AbstractPackManager.java | 1 + .../momirealms/craftengine/core/plugin/config/Config.java | 1 - .../plugin/context/condition/MatchBlockTypeCondition.java | 6 +----- .../context/condition/MatchEntityTypeCondition.java | 4 +--- .../core/plugin/context/condition/MatchItemCondition.java | 4 +--- .../craftengine/core/plugin/logger/Debugger.java | 2 +- .../net/momirealms/craftengine/core/util/MiscUtils.java | 3 +-- 11 files changed, 22 insertions(+), 20 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/CraftEngineReloadEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/CraftEngineReloadEvent.java index 58d5dea02..949299b48 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/CraftEngineReloadEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/CraftEngineReloadEvent.java @@ -8,9 +8,17 @@ import org.jetbrains.annotations.NotNull; public class CraftEngineReloadEvent extends Event { private static final HandlerList HANDLER_LIST = new HandlerList(); private final BukkitCraftEngine plugin; + private static boolean firstFlag = true; + private final boolean isFirstReload; public CraftEngineReloadEvent(BukkitCraftEngine plugin) { this.plugin = plugin; + this.isFirstReload = firstFlag; + firstFlag = false; + } + + public boolean isFirstReload() { + return this.isFirstReload; } public BukkitCraftEngine plugin() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java index 2193b86f9..caf677a3a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.bukkit.entity; -import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.bukkit.util.EntityUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.util.Direction; @@ -68,7 +68,7 @@ public class BukkitEntity extends AbstractEntity { @Override public Key type() { - return KeyUtils.namespacedKey2Key(literalObject().getType().getKey()); + return EntityUtils.getEntityType(literalObject()); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java index 2be28902a..49ec901f2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.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.plugin.reflection.minecraft.MFluids; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -63,7 +62,11 @@ public class BukkitBlockInWorld implements BlockInWorld { @Override public Key type() { - return KeyUtils.namespacedKey2Key(this.block.getType().getKey()); + CustomBlock customBlock = customBlock(); + if (customBlock == null) { + return BlockStateUtils.getBlockOwnerIdFromData(this.block.getBlockData()); + } + return customBlock.id(); } @Override diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 6a8a03355..cc87f1c59 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -377,7 +377,7 @@ chunk-system: # Settings for injection injection: # Requires a restart to apply. - # SECTION: Inject the LevelChunkSection + # SECTION: Inject the LevelChunkSection (Use this if you have installed both FastAsyncWorldEdit and Axiom) # PALETTE: Inject the PalettedContainer target: PALETTE # Enables faster injection method 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 c8c09139c..99313220e 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,6 +125,7 @@ public abstract class AbstractPackManager implements PackManager { loadInternalList("textures", "", VANILLA_TEXTURES::add); VANILLA_MODELS.add(Key.of("minecraft", "builtin/entity")); + VANILLA_MODELS.add(Key.of("minecraft", "item/player_head")); for (int i = 0; i < 256; i++) { VANILLA_TEXTURES.add(Key.of("minecraft", "font/unicode_page_" + String.format("%02x", i))); } 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 18e0bf089..d1268aca1 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 @@ -420,7 +420,6 @@ public class Config { emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true); emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); - firstTime = false; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java index b8d825437..515166d68 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.plugin.context.condition; -import net.momirealms.craftengine.core.block.ImmutableBlockState; 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; @@ -29,10 +28,7 @@ public class MatchBlockTypeCondition implements Condition block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); - if (block.isEmpty()) return false; - Optional customBlock = ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE); - Key key = customBlock.isPresent() ? customBlock.get().owner().value().id() : block.get().type(); - return MiscUtils.matchObject(key, this.regexMatch, this.ids); + return block.filter(blockInWorld -> MiscUtils.matchRegex(blockInWorld.type().asString(), this.ids, this.regexMatch)).isPresent(); } public static class FactoryImpl implements ConditionFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java index 5f51872ab..2ec39b86e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java @@ -28,9 +28,7 @@ @Override public boolean test(CTX ctx) { Optional entity = ctx.getOptionalParameter(DirectContextParameters.ENTITY); - if (entity.isEmpty()) return false; - Key key = entity.get().type(); - return MiscUtils.matchObject(key, this.regexMatch, this.ids); + return entity.filter(value -> MiscUtils.matchRegex(value.type().asString(), this.ids, this.regexMatch)).isPresent(); } public static class FactoryImpl implements ConditionFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java index 4ba82709f..f6c7f78a8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchItemCondition.java @@ -28,9 +28,7 @@ public class MatchItemCondition implements Condition { @Override public boolean test(CTX ctx) { Optional> item = ctx.getOptionalParameter(DirectContextParameters.ITEM_IN_HAND); - if (item.isEmpty()) return false; - Key key = item.get().id(); - return MiscUtils.matchObject(key, this.regexMatch, this.ids); + return item.filter(value -> MiscUtils.matchRegex(value.id().asString(), this.ids, this.regexMatch)).isPresent(); } public static class FactoryImpl implements ConditionFactory { 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 4ca696c84..fba7f7fa9 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 @@ -9,7 +9,7 @@ public enum Debugger { COMMON(Config::debugCommon), PACKET(Config::debugPacket), FURNITURE(Config::debugFurniture), - RESOURCE_PACK(Config::debugFurniture), + RESOURCE_PACK(Config::debugResourcePack), ITEM(Config::debugItem); private final Supplier condition; 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 77853bb18..ed0c90d9c 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 @@ -151,8 +151,7 @@ public class MiscUtils { } } - public static boolean matchObject(Key key, boolean regexMatch, Set ids) { - String id = key.toString(); + public static boolean matchRegex(String id, Set ids, boolean regexMatch) { if (regexMatch) { for (String regex : ids) { if (id.matches(regex)) { From 42f890a6bfa0ea820df0e6e425bd442be39dbd5d Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 23 Aug 2025 20:57:08 +0800 Subject: [PATCH 018/177] 0.0.62.3 --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index fca0f2432..0778f33c5 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.2 +project_version=0.0.62.3 config_version=45 -lang_version=24 +lang_version=25 project_group=net.momirealms latest_supported_version=1.21.8 From e4f4324a31261ad5ec765073a4d07ab0cd8d3cce Mon Sep 17 00:00:00 2001 From: halogly Date: Sat, 23 Aug 2025 21:34:28 +0800 Subject: [PATCH 019/177] =?UTF-8?q?=E4=BF=AE=E6=AD=A3MatchBlockTypeConditi?= =?UTF-8?q?on=EF=BC=9B=E6=B8=85=E9=99=A4MatchEntityTypeCondition=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84=E7=BC=A9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../condition/MatchBlockTypeCondition.java | 2 +- .../condition/MatchEntityTypeCondition.java | 74 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java index 515166d68..7a08fd238 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -22,7 +22,7 @@ public class MatchBlockTypeCondition implements Condition implements Condition { - private final Set ids; - private final boolean regexMatch; +public class MatchEntityTypeCondition implements Condition { + private final Set ids; + private final boolean regexMatch; - public MatchEntityTypeCondition(Collection ids, boolean regexMatch) { - this.ids = new HashSet<>(ids); - this.regexMatch = regexMatch; - } + public MatchEntityTypeCondition(Collection ids, boolean regexMatch) { + this.ids = new HashSet<>(ids); + this.regexMatch = regexMatch; + } + + @Override + public Key type() { + return CommonConditions.MATCH_ENTITY_TYPE; + } + + @Override + public boolean test(CTX ctx) { + Optional entity = ctx.getOptionalParameter(DirectContextParameters.ENTITY); + return entity.filter(value -> MiscUtils.matchRegex(value.type().asString(), this.ids, this.regexMatch)).isPresent(); + } + + public static class FactoryImpl implements ConditionFactory { @Override - public Key type() { - return CommonConditions.MATCH_ENTITY_TYPE; - } - - @Override - public boolean test(CTX ctx) { - Optional entity = ctx.getOptionalParameter(DirectContextParameters.ENTITY); - return entity.filter(value -> MiscUtils.matchRegex(value.type().asString(), this.ids, this.regexMatch)).isPresent(); - } - - public static class FactoryImpl implements ConditionFactory { - - @Override - public Condition create(Map arguments) { - List ids = MiscUtils.getAsStringList(arguments.get("id")); - if (ids.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.condition.match_entity_type.missing_id"); - } - boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); - return new MatchEntityTypeCondition<>(ids, regex); + public Condition create(Map arguments) { + List ids = MiscUtils.getAsStringList(arguments.get("id")); + if (ids.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.condition.match_entity_type.missing_id"); } + boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); + return new MatchEntityTypeCondition<>(ids, regex); } } +} From 6f4cd1e501bb5ed41ad9c799f78ae2c4a2e4890d Mon Sep 17 00:00:00 2001 From: halogly Date: Sat, 23 Aug 2025 23:39:42 +0800 Subject: [PATCH 020/177] =?UTF-8?q?=E6=81=A2=E5=A4=8DMatchBlockTypeConditi?= =?UTF-8?q?on=E8=AF=86=E5=88=ABce=E8=87=AA=E5=AE=9A=E4=B9=89=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/context/condition/MatchBlockTypeCondition.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java index 7a08fd238..08d3afe48 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.plugin.context.condition; +import net.momirealms.craftengine.core.block.ImmutableBlockState; 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; @@ -28,7 +29,12 @@ public class MatchBlockTypeCondition implements Condition block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); - return block.filter(blockInWorld -> MiscUtils.matchRegex(blockInWorld.type().asString(), this.ids, this.regexMatch)).isPresent(); + Optional customBlock = ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE); + return block.filter(blockInWorld -> { + String key = customBlock.map(immutableBlockState -> + immutableBlockState.owner().value().id().asString()).orElseGet(() -> blockInWorld.type().asString()); + return MiscUtils.matchRegex(key, this.ids, this.regexMatch); + }).isPresent(); } public static class FactoryImpl implements ConditionFactory { From 2697f69730d5c5b28f75a20885e718c12acc6c6e Mon Sep 17 00:00:00 2001 From: halogly Date: Sun, 24 Aug 2025 13:34:16 +0800 Subject: [PATCH 021/177] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=B6=E5=9B=BE?= =?UTF-8?q?=E5=8F=B0=E4=BA=A4=E4=BA=92=E5=88=A4=E6=96=AD=EF=BC=88=E9=81=97?= =?UTF-8?q?=E6=BC=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/momirealms/craftengine/bukkit/util/InteractUtils.java | 1 + .../java/net/momirealms/craftengine/core/block/BlockKeys.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java index d16537def..dee6874b7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java @@ -169,6 +169,7 @@ public final class InteractUtils { registerInteraction(BlockKeys.DAMAGED_ANVIL, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.FURNACE, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.CRAFTING_TABLE, (player, item, blockState, result) -> true); + registerInteraction(BlockKeys.CARTOGRAPHY_TABLE, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.STONECUTTER, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.SMITHING_TABLE, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.LOOM, (player, item, blockState, result) -> true); 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 6f5daecc9..c6b9cd591 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 @@ -8,6 +8,7 @@ public final class BlockKeys { 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"); + public static final Key CARTOGRAPHY_TABLE = Key.of("minecraft:cartography_table"); public static final Key STONECUTTER = Key.of("minecraft:stonecutter"); public static final Key BELL = Key.of("minecraft:bell"); public static final Key SMITHING_TABLE = Key.of("minecraft:smithing_table"); @@ -176,8 +177,6 @@ public final class BlockKeys { public static final Key WAXED_OXIDIZED_COPPER_TRAPDOOR = Key.of("minecraft:waxed_oxidized_copper_trapdoor"); public static final Key WAXED_WEATHERED_COPPER_TRAPDOOR = Key.of("minecraft:waxed_weathered_copper_trapdoor"); - - public static final Key OAK_FENCE_GATE = Key.of("minecraft:oak_fence_gate"); public static final Key SPRUCE_FENCE_GATE = Key.of("minecraft:spruce_fence_gate"); public static final Key BIRCH_FENCE_GATE = Key.of("minecraft:birch_fence_gate"); From 8fd9a739ef28ac8ce393c2adb1f854c4dfd31641 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 24 Aug 2025 17:41:25 +0800 Subject: [PATCH 022/177] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AConPlace=20onRemove?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/PressurePlateBlockBehavior.java | 1 - .../bukkit/plugin/injector/BlockGenerator.java | 9 +-------- .../reflection/minecraft/CoreReflections.java | 15 +++++++++------ 3 files changed, 10 insertions(+), 15 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 ce4f40b47..605a84bbb 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 @@ -105,7 +105,6 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { if (signalForState == 0) { this.checkPressed(args[3], args[1], args[2], state, signalForState, thisBlock); } else { - // todo 为什么 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/BlockGenerator.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BlockGenerator.java index b42ab2ccd..c8908a393 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 @@ -106,14 +106,7 @@ 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) 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 b7672a23c..3ccd7553c 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 @@ -3458,6 +3458,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" @@ -4005,15 +4010,13 @@ 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 = VersionHelper.isOrAbove1_21_5() ? null : - ReflectionUtils.getDeclaredMethod( - clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$BlockState, boolean.class + public static final Method method$BlockBehaviour$onRemove = MiscUtils.requireNonNullIf( + 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; From 4d7a7ff4731de9a0e0b77e16b8cd0a38478961ea Mon Sep 17 00:00:00 2001 From: halogly Date: Sun, 24 Aug 2025 17:43:16 +0800 Subject: [PATCH 023/177] =?UTF-8?q?=E6=92=A4=E5=9B=9E=E8=BF=99=E4=B8=AA?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=20:)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/context/condition/MatchBlockTypeCondition.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java index 08d3afe48..7a08fd238 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.plugin.context.condition; -import net.momirealms.craftengine.core.block.ImmutableBlockState; 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; @@ -29,12 +28,7 @@ public class MatchBlockTypeCondition implements Condition block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); - Optional customBlock = ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE); - return block.filter(blockInWorld -> { - String key = customBlock.map(immutableBlockState -> - immutableBlockState.owner().value().id().asString()).orElseGet(() -> blockInWorld.type().asString()); - return MiscUtils.matchRegex(key, this.ids, this.regexMatch); - }).isPresent(); + return block.filter(blockInWorld -> MiscUtils.matchRegex(blockInWorld.type().asString(), this.ids, this.regexMatch)).isPresent(); } public static class FactoryImpl implements ConditionFactory { From 0ed58de56462b49f9958640468934e97a24afa68 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 24 Aug 2025 19:40:40 +0800 Subject: [PATCH 024/177] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/injector/BlockGenerator.java | 42 +++++-------------- .../reflection/minecraft/CoreReflections.java | 41 ++++++++++++------ 2 files changed, 39 insertions(+), 44 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 c8908a393..059ce70fd 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 @@ -109,44 +109,18 @@ public final class BlockGenerator { .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)) @@ -177,15 +151,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 = 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 = 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)) 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 3ccd7553c..55ae08a45 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) ); @@ -1663,6 +1674,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")) ); @@ -1685,6 +1705,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) ); @@ -2969,13 +2997,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) ); @@ -3620,12 +3641,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"); From 9e660e81305be4d7a95bef54624d1e55d9723c9d Mon Sep 17 00:00:00 2001 From: ThLion228 Date: Sun, 24 Aug 2025 23:29:10 +0500 Subject: [PATCH 025/177] . --- .../momirealms/craftengine/bukkit/block/BlockEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7052fc484..0a6f76854 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 @@ -198,7 +198,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(), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + block.getWorld().playSound(block.getLocation().add(0.5D, 0.5D, 0.5D), 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); } From c5de57cc0f0140193f3bc30ba7b7a858e4554ca3 Mon Sep 17 00:00:00 2001 From: ThLion228 Date: Sun, 24 Aug 2025 23:29:18 +0500 Subject: [PATCH 026/177] . --- .../momirealms/craftengine/bukkit/block/BlockEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0a6f76854..4cbc36cf1 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 @@ -99,7 +99,7 @@ public final class BlockEventListener implements Listener { 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(), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + player.playSound(block.getLocation().add(0.5D, 0.5D, 0.5D), 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); } From 1ef26e5e305225603facd59ab59d35599c76e917 Mon Sep 17 00:00:00 2001 From: ThLion228 Date: Sun, 24 Aug 2025 23:29:22 +0500 Subject: [PATCH 027/177] . --- .../momirealms/craftengine/bukkit/block/BlockEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4cbc36cf1..91b46b5f4 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 @@ -82,7 +82,7 @@ public final class BlockEventListener implements Listener { try { Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); Object placeSound = CoreReflections.field$SoundType$placeSound.get(soundType); - player.playSound(block.getLocation(), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + player.playSound(block.getLocation().add(0.5D, 0.5D, 0.5D), 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); } From 8d648f7d1bcb4fd8061ddce0b14078c5a5564b41 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 03:34:01 +0800 Subject: [PATCH 028/177] =?UTF-8?q?=E5=85=A8=E6=96=B0=E6=80=9D=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/item/recipe/BukkitRecipeManager.java | 9 ++------- gradle.properties | 2 +- 2 files changed, 3 insertions(+), 8 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 cb691ab5c..11cd5b5a1 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 @@ -72,37 +72,31 @@ public class BukkitRecipeManager extends AbstractRecipeManager { RecipeSerializers.SHAPED, recipe -> { CustomShapedRecipe shapedRecipe = (CustomShapedRecipe) recipe; Object mcRecipe = FastNMS.INSTANCE.createShapedRecipe(shapedRecipe); - modifyShapedRecipeIngredients(shapedRecipe, mcRecipe); return MINECRAFT_RECIPE_ADDER.apply(recipe.id(), mcRecipe); }, RecipeSerializers.SHAPELESS, recipe -> { CustomShapelessRecipe shapelessRecipe = (CustomShapelessRecipe) recipe; Object mcRecipe = FastNMS.INSTANCE.createShapelessRecipe(shapelessRecipe); - modifyShapelessRecipeIngredients(shapelessRecipe, mcRecipe); return MINECRAFT_RECIPE_ADDER.apply(recipe.id(), mcRecipe); }, RecipeSerializers.SMELTING, recipe -> { CustomSmeltingRecipe smeltingRecipe = (CustomSmeltingRecipe) recipe; Object mcRecipe = FastNMS.INSTANCE.createSmeltingRecipe(smeltingRecipe); - modifyCookingRecipeIngredient(smeltingRecipe, mcRecipe); return MINECRAFT_RECIPE_ADDER.apply(recipe.id(), mcRecipe); }, RecipeSerializers.BLASTING, recipe -> { CustomBlastingRecipe blastingRecipe = (CustomBlastingRecipe) recipe; Object mcRecipe = FastNMS.INSTANCE.createBlastingRecipe(blastingRecipe); - modifyCookingRecipeIngredient(blastingRecipe, mcRecipe); return MINECRAFT_RECIPE_ADDER.apply(recipe.id(), mcRecipe); }, RecipeSerializers.SMOKING, recipe -> { CustomSmokingRecipe smokingRecipe = (CustomSmokingRecipe) recipe; Object mcRecipe = FastNMS.INSTANCE.createSmokingRecipe(smokingRecipe); - modifyCookingRecipeIngredient(smokingRecipe, mcRecipe); return MINECRAFT_RECIPE_ADDER.apply(recipe.id(), mcRecipe); }, RecipeSerializers.CAMPFIRE_COOKING, recipe -> { CustomCampfireRecipe campfireRecipe = (CustomCampfireRecipe) recipe; Object mcRecipe = FastNMS.INSTANCE.createCampfireRecipe(campfireRecipe); - modifyCookingRecipeIngredient(campfireRecipe, mcRecipe); return MINECRAFT_RECIPE_ADDER.apply(recipe.id(), mcRecipe); }, RecipeSerializers.STONECUTTING, recipe -> { @@ -201,7 +195,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { } } - private static List getIngredientLooks(List holders) { + public static List getIngredientLooks(List holders) { List itemStacks = new ArrayList<>(); for (UniqueKey holder : holders) { Optional> buildableItem = BukkitItemManager.instance().getBuildableItem(holder.key()); @@ -213,6 +207,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { Item barrier = BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, null); assert barrier != null; barrier.customNameJson(AdventureHelper.componentToJson(Component.text(holder.key().asString()).color(NamedTextColor.RED))); + itemStacks.add(barrier.getLiteralObject()); } } return itemStacks; diff --git a/gradle.properties b/gradle.properties index 0778f33c5..c18c2f5aa 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.19 -nms_helper_version=1.0.59 +nms_helper_version=1.0.63 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From c1e6410d476b336e8e74043902466430edeed23b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 15:49:15 +0800 Subject: [PATCH 029/177] =?UTF-8?q?=E6=9B=B4=E5=A5=BD=E7=9A=84match=20prop?= =?UTF-8?q?erty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../worldedit/WorldEditBlockRegister.java | 2 +- .../bukkit/api/BukkitAdaptors.java | 6 +- .../bukkit/api/CraftEngineBlocks.java | 4 +- .../bukkit/block/BlockEventListener.java | 18 ++--- .../bukkit/block/BukkitBlockManager.java | 10 +-- .../bukkit/block/BukkitBlockStateWrapper.java | 23 +++++++ .../bukkit/block/BukkitCustomBlock.java | 16 ++--- .../block/behavior/BukkitBlockBehavior.java | 20 +++--- .../behavior/ConcretePowderBlockBehavior.java | 2 +- .../block/behavior/CropBlockBehavior.java | 8 +-- .../block/behavior/DoorBlockBehavior.java | 6 +- .../behavior/DoubleHighBlockBehavior.java | 2 +- .../behavior/FenceGateBlockBehavior.java | 8 +-- .../block/behavior/GrassBlockBehavior.java | 8 +-- .../block/behavior/LampBlockBehavior.java | 4 +- .../block/behavior/LeavesBlockBehavior.java | 4 +- .../behavior/PressurePlateBlockBehavior.java | 2 +- .../block/behavior/SaplingBlockBehavior.java | 4 +- .../behavior/StackableBlockBehavior.java | 2 +- .../block/behavior/StairsBlockBehavior.java | 2 +- .../block/behavior/TrapDoorBlockBehavior.java | 6 +- .../behavior/VerticalCropBlockBehavior.java | 8 +-- .../bukkit/item/behavior/AxeItemBehavior.java | 10 +-- .../item/behavior/BlockItemBehavior.java | 8 +-- .../behavior/CompostableItemBehavior.java | 4 +- .../behavior/FlintAndSteelItemBehavior.java | 12 ++-- .../item/listener/ItemEventListener.java | 14 ++-- .../DebugGetBlockInternalIdCommand.java | 2 +- .../plugin/injector/BlockStateGenerator.java | 4 +- .../plugin/injector/LootEntryInjector.java | 2 + .../plugin/injector/WorldStorageInjector.java | 6 +- .../plugin/network/PacketConsumers.java | 4 +- .../plugin/user/BukkitServerPlayer.java | 4 +- .../bukkit/util/BlockStateUtils.java | 11 ++-- .../bukkit/util/ParticleUtils.java | 2 +- ...kInWorld.java => BukkitExistingBlock.java} | 39 +++++++++-- .../craftengine/bukkit/world/BukkitWorld.java | 6 +- .../bukkit/world/BukkitWorldManager.java | 4 +- .../core/block/AbstractBlockManager.java | 4 +- .../core/block/AbstractCustomBlock.java | 4 +- .../craftengine/core/block/BlockManager.java | 2 +- .../core/block/BlockStateWrapper.java | 66 +------------------ .../core/block/ImmutableBlockState.java | 8 +-- .../block/state/StatePropertyAccessor.java | 14 ++++ .../MatchBlockPropertyCondition.java | 48 +++++++++++--- .../condition/MatchBlockTypeCondition.java | 4 +- .../context/function/ParticleFunction.java | 2 +- .../context/function/PlaceBlockFunction.java | 2 +- .../parameter/BlockParameterProvider.java | 28 ++++---- .../parameter/DirectContextParameters.java | 4 +- .../core/plugin/locale/I18NData.java | 2 +- .../{BlockInWorld.java => ExistingBlock.java} | 13 +++- .../craftengine/core/world/World.java | 4 +- gradle.properties | 2 +- 54 files changed, 272 insertions(+), 232 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/{BukkitBlockInWorld.java => BukkitExistingBlock.java} (67%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/StatePropertyAccessor.java rename core/src/main/java/net/momirealms/craftengine/core/world/{BlockInWorld.java => ExistingBlock.java} (69%) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java index 5b702192b..be5b1f2ed 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java @@ -90,7 +90,7 @@ public class WorldEditBlockRegister { if (state == null) return null; try { - String id = state.customBlockState().handle().toString(); + String id = state.customBlockState().literalObject().toString(); int first = id.indexOf('{'); int last = id.indexOf('}'); if (first != -1 && last != -1 && last > first) { 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 3986509cf..6dcd0167a 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 @@ -4,7 +4,7 @@ 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.BukkitBlockInWorld; +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; @@ -29,8 +29,8 @@ public final class BukkitAdaptors { return new BukkitEntity(entity); } - public static BukkitBlockInWorld adapt(final Block block) { - return new BukkitBlockInWorld(block); + public static BukkitExistingBlock adapt(final Block block) { + return new BukkitExistingBlock(block); } public static Location toLocation(WorldPosition position) { 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 ffddee806..678f1b710 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 @@ -126,7 +126,7 @@ public final class CraftEngineBlocks { boolean success; Object worldServer = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()); Object blockPos = FastNMS.INSTANCE.constructor$BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - Object blockState = block.customBlockState().handle(); + Object blockState = block.customBlockState().literalObject(); Object oldBlockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(worldServer, blockPos); success = FastNMS.INSTANCE.method$LevelWriter$setBlock(worldServer, blockPos, blockState, option.flags()); if (success) { @@ -249,6 +249,6 @@ public final class CraftEngineBlocks { */ @NotNull public static BlockData getBukkitBlockData(@NotNull ImmutableBlockState blockState) { - return BlockStateUtils.fromBlockData(blockState.customBlockState().handle()); + return BlockStateUtils.fromBlockData(blockState.customBlockState().literalObject()); } } 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 91b46b5f4..24da42748 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 @@ -9,7 +9,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflect import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.*; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; @@ -82,7 +82,7 @@ public final class BlockEventListener implements Listener { try { Object soundType = CoreReflections.field$BlockBehaviour$soundType.get(ownerBlock); Object placeSound = CoreReflections.field$SoundType$placeSound.get(soundType); - player.playSound(block.getLocation().add(0.5D, 0.5D, 0.5D), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + 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); } @@ -99,7 +99,7 @@ public final class BlockEventListener implements Listener { 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.5D, 0.5D, 0.5D), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + 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); } @@ -124,7 +124,7 @@ public final class BlockEventListener implements Listener { Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); optionalCustomItem.get().execute( PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.POSITION, position) .withParameter(DirectContextParameters.PLAYER, serverPlayer) .withParameter(DirectContextParameters.EVENT, cancellable) @@ -156,7 +156,7 @@ public final class BlockEventListener implements Listener { // execute functions Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) .withParameter(DirectContextParameters.EVENT, cancellable) .withParameter(DirectContextParameters.POSITION, position) @@ -179,7 +179,7 @@ public final class BlockEventListener implements Listener { event.setExpToDrop(0); } ContextHolder lootContext = ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.POSITION, position) .withParameter(DirectContextParameters.PLAYER, serverPlayer) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand).build(); @@ -198,7 +198,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.5D, 0.5D, 0.5D), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + block.getWorld().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); } @@ -224,7 +224,7 @@ public final class BlockEventListener implements Listener { WorldPosition position = new WorldPosition(world, location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5); ContextHolder.Builder builder = ContextHolder.builder() .withParameter(DirectContextParameters.POSITION, position) - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)); + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)); for (LootTable lootTable : it.lootTables()) { for (Item item : lootTable.getRandomItems(builder.build(), world, null)) { world.dropItemNaturally(position, item); @@ -250,7 +250,7 @@ public final class BlockEventListener implements Listener { state.owner().value().execute(PlayerOptionalContext.of(BukkitAdaptors.adapt(player), ContextHolder.builder() .withParameter(DirectContextParameters.EVENT, cancellable) .withParameter(DirectContextParameters.POSITION, new WorldPosition(new BukkitWorld(event.getWorld()), LocationUtils.toVec3d(location))) - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) ), EventTrigger.STEP); if (cancellable.isCancelled()) { 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 d3c7709fd..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 @@ -181,14 +181,14 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Nullable @Override - public BlockStateWrapper createPackedBlockState(String blockState) { + public BlockStateWrapper createBlockState(String blockState) { ImmutableBlockState state = BlockStateParser.deserialize(blockState); if (state != null) { return state.customBlockState(); } try { BlockData blockData = Bukkit.createBlockData(blockState); - return BlockStateUtils.toPackedBlockState(blockData); + return BlockStateUtils.toBlockStateWrapper(blockData); } catch (IllegalArgumentException e) { return null; } @@ -231,7 +231,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public Key getBlockOwnerId(BlockStateWrapper state) { - return BlockStateUtils.getBlockOwnerIdFromState(state.handle()); + return BlockStateUtils.getBlockOwnerIdFromState(state.literalObject()); } @Override @@ -258,9 +258,9 @@ public final class BukkitBlockManager extends AbstractBlockManager { int size = RegistryUtils.currentBlockRegistrySize(); BlockStateWrapper[] states = new BlockStateWrapper[size]; for (int i = 0; i < size; i++) { - states[i] = BlockStateWrapper.create(BlockStateUtils.idToBlockState(i), i, BlockStateUtils.isVanillaBlock(i)); + states[i] = new BukkitBlockStateWrapper(BlockStateUtils.idToBlockState(i), i); } - BlockRegistryMirror.init(states, BlockStateWrapper.vanilla(MBlocks.STONE$defaultState, BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState))); + BlockRegistryMirror.init(states, new BukkitBlockStateWrapper(MBlocks.STONE$defaultState, BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState))); } private void registerEmptyBlock() { 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 new file mode 100644 index 000000000..cafb79317 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockStateWrapper.java @@ -0,0 +1,23 @@ +package net.momirealms.craftengine.bukkit.block; + +import net.momirealms.craftengine.core.block.BlockStateWrapper; + +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; + } +} 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 7f0506438..84f1cbfe3 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 @@ -82,7 +82,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { CraftEngine.instance().logger().warn("Could not find custom block immutableBlockState for " + immutableBlockState + ". This might cause errors!"); continue; } - DelegatingBlockState nmsState = (DelegatingBlockState) immutableBlockState.customBlockState().handle(); + DelegatingBlockState nmsState = (DelegatingBlockState) immutableBlockState.customBlockState().literalObject(); nmsState.setBlockState(immutableBlockState); BlockSettings settings = immutableBlockState.settings(); @@ -98,10 +98,10 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { 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().handle()) : settings.canOcclude().asBoolean(); + 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().handle()) : settings.useShapeForLightOcclusion().asBoolean(); + 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); @@ -111,7 +111,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { // set parent block properties DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState); ObjectHolder shapeHolder = nmsBlock.shapeDelegate(); - shapeHolder.bindValue(new BukkitBlockShape(immutableBlockState.vanillaBlockState().handle(), Optional.ofNullable(immutableBlockState.settings().supportShapeBlockState()).map(it -> { + 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)) { @@ -140,7 +140,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { } // modify cache if (VersionHelper.isOrAbove1_21_2()) { - int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$lightBlock.getInt(immutableBlockState.vanillaBlockState().handle()); + 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 @@ -149,11 +149,11 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { } 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().handle())); + 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().handle())); + 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 @@ -162,7 +162,7 @@ public final class BukkitCustomBlock extends AbstractCustomBlock { } 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().handle()))); + 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); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java index bd8b082c9..ea36861b8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java @@ -31,11 +31,11 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { Direction.Axis axis = blockState.get(axisProperty); return switch (rotation) { case COUNTERCLOCKWISE_90, CLOCKWISE_90 -> switch (axis) { - case X -> blockState.with(axisProperty, Direction.Axis.Z).customBlockState().handle(); - case Z -> blockState.with(axisProperty, Direction.Axis.X).customBlockState().handle(); - default -> blockState.customBlockState().handle(); + case X -> blockState.with(axisProperty, Direction.Axis.Z).customBlockState().literalObject(); + case Z -> blockState.with(axisProperty, Direction.Axis.X).customBlockState().literalObject(); + default -> blockState.customBlockState().literalObject(); }; - default -> blockState.customBlockState().handle(); + default -> blockState.customBlockState().literalObject(); }; }; }); @@ -45,7 +45,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { Property directionProperty = (Property) property; behavior.rotateFunction = (thisBlock, blockState, rotation) -> blockState.with(directionProperty, rotation.rotate(blockState.get(directionProperty).toDirection()).toHorizontalDirection()) - .customBlockState().handle(); + .customBlockState().literalObject(); behavior.mirrorFunction = (thisBlock, blockState, mirror) -> { Rotation rotation = mirror.getRotation(blockState.get(directionProperty).toDirection()); return behavior.rotateFunction.rotate(thisBlock, blockState, rotation); @@ -55,7 +55,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { Property directionProperty = (Property) property; behavior.rotateFunction = (thisBlock, blockState, rotation) -> blockState.with(directionProperty, rotation.rotate(blockState.get(directionProperty))) - .customBlockState().handle(); + .customBlockState().literalObject(); behavior.mirrorFunction = (thisBlock, blockState, mirror) -> { Rotation rotation = mirror.getRotation(blockState.get(directionProperty)); return behavior.rotateFunction.rotate(thisBlock, blockState, rotation); @@ -68,7 +68,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { Property directionProperty = (Property) property; behavior.rotateFunction = (thisBlock, blockState, rotation) -> blockState.with(directionProperty, rotation.rotate(blockState.get(directionProperty).toDirection()).toHorizontalDirection()) - .customBlockState().handle(); + .customBlockState().literalObject(); behavior.mirrorFunction = (thisBlock, blockState, mirror) -> { Rotation rotation = mirror.getRotation(blockState.get(directionProperty).toDirection()); return behavior.rotateFunction.rotate(thisBlock, blockState, rotation); @@ -139,7 +139,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { if (optionalCustomState.isEmpty()) return CoreReflections.instance$ItemStack$EMPTY; ImmutableBlockState immutableBlockState = optionalCustomState.get(); if (immutableBlockState.get(this.waterloggedProperty)) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(world, pos, immutableBlockState.with(this.waterloggedProperty, false).customBlockState().handle(), 3); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, pos, immutableBlockState.with(this.waterloggedProperty, false).customBlockState().literalObject(), 3); return FastNMS.INSTANCE.constructor$ItemStack(MItems.WATER_BUCKET, 1); } return CoreReflections.instance$ItemStack$EMPTY; @@ -154,7 +154,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { ImmutableBlockState immutableBlockState = optionalCustomState.get(); Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(args[3]); if (!immutableBlockState.get(this.waterloggedProperty) && fluidType == MFluids.WATER) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(args[0], args[1], immutableBlockState.with(this.waterloggedProperty, true).customBlockState().handle(), 3); + FastNMS.INSTANCE.method$LevelWriter$setBlock(args[0], args[1], immutableBlockState.with(this.waterloggedProperty, true).customBlockState().literalObject(), 3); FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[0], args[1], fluidType, 5); return true; } @@ -182,6 +182,6 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { if (optionalCustomState.isEmpty()) return false; BlockStateWrapper vanillaState = optionalCustomState.get().vanillaBlockState(); if (vanillaState == null) return false; - return FastNMS.INSTANCE.method$BlockStateBase$isPathFindable(vanillaState.handle(), VersionHelper.isOrAbove1_20_5() ? null : args[1], VersionHelper.isOrAbove1_20_5() ? null : args[2], args[isPathFindable$type]); + return FastNMS.INSTANCE.method$BlockStateBase$isPathFindable(vanillaState.literalObject(), VersionHelper.isOrAbove1_20_5() ? null : args[1], VersionHelper.isOrAbove1_20_5() ? null : args[2], args[isPathFindable$type]); } } 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 f1dc2529e..aa872018e 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 @@ -48,7 +48,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { Optional optionalCustomBlock = BukkitBlockManager.instance().blockById(this.targetBlock); if (optionalCustomBlock.isPresent()) { CustomBlock customBlock = optionalCustomBlock.get(); - this.defaultBlockState = customBlock.defaultState().customBlockState().handle(); + this.defaultBlockState = customBlock.defaultState().customBlockState().literalObject(); this.defaultImmutableBlockState = customBlock.defaultState(); } else { CraftEngine.instance().logger().warn("Failed to create solid block " + this.targetBlock + " in ConcretePowderBlockBehavior"); 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 a2b0b3c0f..7e0746ead 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 @@ -94,7 +94,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { BlockStateUtils.getOptionalCustomBlockState(state).ifPresent(customState -> { int age = this.getAge(customState); if (age < this.ageProperty.max && RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, customState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, customState.with(this.ageProperty, age + 1).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); } }); } @@ -133,7 +133,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { if (isMaxAge(state)) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().handle(); + Object visualState = state.vanillaBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); @@ -156,7 +156,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { } ImmutableBlockState customState = optionalCustomState.get(); boolean sendParticles = false; - Object visualState = customState.vanillaBlockState().handle(); + Object visualState = customState.vanillaBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState); @@ -180,7 +180,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { if (i > maxAge) { i = maxAge; } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, customState.with(this.ageProperty, i).customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, customState.with(this.ageProperty, i).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); if (sendParticles) { world.spawnParticle(ParticleUtils.HAPPY_VILLAGER, x + 0.5, y + 0.5, z + 0.5, 15, 0.25, 0.25, 0.25); } 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 768f01adf..864563451 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 @@ -107,7 +107,7 @@ public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior { return MBlocks.AIR$defaultState; } if (neighborState.get(anotherDoorBehavior.get().halfProperty) != half) { - return neighborState.with(anotherDoorBehavior.get().halfProperty, half).customBlockState().handle(); + return neighborState.with(anotherDoorBehavior.get().halfProperty, half).customBlockState().literalObject(); } return MBlocks.AIR$defaultState; } else { @@ -245,7 +245,7 @@ public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior { public void setOpen(@Nullable Player player, Object serverLevel, ImmutableBlockState state, BlockPos pos, boolean isOpen) { if (isOpen(state) != isOpen) { org.bukkit.World world = FastNMS.INSTANCE.method$Level$getCraftWorld(serverLevel); - FastNMS.INSTANCE.method$LevelWriter$setBlock(serverLevel, LocationUtils.toBlockPos(pos), state.with(this.openProperty, isOpen).customBlockState().handle(), UpdateOption.builder().updateImmediate().updateClients().build().flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(serverLevel, LocationUtils.toBlockPos(pos), state.with(this.openProperty, isOpen).customBlockState().literalObject(), UpdateOption.builder().updateImmediate().updateClients().build().flags()); world.sendGameEvent(player == null ? null : (org.bukkit.entity.Player) player.platformPlayer(), isOpen ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, new Vector(pos.x(), pos.y(), pos.z())); SoundData soundData = isOpen ? this.openSound : this.closeSound; if (soundData != null) { @@ -307,7 +307,7 @@ public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior { ); } } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, customState.with(this.poweredProperty, flag).with(this.openProperty, flag).customBlockState().handle(), UpdateOption.Flags.UPDATE_CLIENTS); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, customState.with(this.poweredProperty, flag).with(this.openProperty, flag).customBlockState().literalObject(), UpdateOption.Flags.UPDATE_CLIENTS); } } 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 016979338..14305b479 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 @@ -63,7 +63,7 @@ public class DoubleHighBlockBehavior extends BukkitBlockBehavior { World level = context.getLevel(); BlockPos anotherHalfPos = context.getClickedPos().relative(Direction.UP); BlockStateWrapper blockStateWrapper = state.with(this.halfProperty, DoubleBlockHalf.UPPER).customBlockState(); - FastNMS.INSTANCE.method$LevelWriter$setBlock(level.serverWorld(), LocationUtils.toBlockPos(anotherHalfPos), blockStateWrapper.handle(), UpdateOption.Flags.UPDATE_CLIENTS); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level.serverWorld(), LocationUtils.toBlockPos(anotherHalfPos), blockStateWrapper.literalObject(), UpdateOption.Flags.UPDATE_CLIENTS); } @Override 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 7040f8b58..99f897792 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 @@ -113,7 +113,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior { if (relativeStateIsWall) { // TODO: 连接原版方块 } - return customState.with(this.inWallProperty, flag).customBlockState().handle(); + return customState.with(this.inWallProperty, flag).customBlockState().literalObject(); } @Override @@ -148,7 +148,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior { private void playerToggle(UseOnContext context, ImmutableBlockState state) { Player player = context.getPlayer(); this.toggle(state, context.getLevel(), context.getClickedPos(), player); - if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()), context.getHitResult(), (Item) context.getItem())) { + if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { player.swingHand(context.getHand()); } } @@ -223,7 +223,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior { this.playSound(LocationUtils.fromBlockPos(blockPos), world, hasSignal); } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, customState.with(this.poweredProperty, hasSignal).customBlockState().handle(), UpdateOption.Flags.UPDATE_CLIENTS); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, customState.with(this.poweredProperty, hasSignal).customBlockState().literalObject(), UpdateOption.Flags.UPDATE_CLIENTS); } private void toggle(ImmutableBlockState state, World world, BlockPos pos, @Nullable Player player) { @@ -240,7 +240,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior { } newState = blockState.with(this.openProperty, true); } - FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); boolean open = isOpen(newState); ((org.bukkit.World) world.platformWorld()).sendGameEvent( player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null, 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 d4009daae..20833c67c 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 @@ -8,7 +8,7 @@ import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.FeatureUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.ParticleUtils; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.core.block.BlockBehavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -59,7 +59,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { } boolean sendParticles = false; ImmutableBlockState customState = optionalCustomState.get(); - Object visualState = customState.vanillaBlockState().handle(); + Object visualState = customState.vanillaBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); @@ -86,12 +86,12 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) return InteractionResult.PASS; BlockPos pos = context.getClickedPos(); - BukkitBlockInWorld upper = (BukkitBlockInWorld) context.getLevel().getBlockAt(pos.x(), pos.y() + 1, pos.z()); + BukkitExistingBlock upper = (BukkitExistingBlock) context.getLevel().getBlockAt(pos.x(), pos.y() + 1, pos.z()); Block block = upper.block(); if (!block.isEmpty()) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().handle(); + Object visualState = state.vanillaBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); 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 21398bb83..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 @@ -44,7 +44,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).customBlockState().handle(), 2); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).customBlockState().literalObject(), 2); } } @@ -64,7 +64,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).customBlockState().handle(), 2); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, customState.cycle(this.litProperty).customBlockState().literalObject(), 2); } } } 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 7db9e3abb..c863c19a6 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 @@ -86,10 +86,10 @@ public class LeavesBlockBehavior extends BukkitBlockBehavior { LeavesBlockBehavior behavior = optionalBehavior.get(); ImmutableBlockState newState = behavior.updateDistance(customState, level, blockPos); if (newState != customState) { - if (blockState == newState.customBlockState().handle()) { + if (blockState == newState.customBlockState().literalObject()) { CoreReflections.method$BlockStateBase$updateNeighbourShapes.invoke(blockState, level, blockPos, UpdateOption.UPDATE_ALL.flags(), 512); } else { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState.customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); } } } 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 605a84bbb..da6eb9d78 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 @@ -121,7 +121,7 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { private Object setSignalForState(Object state, int strength) { Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); if (optionalCustomState.isEmpty()) return state; - return optionalCustomState.get().with(this.poweredProperty, strength > 0).customBlockState().handle(); + 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) { 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 324c4752b..c56fa4050 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 @@ -114,7 +114,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { } ImmutableBlockState customState = optionalCustomState.get(); boolean sendParticles = false; - Object visualState = customState.vanillaBlockState().handle(); + Object visualState = customState.vanillaBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); @@ -151,7 +151,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().handle(); + Object visualState = state.vanillaBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java index bb7552b74..6556345dc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java @@ -68,7 +68,7 @@ public class StackableBlockBehavior extends BukkitBlockBehavior { private void updateStackableBlock(ImmutableBlockState state, BlockPos pos, World world, Item item, Player player, InteractionHand hand) { ImmutableBlockState nextStage = state.cycle(this.amountProperty); Location location = new Location((org.bukkit.World) world.platformWorld(), pos.x(), pos.y(), pos.z()); - FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), nextStage.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), nextStage.customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); if (this.stackSound != null) { world.playBlockSound(new Vec3d(location.getX(), location.getY(), location.getZ()), this.stackSound); } 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 64fc2c627..2ad532eed 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 @@ -65,7 +65,7 @@ public class StairsBlockBehavior extends BukkitBlockBehavior { 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().handle() + ? customState.with(this.shapeProperty, stairsShape).customBlockState().literalObject() : superMethod.call(); } 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 5f16405dd..71db218b5 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 @@ -118,7 +118,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior { private void playerToggle(UseOnContext context, ImmutableBlockState state) { Player player = context.getPlayer(); this.toggle(state, context.getLevel(), context.getClickedPos(), player); - if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()), context.getHitResult(), (Item) context.getItem())) { + if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { player.swingHand(context.getHand()); } } @@ -195,7 +195,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior { this.playSound(LocationUtils.fromBlockPos(blockPos), world, hasSignal); } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, customState.with(this.poweredProperty, hasSignal).customBlockState().handle(), UpdateOption.Flags.UPDATE_CLIENTS); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, customState.with(this.poweredProperty, hasSignal).customBlockState().literalObject(), UpdateOption.Flags.UPDATE_CLIENTS); if (this.waterloggedProperty != null && customState.get(this.waterloggedProperty)) { FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(level, blockPos, MFluids.WATER, 5); } @@ -203,7 +203,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior { private void toggle(ImmutableBlockState state, World world, BlockPos pos, @Nullable Player player) { ImmutableBlockState newState = state.cycle(this.openProperty); - FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); boolean open = newState.get(this.openProperty); ((org.bukkit.World) world.platformWorld()).sendGameEvent( player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null, 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 b5d2d9c9a..8e416ac4f 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 @@ -66,13 +66,13 @@ public class VerticalCropBlockBehavior extends BukkitBlockBehavior { if (age >= this.ageProperty.max || RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { Object nextPos = this.direction ? LocationUtils.above(blockPos) : LocationUtils.below(blockPos); if (VersionHelper.isOrAbove1_21_5()) { - CraftBukkitReflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + CraftBukkitReflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); } else { - CraftBukkitReflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().handle()); + CraftBukkitReflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().literalObject()); } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().literalObject(), UpdateOption.UPDATE_NONE.flags()); } else if (RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, age + 1).customBlockState().literalObject(), UpdateOption.UPDATE_NONE.flags()); } } } 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 0b8574385..e928f9233 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 @@ -6,7 +6,7 @@ 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.util.*; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; @@ -82,12 +82,12 @@ public class AxeItemBehavior extends ItemBehavior { CompoundTag compoundTag = customState.propertiesNbt(); ImmutableBlockState newState = newCustomBlock.getBlockState(compoundTag); - BukkitBlockInWorld clicked = (BukkitBlockInWorld) context.getLevel().getBlockAt(context.getClickedPos()); + BukkitExistingBlock clicked = (BukkitExistingBlock) context.getLevel().getBlockAt(context.getClickedPos()); org.bukkit.entity.Player bukkitPlayer = null; if (player != null) { bukkitPlayer = ((org.bukkit.entity.Player) player.platformPlayer()); // Call bukkit event - EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, clicked.block(), BlockStateUtils.fromBlockData(newState.customBlockState().handle())); + EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, clicked.block(), BlockStateUtils.fromBlockData(newState.customBlockState().literalObject())); if (EventUtils.fireAndCheckCancel(event)) { return InteractionResult.FAIL; } @@ -98,7 +98,7 @@ public class AxeItemBehavior extends ItemBehavior { if (ItemUtils.isEmpty(item)) return InteractionResult.FAIL; BlockPos pos = context.getClickedPos(); context.getLevel().playBlockSound(Vec3d.atCenterOf(pos), AXE_STRIP_SOUND, 1, 1); - FastNMS.INSTANCE.method$LevelWriter$setBlock(context.getLevel().serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().handle(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags()); + FastNMS.INSTANCE.method$LevelWriter$setBlock(context.getLevel().serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().literalObject(), UpdateOption.UPDATE_ALL_IMMEDIATE.flags()); clicked.block().getWorld().sendGameEvent(bukkitPlayer, GameEvent.BLOCK_CHANGE, new Vector(pos.x(), pos.y(), pos.z())); Material material = MaterialUtils.getMaterial(item.vanillaId()); if (bukkitPlayer != null) { @@ -106,7 +106,7 @@ public class AxeItemBehavior extends ItemBehavior { // resend swing if it's not interactable on client side if (!InteractUtils.isInteractable( - bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().handle()), + bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().literalObject()), context.getHitResult(), item ) || player.isSecondaryUseActive()) { player.swingHand(context.getHand()); 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 faa5afefc..3422f0352 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 @@ -9,7 +9,7 @@ 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.*; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; @@ -111,7 +111,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { } else { ImmutableBlockState customState = optionalCustomState.get(); // custom block - if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().handle() : againstBlockState)) { + if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().literalObject() : againstBlockState)) { return InteractionResult.FAIL; } } @@ -157,7 +157,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { WorldPosition position = new WorldPosition(context.getLevel(), pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5); Cancellable dummy = Cancellable.dummy(); PlayerOptionalContext functionContext = PlayerOptionalContext.of(player, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(bukkitBlock)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(bukkitBlock)) .withParameter(DirectContextParameters.POSITION, position) .withParameter(DirectContextParameters.EVENT, dummy) .withParameter(DirectContextParameters.HAND, context.getHand()) @@ -196,7 +196,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { try { Player cePlayer = context.getPlayer(); Object player = cePlayer != null ? cePlayer.serverPlayer() : null; - Object blockState = state.customBlockState().handle(); + Object blockState = state.customBlockState().literalObject(); Object blockPos = LocationUtils.toBlockPos(context.getClickedPos()); Object voxelShape; if (VersionHelper.isOrAbove1_21_6()) { 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 480b4db98..e4d92d241 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 @@ -5,7 +5,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; @@ -38,7 +38,7 @@ public class CompostableItemBehavior extends ItemBehavior { @SuppressWarnings("UnstableApiUsage") @Override public InteractionResult useOnBlock(UseOnContext context) { - BukkitBlockInWorld block = (BukkitBlockInWorld) context.getLevel().getBlockAt(context.getClickedPos()); + BukkitExistingBlock block = (BukkitExistingBlock) context.getLevel().getBlockAt(context.getClickedPos()); BlockData blockData = block.block().getBlockData(); Object blockOwner = BlockStateUtils.getBlockOwner(BlockStateUtils.blockDataToBlockState(blockData)); if (blockOwner != MBlocks.COMPOSTER) return InteractionResult.PASS; 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 5365f9692..2be578ff4 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 @@ -6,7 +6,7 @@ import net.momirealms.craftengine.bukkit.util.BlockStateUtils; 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.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.Item; @@ -40,7 +40,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { if (player == null) return InteractionResult.PASS; BlockPos clickedPos = context.getClickedPos(); - BukkitBlockInWorld clicked = (BukkitBlockInWorld) context.getLevel().getBlockAt(clickedPos); + BukkitExistingBlock clicked = (BukkitExistingBlock) context.getLevel().getBlockAt(clickedPos); Block block = clicked.block(); BlockPos firePos = clickedPos.relative(context.getClickedFace()); Direction direction = context.getHorizontalDirection(); @@ -77,10 +77,10 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { // 点击对象为自定义方块 ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); // 原版外观也可燃 - if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().handle())) { + if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().literalObject())) { return InteractionResult.PASS; } - BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().handle()); + BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); // 点击的是方块上面,则只需要判断shift和可交互 if (direction == Direction.UP) { // 客户端层面必须可交互 @@ -95,7 +95,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { } else { // 玩家觉得自定义方块不可燃,且点击了侧面,那么就要判断火源下方的方块是否可燃,如果不可燃,则补发声音 BlockPos belowFirePos = firePos.relative(Direction.DOWN); - BukkitBlockInWorld belowFireBlock = (BukkitBlockInWorld) context.getLevel().getBlockAt(belowFirePos); + BukkitExistingBlock belowFireBlock = (BukkitExistingBlock) context.getLevel().getBlockAt(belowFirePos); boolean belowCanBurn; try { Block belowBlock = belowFireBlock.block(); @@ -134,7 +134,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { for (Direction dir : Direction.values()) { if (dir == relativeDirection) continue; BlockPos relPos = firePos.relative(dir); - BukkitBlockInWorld nearByBlock = (BukkitBlockInWorld) context.getLevel().getBlockAt(relPos); + BukkitExistingBlock nearByBlock = (BukkitExistingBlock) context.getLevel().getBlockAt(relPos); BlockData nearbyBlockData = nearByBlock.block().getBlockData(); Object nearbyBlockState = BlockStateUtils.blockDataToBlockState(nearbyBlockData); int stateID = BlockStateUtils.blockStateToId(nearbyBlockState); 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 e5906ab91..ce1500a38 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 @@ -11,7 +11,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.*; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; +import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; @@ -164,7 +164,7 @@ public class ItemEventListener implements Listener { // fix client side issues if (action.isRightClick() && hitResult != null && - InteractUtils.willConsume(player, BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().handle()), hitResult, itemInHand)) { + InteractUtils.willConsume(player, BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()), hitResult, itemInHand)) { player.updateInventory(); //PlayerUtils.resendItemInHand(player); } @@ -173,7 +173,7 @@ public class ItemEventListener implements Listener { // run custom functions CustomBlock customBlock = immutableBlockState.owner().value(); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withParameter(DirectContextParameters.HAND, hand) .withParameter(DirectContextParameters.EVENT, dummy) @@ -255,13 +255,13 @@ public class ItemEventListener implements Listener { if (immutableBlockState != null) { // client won't have sounds if the clientside block is interactable // so we should check and resend sounds on BlockPlaceEvent - BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().handle()); + BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); if (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) { if (!serverPlayer.isSecondaryUseActive()) { serverPlayer.setResendSound(); } } else { - if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().handle()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().handle())) { + if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().literalObject())) { serverPlayer.setResendSwing(); } } @@ -318,7 +318,7 @@ public class ItemEventListener implements Listener { if (serverPlayer.isSecondaryUseActive() || !InteractUtils.isInteractable(player, blockData, hitResult, itemInHand)) { Cancellable dummy = Cancellable.dummy(); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation())) @@ -339,7 +339,7 @@ public class ItemEventListener implements Listener { if (hasCustomItem && action == Action.LEFT_CLICK_BLOCK) { Cancellable dummy = Cancellable.dummy(); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation())) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java index 03e90d034..f08c257c0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java @@ -38,7 +38,7 @@ public class DebugGetBlockInternalIdCommand extends BukkitCommandFeature waterloggedProperty = (Property) state.owner().value().getProperty("waterlogged"); if (waterloggedProperty == null) return thisObj; - return state.with(waterloggedProperty, (boolean) args[1]).customBlockState().handle(); + return state.with(waterloggedProperty, (boolean) args[1]).customBlockState().literalObject(); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/LootEntryInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/LootEntryInjector.java index e2f1f4267..7002c9084 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/LootEntryInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/LootEntryInjector.java @@ -10,6 +10,8 @@ import java.util.Set; public final class LootEntryInjector { + private LootEntryInjector() {} + public static void init() throws ReflectiveOperationException { Object registry = MBuiltInRegistries.LOOT_POOL_ENTRY_TYPE; CoreReflections.field$MappedRegistry$frozen.set(registry, false); 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 1fdc089d0..89e4bfea1 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 @@ -229,7 +229,7 @@ public final class WorldStorageInjector { // 自定义块到原版块,只需要判断旧块是否和客户端一直 BlockStateWrapper wrapper = previous.vanillaBlockState(); if (wrapper != null) { - updateLight(holder, wrapper.handle(), previousState, x, y, z); + updateLight(holder, wrapper.literalObject(), previousState, x, y, z); } } } @@ -245,10 +245,10 @@ public final class WorldStorageInjector { if (Config.enableLightSystem()) { if (previousImmutableBlockState.isEmpty()) { // 原版块到自定义块,只需要判断新块是否和客户端视觉一致 - updateLight(holder, immutableBlockState.vanillaBlockState().handle(), newState, x, y, z); + updateLight(holder, immutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z); } else { // 自定义块到自定义块 - updateLight$complex(holder, immutableBlockState.vanillaBlockState().handle(), newState, previousState, x, y, z); + updateLight$complex(holder, immutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); } } } 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 2fc91c8af..0409fdf9d 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 @@ -1168,7 +1168,7 @@ public class PacketConsumers { if (player.isAdventureMode()) { if (Config.simplifyAdventureBreakCheck()) { ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); - if (!player.canBreak(pos, state.vanillaBlockState().handle())) { + if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) { player.preventMiningBlock(); return; } @@ -1336,7 +1336,7 @@ public class PacketConsumers { Key itemId = state.settings().itemId(); // no item available if (itemId == null) return; - Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.vanillaBlockState().handle()); + 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()); 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 51f382f78..839e5d926 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 @@ -541,7 +541,7 @@ public class BukkitServerPlayer extends Player { if (custom && getDestroyProgress(state, pos) >= 1f) { BlockStateWrapper vanillaBlockState = immutableBlockState.vanillaBlockState(); // if it's not an instant break on client side, we should resend level event - if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.handle(), pos) < 1f) { + if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.literalObject(), pos) < 1f) { Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(pos), BlockStateUtils.blockStateToId(state), false); sendPacket(levelEventPacket, false); @@ -703,7 +703,7 @@ public class BukkitServerPlayer extends Player { // for simplified adventure break, switch mayBuild temporarily if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) { // check the appearance state - if (canBreak(hitPos, customState.vanillaBlockState().handle())) { + if (canBreak(hitPos, customState.vanillaBlockState().literalObject())) { // Error might occur so we use try here try { FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer, true); 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 5728c14df..3d3a592f7 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,5 +1,6 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.block.BukkitBlockStateWrapper; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.ReflectionInitException; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; @@ -42,10 +43,10 @@ public final class BlockStateUtils { hasInit = true; } - public static BlockStateWrapper toPackedBlockState(BlockData blockData) { + public static BlockStateWrapper toBlockStateWrapper(BlockData blockData) { Object state = blockDataToBlockState(blockData); int id = blockStateToId(state); - return BlockStateWrapper.create(state, id, isVanillaBlock(id)); + return new BukkitBlockStateWrapper(state, id); } public static boolean isCorrectTool(@NotNull ImmutableBlockState state, @Nullable Item itemInHand) { @@ -53,7 +54,7 @@ public final class BlockStateUtils { if (settings.requireCorrectTool()) { if (itemInHand == null || itemInHand.isEmpty()) return false; return settings.isCorrectTool(itemInHand.id()) || - (settings.respectToolComponent() && FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(itemInHand.getLiteralObject(), state.customBlockState().handle())); + (settings.respectToolComponent() && FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(itemInHand.getLiteralObject(), state.customBlockState().literalObject())); } return true; } @@ -145,8 +146,6 @@ public final class BlockStateUtils { } public static Object getBlockState(Block block) { - Object worldServer = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld()); - Object blockPos = LocationUtils.toBlockPos(block.getX(), block.getY(), block.getZ()); - return FastNMS.INSTANCE.method$BlockGetter$getBlockState(worldServer, blockPos); + return FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld()), LocationUtils.toBlockPos(block.getX(), block.getY(), block.getZ())); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java index 0703d45ea..61ef8bc5e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java @@ -47,7 +47,7 @@ public final class ParticleUtils { public static Object toBukkitParticleData(ParticleData particleData, Context context, World world, double x, double y, double z) { return switch (particleData) { - case BlockStateData data -> BlockStateUtils.fromBlockData(data.blockState().handle()); + case BlockStateData data -> BlockStateUtils.fromBlockData(data.blockState().literalObject()); case ColorData data -> ColorUtils.toBukkit(data.color()); case DustData data -> new Particle.DustOptions(ColorUtils.toBukkit(data.color()), data.size()); case DustTransitionData data -> new Particle.DustTransition(ColorUtils.toBukkit(data.from()), ColorUtils.toBukkit(data.to()), data.size()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java similarity index 67% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java index 49ec901f2..0b5912ea8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitExistingBlock.java @@ -7,19 +7,27 @@ 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.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.BlockInWorld; +import net.momirealms.craftengine.core.world.ExistingBlock; import net.momirealms.craftengine.core.world.World; import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class BukkitBlockInWorld implements BlockInWorld { +import java.util.Optional; + +public class BukkitExistingBlock implements ExistingBlock { private final Block block; - public BukkitBlockInWorld(Block block) { + public BukkitExistingBlock(Block block) { this.block = block; } @@ -45,6 +53,22 @@ public class BukkitBlockInWorld implements BlockInWorld { return FastNMS.INSTANCE.method$FluidState$getType(fluidData) == MFluids.WATER; } + @Override + public @NotNull StatePropertyAccessor createStatePropertyAccessor() { + return FastNMS.INSTANCE.createStatePropertyAccessor(this.block); + } + + @Override + public boolean isCustom() { + return CraftEngineBlocks.isCustomBlock(this.block); + } + + @Override + public @NotNull BlockStateWrapper blockState() { + Object blockState = BlockStateUtils.getBlockState(this.block); + return BlockRegistryMirror.stateByRegistryId(BlockStateUtils.blockStateToId(blockState)); + } + @Override public int x() { return this.block.getX(); @@ -62,11 +86,12 @@ public class BukkitBlockInWorld implements BlockInWorld { @Override public Key type() { - CustomBlock customBlock = customBlock(); - if (customBlock == null) { - return BlockStateUtils.getBlockOwnerIdFromData(this.block.getBlockData()); + Object blockState = BlockStateUtils.getBlockState(this.block); + Optional optionalCustomBlockState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCustomBlockState.isPresent()) { + return optionalCustomBlockState.get().owner().value().id(); } - return customBlock.id(); + return BlockStateUtils.getBlockOwnerIdFromState(blockState); } @Override 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 af2d6c269..bfb04f654 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 @@ -50,8 +50,8 @@ public class BukkitWorld implements World { } @Override - public BlockInWorld getBlockAt(int x, int y, int z) { - return new BukkitBlockInWorld(platformWorld().getBlockAt(x, y, z)); + public ExistingBlock getBlockAt(int x, int y, int z) { + return new BukkitExistingBlock(platformWorld().getBlockAt(x, y, z)); } @Override @@ -116,7 +116,7 @@ public class BukkitWorld implements World { public void setBlockAt(int x, int y, int z, BlockStateWrapper blockState, int flags) { Object worldServer = serverWorld(); Object blockPos = FastNMS.INSTANCE.constructor$BlockPos(x, y, z); - FastNMS.INSTANCE.method$LevelWriter$setBlock(worldServer, blockPos, blockState.handle(), flags); + FastNMS.INSTANCE.method$LevelWriter$setBlock(worldServer, blockPos, blockState.literalObject(), flags); } @Override 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 45d45bf9d..6f2c47628 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 @@ -311,7 +311,7 @@ public class BukkitWorldManager implements WorldManager, Listener { for (int y = 0; y < 16; y++) { ImmutableBlockState customState = ceSection.getBlockState(x, y, z); if (!customState.isEmpty() && customState.vanillaBlockState() != null) { - FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.vanillaBlockState().handle(), false); + FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.vanillaBlockState().literalObject(), false); unsaved = true; } } @@ -398,7 +398,7 @@ public class BukkitWorldManager implements WorldManager, Listener { 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().handle(), false); + FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.customBlockState().literalObject(), false); } } } 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 8b1f9e59d..ba6327f9d 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 @@ -397,8 +397,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } else { // 其他情况则是完整的方块 - BlockStateWrapper packedBlockState = createPackedBlockState(blockState); - if (packedBlockState == null || !packedBlockState.isVanillaBlock()) { + BlockStateWrapper packedBlockState = createBlockState(blockState); + if (packedBlockState == null) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState); } registryId = packedBlockState.registryId(); 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 e524b86c9..ec7a0ff65 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 @@ -77,8 +77,8 @@ public abstract class AbstractCustomBlock implements CustomBlock { // Late init states ImmutableBlockState state = possibleStates.getFirst(); state.setSettings(blockStateVariant.settings()); - state.setVanillaBlockState((BlockStateWrapper.VanillaBlockState) BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); - state.setCustomBlockState((BlockStateWrapper.CustomBlockState) BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); + state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); + state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId())); } // double check if there's any invalid state for (ImmutableBlockState state : this.variantProvider().states()) { 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 6b69f04ee..1f6568636 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 @@ -43,5 +43,5 @@ public interface BlockManager extends Manageable, ModelGenerator { ImmutableBlockState getImmutableBlockState(int stateId); @Nullable - BlockStateWrapper createPackedBlockState(String blockState); + BlockStateWrapper createBlockState(String 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 f019602f6..fb880292e 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 @@ -2,71 +2,7 @@ package net.momirealms.craftengine.core.block; public interface BlockStateWrapper { - Object handle(); + Object literalObject(); int registryId(); - - boolean isVanillaBlock(); - - static BlockStateWrapper vanilla(Object handle, int registryId) { - return new VanillaBlockState(handle, registryId); - } - - static BlockStateWrapper custom(Object handle, int registryId) { - return new CustomBlockState(handle, registryId); - } - - static BlockStateWrapper create(Object handle, int registryId, boolean isVanillaBlock) { - if (isVanillaBlock) return new VanillaBlockState(handle, registryId); - else return new CustomBlockState(handle, registryId); - } - - abstract class AbstractBlockState implements BlockStateWrapper { - protected final Object handle; - protected final int registryId; - - public AbstractBlockState(Object handle, int registryId) { - this.handle = handle; - this.registryId = registryId; - } - - @Override - public Object handle() { - return this.handle; - } - - @Override - public int registryId() { - return this.registryId; - } - } - - class VanillaBlockState extends AbstractBlockState { - - public VanillaBlockState(Object handle, int registryId) { - super(handle, registryId); - } - - @Override - public boolean isVanillaBlock() { - return true; - } - } - - class CustomBlockState extends AbstractBlockState { - - public CustomBlockState(Object handle, int registryId) { - super(handle, registryId); - } - - @Override - public DelegatingBlockState handle() { - return (DelegatingBlockState) super.handle(); - } - - @Override - public boolean isVanillaBlock() { - return false; - } - } } 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 5c27421bf..fbf649bba 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 @@ -19,8 +19,8 @@ import java.util.List; public final class ImmutableBlockState extends BlockStateHolder { private CompoundTag tag; - private BlockStateWrapper.CustomBlockState customBlockState; - private BlockStateWrapper.VanillaBlockState vanillaBlockState; + private BlockStateWrapper customBlockState; + private BlockStateWrapper vanillaBlockState; private BlockBehavior behavior; private Integer hashCode; private BlockSettings settings; @@ -75,11 +75,11 @@ public final class ImmutableBlockState extends BlockStateHolder { return this.vanillaBlockState; } - public void setCustomBlockState(@NotNull BlockStateWrapper.CustomBlockState customBlockState) { + public void setCustomBlockState(@NotNull BlockStateWrapper customBlockState) { this.customBlockState = customBlockState; } - public void setVanillaBlockState(@NotNull BlockStateWrapper.VanillaBlockState vanillaBlockState) { + public void setVanillaBlockState(@NotNull BlockStateWrapper vanillaBlockState) { this.vanillaBlockState = vanillaBlockState; } 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/state/StatePropertyAccessor.java new file mode 100644 index 000000000..a69d4e24e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/state/StatePropertyAccessor.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.core.block.state; + +import java.util.Collection; + +public interface StatePropertyAccessor { + + String getPropertyValueAsString(String property); + + Collection getPropertyNames(); + + boolean hasProperty(String property); + + T getPropertyValue(String property); +} 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 b8a213d27..e5ce7b198 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 @@ -1,7 +1,9 @@ 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.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; @@ -9,11 +11,9 @@ import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.Pair; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.ExistingBlock; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; public class MatchBlockPropertyCondition implements Condition { private final List> properties; @@ -29,19 +29,49 @@ public class MatchBlockPropertyCondition implements Conditi @Override public boolean test(CTX ctx) { - return ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE).map(state -> { - CustomBlock block = state.owner().value(); + ImmutableBlockState customBlockState = null; + StatePropertyAccessor vanillaStatePropertyAccessor = null; + // 优先使用自定义状态,其主要应用于自定义方块掉落物 + Optional optionalCustomState = ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE); + if (optionalCustomState.isPresent()) { + customBlockState = optionalCustomState.get(); + } else { + // 其次再判断block,这个过程会更慢,因为每次获取都是全新的方块状态,适用于物品等事件 + Optional optionalExistingBlock = ctx.getOptionalParameter(DirectContextParameters.BLOCK); + if (optionalExistingBlock.isPresent()) { + ExistingBlock existingBlock = optionalExistingBlock.get(); + customBlockState = existingBlock.customBlockState(); + if (customBlockState == null) { + vanillaStatePropertyAccessor = existingBlock.createStatePropertyAccessor(); + } + } else { + // 都没有则条件不过 + return false; + } + } + if (customBlockState != null) { + CustomBlock block = customBlockState.owner().value(); for (Pair property : this.properties) { Property propertyIns = block.getProperty(property.left()); if (propertyIns == null) { return false; } - if (!state.get(propertyIns).toString().toLowerCase(Locale.ENGLISH).equals(property.right())) { + if (!customBlockState.get(propertyIns).toString().toLowerCase(Locale.ENGLISH).equals(property.right())) { return false; } } - return true; - }).orElse(false); + } else { + for (Pair property : this.properties) { + String value = vanillaStatePropertyAccessor.getPropertyValueAsString(property.left()); + if (value == null) { + return false; + } + if (!value.equals(property.right())) { + return false; + } + } + } + return true; } public static class FactoryImpl implements ConditionFactory { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java index 7a08fd238..9bf6830fa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce 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.BlockInWorld; +import net.momirealms.craftengine.core.world.ExistingBlock; import java.util.*; @@ -27,7 +27,7 @@ public class MatchBlockTypeCondition implements Condition block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); + Optional block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); return block.filter(blockInWorld -> MiscUtils.matchRegex(blockInWorld.type().asString(), this.ids, this.regexMatch)).isPresent(); } 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 2ab73db03..c97d47bd3 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 @@ -33,7 +33,7 @@ public class ParticleFunction extends AbstractConditionalFu final String blockState = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("block-state"), "warning.config.function.particle.missing_block_state"); @Override public BlockStateWrapper get() { - return CraftEngine.instance().blockManager().createPackedBlockState(this.blockState); + return CraftEngine.instance().blockManager().createBlockState(this.blockState); } })), ParticleTypes.BLOCK, ParticleTypes.FALLING_DUST, ParticleTypes.DUST_PILLAR, ParticleTypes.BLOCK_CRUMBLE, ParticleTypes.BLOCK_MARKER); 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 2efaf8877..15167ef06 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 @@ -62,7 +62,7 @@ public class PlaceBlockFunction extends AbstractConditional NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); NumberProvider flags = Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())); - return new PlaceBlockFunction<>(LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createPackedBlockState(state)), x, y, z, flags, getPredicates(arguments)); + return new PlaceBlockFunction<>(LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(state)), x, y, z, flags, getPredicates(arguments)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java index 1aac69f9c..dc1cff926 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java @@ -2,31 +2,31 @@ 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.world.BlockInWorld; +import net.momirealms.craftengine.core.world.ExistingBlock; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; -public class BlockParameterProvider implements ChainParameterProvider { - private static final Map, Function> CONTEXT_FUNCTIONS = new HashMap<>(); +public class BlockParameterProvider implements ChainParameterProvider { + private static final Map, Function> CONTEXT_FUNCTIONS = new HashMap<>(); static { - CONTEXT_FUNCTIONS.put(DirectContextParameters.X, BlockInWorld::x); - CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, BlockInWorld::y); - CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, BlockInWorld::z); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, BlockInWorld::x); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, BlockInWorld::y); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, BlockInWorld::z); - CONTEXT_FUNCTIONS.put(DirectContextParameters.CUSTOM_BLOCK, BlockInWorld::customBlock); - CONTEXT_FUNCTIONS.put(DirectContextParameters.CUSTOM_BLOCK_STATE, BlockInWorld::customBlockState); - CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, BlockInWorld::world); - CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, BlockInWorld::position); + CONTEXT_FUNCTIONS.put(DirectContextParameters.X, ExistingBlock::x); + CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, ExistingBlock::y); + CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, ExistingBlock::z); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, ExistingBlock::x); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, ExistingBlock::y); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, ExistingBlock::z); + CONTEXT_FUNCTIONS.put(DirectContextParameters.CUSTOM_BLOCK, ExistingBlock::customBlock); + CONTEXT_FUNCTIONS.put(DirectContextParameters.CUSTOM_BLOCK_STATE, ExistingBlock::customBlockState); + CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, ExistingBlock::world); + CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, ExistingBlock::position); } @SuppressWarnings("unchecked") @Override - public Optional getOptionalParameter(ContextKey parameter, BlockInWorld block) { + public Optional getOptionalParameter(ContextKey parameter, ExistingBlock block) { return (Optional) Optional.ofNullable(CONTEXT_FUNCTIONS.get(parameter)).map(f -> f.apply(block)); } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java index 92b3982c9..a3d0b5335 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java @@ -12,7 +12,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.ContextKey; import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.BlockInWorld; +import net.momirealms.craftengine.core.world.ExistingBlock; import net.momirealms.craftengine.core.world.Position; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; @@ -49,7 +49,7 @@ public final class DirectContextParameters { public static final ContextKey> MAIN_HAND_ITEM = ContextKey.direct("main_hand_item"); public static final ContextKey> OFF_HAND_ITEM = ContextKey.direct("off_hand_item"); public static final ContextKey CUSTOM_BLOCK = ContextKey.direct("custom_block"); - public static final ContextKey BLOCK = ContextKey.direct("block"); + public static final ContextKey BLOCK = ContextKey.direct("block"); public static final ContextKey TIME = ContextKey.direct("time"); public static final ContextKey ID = ContextKey.direct("id"); public static final ContextKey CUSTOM_MODEL_DATA = ContextKey.direct("custom_model_data"); 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 836731d7e..a87b62c10 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 @@ -92,7 +92,7 @@ public class I18NData { } private static String stateToRealBlockId(ImmutableBlockState state) { - String id = state.customBlockState().handle().toString(); + String id = state.customBlockState().literalObject().toString(); int first = -1, last = -1; for (int i = 0; i < id.length(); i++) { char c = id.charAt(i); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java similarity index 69% rename from core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java rename to core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java index 9ac55192d..5b1bb41f1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockInWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/ExistingBlock.java @@ -1,12 +1,15 @@ 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.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public interface BlockInWorld { +public interface ExistingBlock { default boolean canBeReplaced(BlockPlaceContext blockPlaceContext) { return false; @@ -19,9 +22,17 @@ public interface BlockInWorld { @Nullable CustomBlock customBlock(); + boolean isCustom(); + @Nullable ImmutableBlockState customBlockState(); + @NotNull + BlockStateWrapper blockState(); + + @NotNull + StatePropertyAccessor createStatePropertyAccessor(); + default WorldPosition position() { return new WorldPosition(world(), x(), y(), z()); } 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 fb4923698..a7d960454 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 @@ -21,9 +21,9 @@ public interface World { WorldHeight worldHeight(); - BlockInWorld getBlockAt(int x, int y, int z); + ExistingBlock getBlockAt(int x, int y, int z); - default BlockInWorld getBlockAt(final BlockPos pos) { + default ExistingBlock getBlockAt(final BlockPos pos) { return getBlockAt(pos.x(), pos.y(), pos.z()); } diff --git a/gradle.properties b/gradle.properties index c18c2f5aa..b639c341c 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.19 -nms_helper_version=1.0.63 +nms_helper_version=1.0.66 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From 1469f87690c71e9ca947e63ea0d9750fd68f5cd4 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 15:52:48 +0800 Subject: [PATCH 030/177] =?UTF-8?q?=E4=BF=AE=E5=A4=8Daccessor=E5=88=9B?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/world/BukkitExistingBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0b5912ea8..10b63da33 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 @@ -55,7 +55,7 @@ public class BukkitExistingBlock implements ExistingBlock { @Override public @NotNull StatePropertyAccessor createStatePropertyAccessor() { - return FastNMS.INSTANCE.createStatePropertyAccessor(this.block); + return FastNMS.INSTANCE.createStatePropertyAccessor(BlockStateUtils.getBlockState(this.block)); } @Override From 0049b1c421707dfb810103671994aad3eac51534 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 16:05:51 +0800 Subject: [PATCH 031/177] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A5op=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/injector/BlockGenerator.java | 1 - .../bukkit/plugin/user/BukkitServerPlayer.java | 16 ++++++++++++++-- .../bukkit/world/BukkitExistingBlock.java | 2 -- .../craftengine/core/entity/player/Player.java | 2 +- .../plugin/context/function/CommandFunction.java | 14 +++++++++----- 5 files changed, 24 insertions(+), 11 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 059ce70fd..e05bada17 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 @@ -34,7 +34,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 { 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 839e5d926..d9bdc7478 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 @@ -956,8 +956,20 @@ public class BukkitServerPlayer extends Player { } @Override - public void performCommand(String command) { - platformPlayer().performCommand(command); + public void performCommand(String command, boolean asOp) { + org.bukkit.entity.Player player = platformPlayer(); + if (asOp) { + boolean isOp = player.isOp(); + player.setOp(true); + try { + player.performCommand(command); + } catch (Throwable t) { + this.plugin.logger().warn("Failed to perform command '" + command + "' for " + this.name() + " as operator", t); + } + player.setOp(isOp); + } else { + player.performCommand(command); + } } @Override 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 10b63da33..3013d73d1 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 @@ -18,9 +18,7 @@ import net.momirealms.craftengine.core.world.ExistingBlock; import net.momirealms.craftengine.core.world.World; import org.bukkit.Location; import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Optional; 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 8bbcbb73b..b0adb4685 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 @@ -112,7 +112,7 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void unloadCurrentResourcePack(); - public abstract void performCommand(String command); + public abstract void performCommand(String command, boolean asOp); public abstract void performCommandAsEvent(String command); 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 ce444cd36..2447d64ff 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 @@ -23,27 +23,30 @@ public class CommandFunction extends AbstractConditionalFun private final PlayerSelector selector; private final boolean asPlayer; private final boolean asEvent; + private final boolean asOp; - public CommandFunction(List> predicates, @Nullable PlayerSelector selector, List command, boolean asPlayer, boolean asEvent) { + public CommandFunction(List> predicates, @Nullable PlayerSelector selector, List command, + boolean asPlayer, boolean asEvent, boolean asOp) { super(predicates); this.command = command; this.selector = selector; this.asPlayer = asPlayer; this.asEvent = asEvent; + this.asOp = asOp; } @Override public void runInternal(CTX ctx) { - if (this.asPlayer) { + if (this.asPlayer || this.asOp) { if (this.selector == null) { ctx.getOptionalParameter(DirectContextParameters.PLAYER) .ifPresent(player -> executeCommands( - ctx, this.asEvent ? player::performCommandAsEvent : player::performCommand + ctx, this.asEvent ? player::performCommandAsEvent : command1 -> player.performCommand(command1, this.asOp) )); } else { for (Player viewer : this.selector.get(ctx)) { RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); - executeCommands(relationalContext, this.asEvent ? viewer::performCommandAsEvent : viewer::performCommand); + executeCommands(relationalContext, this.asEvent ? viewer::performCommandAsEvent : command1 -> viewer.performCommand(command1, this.asOp)); } } } else { @@ -77,7 +80,8 @@ public class CommandFunction extends AbstractConditionalFun List commands = MiscUtils.getAsStringList(command).stream().map(TextProviders::fromString).toList(); boolean asPlayer = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("as-player", false), "as-player"); boolean asEvent = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("as-event", false), "as-event"); - return new CommandFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), commands, asPlayer, asEvent); + boolean asOp = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("as-op", false), "as-op"); + return new CommandFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), commands, asPlayer, asEvent, asOp); } } } From 07f9add5869af8149a7589e79fb7005256dfcc70 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 17:16:01 +0800 Subject: [PATCH 032/177] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/world/BukkitExistingBlock.java | 2 +- .../plugin/context/condition/CommonConditions.java | 4 ++-- ...lockTypeCondition.java => MatchBlockCondition.java} | 10 +++++----- ...ityTypeCondition.java => MatchEntityCondition.java} | 8 ++++---- .../core/plugin/context/event/EventConditions.java | 4 ++-- .../craftengine/core/world/ExistingBlock.java | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) rename core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/{MatchBlockTypeCondition.java => MatchBlockCondition.java} (81%) rename core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/{MatchEntityTypeCondition.java => MatchEntityCondition.java} (85%) 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 3013d73d1..38dd8db86 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 @@ -83,7 +83,7 @@ public class BukkitExistingBlock implements ExistingBlock { } @Override - public Key type() { + public Key id() { Object blockState = BlockStateUtils.getBlockState(this.block); Optional optionalCustomBlockState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomBlockState.isPresent()) { 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 445406954..d9a1eadae 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 @@ -10,8 +10,8 @@ public final class CommonConditions { public static final Key ANY_OF = Key.of("craftengine:any_of"); public static final Key INVERTED = Key.of("craftengine:inverted"); public static final Key MATCH_ITEM = Key.of("craftengine:match_item"); - public static final Key MATCH_ENTITY_TYPE = Key.of("craftengine:match_entity_type"); - public static final Key MATCH_BLOCK_TYPE = Key.of("craftengine:match_block_type"); + public static final Key MATCH_ENTITY = Key.of("craftengine:match_entity"); + public static final Key MATCH_BLOCK = Key.of("craftengine:match_block"); public static final Key MATCH_BLOCK_PROPERTY = Key.from("craftengine:match_block_property"); public static final Key TABLE_BONUS = Key.from("craftengine:table_bonus"); public static final Key SURVIVES_EXPLOSION = Key.from("craftengine:survives_explosion"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java similarity index 81% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java index 9bf6830fa..47a9649dd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java @@ -11,24 +11,24 @@ import net.momirealms.craftengine.core.world.ExistingBlock; import java.util.*; -public class MatchBlockTypeCondition implements Condition { +public class MatchBlockCondition implements Condition { private final Set ids; private final boolean regexMatch; - public MatchBlockTypeCondition(Collection ids, boolean regexMatch) { + public MatchBlockCondition(Collection ids, boolean regexMatch) { this.ids = new HashSet<>(ids); this.regexMatch = regexMatch; } @Override public Key type() { - return CommonConditions.MATCH_BLOCK_TYPE; + return CommonConditions.MATCH_BLOCK; } @Override public boolean test(CTX ctx) { Optional block = ctx.getOptionalParameter(DirectContextParameters.BLOCK); - return block.filter(blockInWorld -> MiscUtils.matchRegex(blockInWorld.type().asString(), this.ids, this.regexMatch)).isPresent(); + return block.filter(blockInWorld -> MiscUtils.matchRegex(blockInWorld.id().asString(), this.ids, this.regexMatch)).isPresent(); } public static class FactoryImpl implements ConditionFactory { @@ -40,7 +40,7 @@ public class MatchBlockTypeCondition implements Condition(ids, regex); + return new MatchBlockCondition<>(ids, regex); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java similarity index 85% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java index 925559992..63e178c5f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityTypeCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java @@ -11,18 +11,18 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.*; -public class MatchEntityTypeCondition implements Condition { +public class MatchEntityCondition implements Condition { private final Set ids; private final boolean regexMatch; - public MatchEntityTypeCondition(Collection ids, boolean regexMatch) { + public MatchEntityCondition(Collection ids, boolean regexMatch) { this.ids = new HashSet<>(ids); this.regexMatch = regexMatch; } @Override public Key type() { - return CommonConditions.MATCH_ENTITY_TYPE; + return CommonConditions.MATCH_ENTITY; } @Override @@ -40,7 +40,7 @@ public class MatchEntityTypeCondition implements Condition< throw new LocalizedResourceConfigException("warning.config.condition.match_entity_type.missing_id"); } boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); - return new MatchEntityTypeCondition<>(ids, regex); + return new MatchEntityCondition<>(ids, regex); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java index 0e3eb288a..c5e0e3c71 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java @@ -17,8 +17,8 @@ public class EventConditions { static { register(CommonConditions.MATCH_ITEM, new MatchItemCondition.FactoryImpl<>()); - register(CommonConditions.MATCH_ENTITY_TYPE, new MatchEntityTypeCondition.FactoryImpl<>()); - register(CommonConditions.MATCH_BLOCK_TYPE, new MatchBlockTypeCondition.FactoryImpl<>()); + register(CommonConditions.MATCH_ENTITY, new MatchEntityCondition.FactoryImpl<>()); + register(CommonConditions.MATCH_BLOCK, new MatchBlockCondition.FactoryImpl<>()); register(CommonConditions.MATCH_BLOCK_PROPERTY, new MatchBlockPropertyCondition.FactoryImpl<>()); register(CommonConditions.TABLE_BONUS, new TableBonusCondition.FactoryImpl<>()); register(CommonConditions.SURVIVES_EXPLOSION, new SurvivesExplosionCondition.FactoryImpl<>()); 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 5b1bb41f1..947bc664d 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 @@ -39,7 +39,7 @@ public interface ExistingBlock { World world(); - Key type(); + Key id(); int x(); From 89591aae0205440b6b50a03f7dfe54c47eadc1b3 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 17:18:59 +0800 Subject: [PATCH 033/177] =?UTF-8?q?=E6=9B=B4=E6=AD=A3=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/translations/en.yml | 4 ++-- common-files/src/main/resources/translations/zh_cn.yml | 4 ++-- .../core/plugin/context/condition/MatchBlockCondition.java | 2 +- .../core/plugin/context/condition/MatchEntityCondition.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index eab94b9de..60d3ef131 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -92,8 +92,8 @@ warning.config.condition.inverted.invalid_term_type: "Issue found in fil warning.config.condition.enchantment.missing_predicate: "Issue found in file - The config '' is missing the required 'predicate' argument for 'enchantment' condition." warning.config.condition.enchantment.invalid_predicate: "Issue found in file - The config '' is using an invalid enchantment 'predicate' argument ''." warning.config.condition.match_block_property.missing_properties: "Issue found in file - The config '' is missing the required 'properties' argument for 'match_block_property' condition." -warning.config.condition.match_block_type.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_block_type' condition." -warning.config.condition.match_entity_type.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_entity_type' condition." +warning.config.condition.match_block.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_block' condition." +warning.config.condition.match_entity.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_entity' condition." warning.config.condition.match_item.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'match_item' condition." warning.config.condition.table_bonus.missing_enchantment: "Issue found in file - The config '' is missing the required 'enchantment' argument for 'table_bonus' condition." warning.config.condition.table_bonus.missing_chances: "Issue found in file - The config '' is missing the required 'chances' argument for 'table_bonus' condition." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index eeaff9665..8b4b9b18e 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -92,8 +92,8 @@ warning.config.condition.inverted.invalid_term_type: "在文件 warning.config.condition.enchantment.missing_predicate: "在文件 发现问题 - 配置项 '' 缺少 'enchantment' 条件所需的 'predicate' 参数" warning.config.condition.enchantment.invalid_predicate: "在文件 发现问题 - 配置项 '' 使用了无效的附魔 'predicate' 参数 ''" warning.config.condition.match_block_property.missing_properties: "在文件 发现问题 - 配置项 '' 缺少 'match_block_property' 条件所需的 'properties' 参数" -warning.config.condition.match_block_type.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_block_type' 条件所需的 'id' 参数" -warning.config.condition.match_entity_type.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_entity_type' 条件所需的 'id' 参数" +warning.config.condition.match_block.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_block' 条件所需的 'id' 参数" +warning.config.condition.match_entity.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_entity' 条件所需的 'id' 参数" warning.config.condition.match_item.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_item' 条件所需的 'id' 参数" warning.config.condition.table_bonus.missing_enchantment: "在文件 发现问题 - 配置项 '' 缺少 'table_bonus' 条件所需的 'enchantment' 参数" warning.config.condition.table_bonus.missing_chances: "在文件 发现问题 - 配置项 '' 缺少 'table_bonus' 条件所需的 'chances' 参数" diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java index 47a9649dd..769e849fa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java @@ -37,7 +37,7 @@ public class MatchBlockCondition implements Condition public Condition create(Map arguments) { List ids = MiscUtils.getAsStringList(arguments.get("id")); if (ids.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.condition.match_block_type.missing_id"); + throw new LocalizedResourceConfigException("warning.config.condition.match_block.missing_id"); } boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); return new MatchBlockCondition<>(ids, regex); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java index 63e178c5f..c1a92cedc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchEntityCondition.java @@ -37,7 +37,7 @@ public class MatchEntityCondition implements Condition public Condition create(Map arguments) { List ids = MiscUtils.getAsStringList(arguments.get("id")); if (ids.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.condition.match_entity_type.missing_id"); + throw new LocalizedResourceConfigException("warning.config.condition.match_entity.missing_id"); } boolean regex = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("regex", false), "regex"); return new MatchEntityCondition<>(ids, regex); From d559debb8481907a5456d75bf93cc66a50f9f493 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 17:32:31 +0800 Subject: [PATCH 034/177] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java | 2 +- .../bukkit/block/behavior/ChangeOverTimeBlockBehavior.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 d914ae7df..d7b4b9b0d 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 @@ -53,6 +53,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(STAIRS_BLOCK, StairsBlockBehavior.FACTORY); register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY); register(DOUBLE_HIGH_BLOCK, DoubleHighBlockBehavior.FACTORY); - register(CHANGE_OVER_TIME_BLOCK,ChangeOverTimeBlockBehavior.FACTORY); + register(CHANGE_OVER_TIME_BLOCK, ChangeOverTimeBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java index 78335c893..465c9b631 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java @@ -29,7 +29,7 @@ public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior { Optional nextState = BukkitBlockManager.instance().blockById(this.nextBlock) .map(CustomBlock::defaultState) .map(ImmutableBlockState::customBlockState) - .map(BlockStateWrapper::handle); + .map(BlockStateWrapper::literalObject); if (nextState.isEmpty()) return; CraftBukkitReflections.method$CraftEventFactory$handleBlockFormEvent.invoke(null, args[1], args[2], nextState.get(), UpdateOption.UPDATE_ALL.flags()); } From db25240cba52aa55d851bd15988870d449af9958 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 17:34:02 +0800 Subject: [PATCH 035/177] =?UTF-8?q?=E5=86=8D=E6=AD=A4=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/ChangeOverTimeBlockBehavior.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/behavior/ChangeOverTimeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java index 465c9b631..b6a40cf8f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ChangeOverTimeBlockBehavior.java @@ -39,7 +39,7 @@ public class ChangeOverTimeBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { float changeSpeed = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("change-speed", 0.05688889F), "change-speed"); - Key nextBlock = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time_block.missing_next_block")); + Key nextBlock = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.getOrDefault("next-block", "minecraft:air"), "warning.config.block.behavior.change_over_time.missing_next_block")); return new ChangeOverTimeBlockBehavior(block, changeSpeed, nextBlock); } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index e303721f3..8e5f7be8b 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -304,7 +304,7 @@ warning.config.block.behavior.stairs.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_block.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' property for 'change_over_time_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 e855e2479..cbd2408d8 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -304,7 +304,7 @@ warning.config.block.behavior.stairs.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_block.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.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" From 26cff004c50d3fe5b3edb17f81c531cae264e241 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Mon, 25 Aug 2025 20:13:30 +0800 Subject: [PATCH 036/177] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reflection/minecraft/NetworkReflections.java | 14 ++++++++++++-- .../bukkit/plugin/user/BukkitServerPlayer.java | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) 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 5bb4bd47d..48bb2b8cd 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 @@ -1255,7 +1255,17 @@ public final class NetworkReflections { // 1.20.2+ public static final Constructor constructor$DiscardedPayload = Optional.ofNullable(clazz$DiscardedPayload) - .map(ReflectionUtils::getTheOnlyConstructor) + .map(it -> { + if (VersionHelper.isOrAbove1_20_5()) { + Constructor constructor1 = ReflectionUtils.getConstructor(it, CoreReflections.clazz$ResourceLocation, ByteBuf.class); + if (constructor1 != null) { + return constructor1; + } + return ReflectionUtils.getConstructor(it, CoreReflections.clazz$ResourceLocation, byte[].class); + } else { + return ReflectionUtils.getConstructor(it, CoreReflections.clazz$ResourceLocation); + } + }) .orElse(null); public static final Class clazz$ClientboundContainerSetContentPacket = requireNonNull( @@ -1644,7 +1654,7 @@ public final class NetworkReflections { // 1.20.2~1.20.4 public static final Constructor constructor$UnknownPayload = Optional.ofNullable(clazz$UnknownPayload) - .map(ReflectionUtils::getTheOnlyConstructor) + .map(it -> ReflectionUtils.getConstructor(it, CoreReflections.clazz$ResourceLocation, ByteBuf.class)) .orElse(null); // 1.21.5+ 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 d9bdc7478..2d13134ce 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 @@ -372,7 +372,7 @@ public class BukkitServerPlayer extends Player { Object responsePacket; if (VersionHelper.isOrAbove1_20_2()) { Object dataPayload; - if (NetworkReflections.clazz$UnknownPayload != null) { + if (!VersionHelper.isOrAbove1_20_5()) { dataPayload = NetworkReflections.constructor$UnknownPayload.newInstance(channelResourceLocation, Unpooled.wrappedBuffer(data)); } else if (DiscardedPayload.useNewMethod) { dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelResourceLocation, data); From f51798283fbbe3d52905f6a953186393e7e84fa5 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 26 Aug 2025 17:20:31 +0800 Subject: [PATCH 037/177] =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/BushBlockBehavior.java | 6 ++---- .../bukkit/block/behavior/HangingBlockBehavior.java | 6 ++---- 2 files changed, 4 insertions(+), 8 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 d535f0030..f20eab56d 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 @@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.BlockTags; +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; @@ -76,10 +77,7 @@ public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { @SuppressWarnings("DuplicatedCode") @Override protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws Exception { - int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); - int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); - int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); - Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z); + Object belowPos = LocationUtils.below(blockPos); Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos); return mayPlaceOn(belowState, world, belowPos); } 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 c6e645afd..594962aca 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 @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; +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; @@ -20,10 +21,7 @@ public class HangingBlockBehavior extends BushBlockBehavior { @Override protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException { - int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); - int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); - int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); - Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y + 1, z); + Object belowPos = LocationUtils.above(blockPos); Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos); return mayPlaceOn(belowState, world, belowPos); } From cf750316b59c25a18fd6afb99f3c737f0bd5d917 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 26 Aug 2025 21:33:53 +0800 Subject: [PATCH 038/177] =?UTF-8?q?=E9=87=87=E7=94=A8=E9=9A=8F=E6=9C=BA?= =?UTF-8?q?=E5=B1=9E=E6=80=A7uuid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modifier/AttributeModifiersModifier.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/AttributeModifiersModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/AttributeModifiersModifier.java index fc1ed038b..63298239f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/AttributeModifiersModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/AttributeModifiersModifier.java @@ -105,7 +105,11 @@ public class AttributeModifiersModifier implements SimpleNetworkItemDataModif @Override public Item apply(Item item, ItemBuildContext context) { - return item.attributeModifiers(this.modifiers.stream().map(it -> it.toAttributeModifier(context)).toList()); + List results = new ArrayList<>(this.modifiers.size()); + for (PreModifier modifier : this.modifiers) { + results.add(modifier.toAttributeModifier(item, context)); + } + return item.attributeModifiers(results); } @Override @@ -125,12 +129,12 @@ public class AttributeModifiersModifier implements SimpleNetworkItemDataModif public record PreModifier(String type, AttributeModifier.Slot slot, - Key id, + Optional id, NumberProvider amount, AttributeModifier.Operation operation, AttributeModifiersModifier.PreModifier.@Nullable PreDisplay display) { - public PreModifier(String type, AttributeModifier.Slot slot, Key id, NumberProvider amount, AttributeModifier.Operation operation, @Nullable PreDisplay display) { + public PreModifier(String type, AttributeModifier.Slot slot, Optional id, NumberProvider amount, AttributeModifier.Operation operation, @Nullable PreDisplay display) { this.amount = amount; this.type = type; this.slot = slot; @@ -139,8 +143,9 @@ public class AttributeModifiersModifier implements SimpleNetworkItemDataModif this.display = display; } - public AttributeModifier toAttributeModifier(ItemBuildContext context) { - return new AttributeModifier(type, slot, id, amount.getDouble(context), operation, display == null ? null : display.toDisplay(context)); + public AttributeModifier toAttributeModifier(Item item, ItemBuildContext context) { + return new AttributeModifier(this.type, this.slot, this.id.orElseGet(() -> Key.of("craftengine", UUID.randomUUID().toString())), + this.amount.getDouble(context), this.operation, this.display == null ? null : this.display.toDisplay(context)); } public record PreDisplay(AttributeModifier.Display.Type type, String value) { @@ -155,15 +160,11 @@ public class AttributeModifiersModifier implements SimpleNetworkItemDataModif @Override public ItemDataModifier create(Object arg) { - MutableInt mutableInt = new MutableInt(0); List attributeModifiers = ResourceConfigUtils.parseConfigAsList(arg, (map) -> { String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.item.data.attribute_modifiers.missing_type"); Key nativeType = AttributeModifiersModifier.getNativeAttributeName(Key.of(type)); AttributeModifier.Slot slot = AttributeModifier.Slot.valueOf(map.getOrDefault("slot", "any").toString().toUpperCase(Locale.ENGLISH)); - Key id = Optional.ofNullable(map.get("id")).map(String::valueOf).map(Key::of).orElseGet(() -> { - mutableInt.add(1); - return Key.of("craftengine", "modifier_" + mutableInt.intValue()); - }); + Optional id = Optional.ofNullable(map.get("id")).map(String::valueOf).map(Key::of); NumberProvider amount = NumberProviders.fromObject(ResourceConfigUtils.requireNonNullOrThrow(map.get("amount"), "warning.config.item.data.attribute_modifiers.missing_amount")); AttributeModifier.Operation operation = AttributeModifier.Operation.valueOf( ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("operation"), "warning.config.item.data.attribute_modifiers.missing_operation").toUpperCase(Locale.ENGLISH) From 613939f6103de1aa2f7fa07fe715c7f46694ff0f Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 26 Aug 2025 21:56:30 +0800 Subject: [PATCH 039/177] =?UTF-8?q?=E7=A6=81=E6=AD=A2=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E9=99=84=E9=AD=94=E7=9A=84=E7=89=A9=E5=93=81=E8=A2=AB=E9=99=84?= =?UTF-8?q?=E9=AD=94=E4=B9=A6=E9=99=84=E9=AD=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/item/recipe/RecipeEventListener.java | 4 ++++ gradle.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 5b6359d3f..1446cf0c4 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 @@ -284,6 +284,10 @@ public class RecipeEventListener implements Listener { } // 如果第二个物品是附魔书,那么忽略 if (wrappedSecond.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) { + // 禁止不可附魔的物品被附魔书附魔 + if (firstCustom.isPresent() && !firstCustom.get().settings().canEnchant()) { + event.setResult(null); + } return; } diff --git a/gradle.properties b/gradle.properties index b639c341c..f91eb949b 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.3 +project_version=0.0.62.4 config_version=45 lang_version=25 project_group=net.momirealms From 56654b481fe61f475d4b110bb4754f044ee21b47 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 26 Aug 2025 22:12:42 +0800 Subject: [PATCH 040/177] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=A6=81=E6=AD=A2?= =?UTF-8?q?=E9=99=84=E9=AD=94=E5=9C=A8=E9=93=81=E7=A0=A7=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E9=87=8C=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/recipe/RecipeEventListener.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 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 1446cf0c4..251f73c61 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,6 +261,7 @@ 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); @@ -317,12 +318,24 @@ public class RecipeEventListener implements Listener { return; } - // 如果禁止在铁砧使用两个相同物品修复 - firstCustom.ifPresent(it -> { - if (it.settings().canRepair() == Tristate.FALSE) { + + if (firstCustom.isPresent()) { + CustomItem firstCustomItem = firstCustom.get(); + if (firstCustomItem.settings().canRepair() == Tristate.FALSE) { event.setResult(null); + return; } - }); + + Item wrappedResult = BukkitItemManager.instance().wrap(event.getResult()); + if (!firstCustomItem.settings().canEnchant()) { + Object previousEnchantment = wrappedFirst.getExactComponent(ComponentTypes.ENCHANTMENTS); + if (previousEnchantment != null) { + wrappedResult.setExactComponent(ComponentTypes.ENCHANTMENTS, previousEnchantment); + } else { + wrappedResult.resetComponent(ComponentTypes.ENCHANTMENTS); + } + } + } } /* @@ -486,9 +499,6 @@ public class RecipeEventListener implements Listener { if (ItemStackUtils.isEmpty(first)) { return; } - if (event.getResult() == null) { - return; - } Item wrappedFirst = BukkitItemManager.instance().wrap(first); wrappedFirst.getCustomItem().ifPresent(item -> { if (!item.settings().renameable()) { From 36aba7527964599162619eeb0a3c39ee41ffeb04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=BF=E7=AB=99?= <97342038+postyizhan@users.noreply.github.com> Date: Tue, 26 Aug 2025 23:56:44 +0800 Subject: [PATCH 041/177] Update palm_tree.yml --- .../resources/default/configuration/palm_tree.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/common-files/src/main/resources/resources/default/configuration/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/palm_tree.yml index a47b29498..901dbbb00 100644 --- a/common-files/src/main/resources/resources/default/configuration/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/palm_tree.yml @@ -643,6 +643,17 @@ recipes: result: id: default:palm_trapdoor count: 2 + default:palm_door: + type: shaped + pattern: + - AA + - AA + - AA + ingredients: + A: default:palm_planks + result: + id: default:palm_door + count: 3 default:palm_fence_gate: type: shaped pattern: @@ -683,4 +694,4 @@ recipes: A: default:palm_planks result: id: default:palm_stairs - count: 4 \ No newline at end of file + count: 4 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 042/177] 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 b97f1c0b64375227fd4d3423ff682189f0f25c3b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 27 Aug 2025 18:12:38 +0800 Subject: [PATCH 043/177] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E8=A7=89?= =?UTF-8?q?=E9=85=8D=E6=96=B9=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/recipe/RecipeEventListener.java | 29 +++++++++++++++++ .../item/recipe/AbstractRecipeSerializer.java | 25 ++++++++++++++- .../recipe/CustomCraftingTableRecipe.java | 32 ++++++++++++++++++- .../core/item/recipe/CustomShapedRecipe.java | 12 +++++-- .../item/recipe/CustomShapelessRecipe.java | 11 +++++-- .../core/item/recipe/UniqueIdItem.java | 4 +-- .../core/plugin/dependency/Dependencies.java | 1 - gradle.properties | 6 ++-- 8 files changed, 106 insertions(+), 14 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 251f73c61..78bf6203f 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 @@ -613,6 +613,35 @@ public class RecipeEventListener implements Listener { if (input == null) return; Player player = InventoryUtils.getPlayerFromInventoryEvent(event); BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + if (craftingTableRecipe.hasVisualResult()) { + inventory.setResult(craftingTableRecipe.assembleVisual(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY))); + } else { + inventory.setResult(craftingTableRecipe.assemble(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY))); + } + } + + @EventHandler(ignoreCancelled = true) + public void onCraftingFinish(CraftItemEvent event) { + if (!Config.enableRecipeSystem()) return; + org.bukkit.inventory.Recipe recipe = event.getRecipe(); + if (!(recipe instanceof CraftingRecipe craftingRecipe)) return; + Key recipeId = Key.of(craftingRecipe.getKey().namespace(), craftingRecipe.getKey().value()); + Optional> optionalRecipe = this.recipeManager.recipeById(recipeId); + // 也许是其他插件注册的配方,直接无视 + if (optionalRecipe.isEmpty()) { + return; + } + CraftingInventory inventory = event.getInventory(); + 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, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY))); } 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 3087306b1..6d9162d8d 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 @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.item.recipe.result.PostProcessors; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.*; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -61,9 +62,10 @@ public abstract class AbstractRecipeSerializer> implement return recipeCategory; } + @NotNull @SuppressWarnings({"unchecked"}) protected CustomRecipeResult parseResult(Map arguments) { - Map resultMap = MiscUtils.castToMap(arguments.get("result"), true); + Map resultMap = ResourceConfigUtils.getAsMapOrNull(arguments.get("result"), "result"); if (resultMap == null) { throw new LocalizedResourceConfigException("warning.config.recipe.missing_result"); } @@ -81,6 +83,27 @@ public abstract class AbstractRecipeSerializer> implement ); } + @Nullable + @SuppressWarnings({"unchecked"}) + protected CustomRecipeResult parseVisualResult(Map arguments) { + Map resultMap = ResourceConfigUtils.getAsMapOrNull(arguments.get("visual-result"), "visual-result"); + if (resultMap == null) { + return null; + } + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(resultMap.get("id"), "warning.config.recipe.visual_result.missing_id"); + int count = ResourceConfigUtils.getAsInt(resultMap.getOrDefault("count", 1), "count"); + BuildableItem resultItem = (BuildableItem) CraftEngine.instance().itemManager().getBuildableItem(Key.of(id)).orElseThrow(() -> new LocalizedResourceConfigException("warning.config.recipe.invalid_visual_result", id)); + if (resultItem.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.recipe.invalid_visual_result", id); + } + List> processors = ResourceConfigUtils.parseConfigAsList(resultMap.get("post-processors"), PostProcessors::fromMap); + return new CustomRecipeResult<>( + resultItem, + count, + processors.isEmpty() ? null : processors.toArray(new PostProcessor[0]) + ); + } + @SuppressWarnings("unchecked") protected CustomRecipeResult parseResult(DatapackRecipeResult recipeResult) { Item result = (Item) CraftEngine.instance().itemManager().build(recipeResult); 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 2adf21dc3..655f40ea4 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 @@ -1,14 +1,20 @@ package net.momirealms.craftengine.core.item.recipe; +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.util.Key; +import org.jetbrains.annotations.Nullable; public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe { protected final CraftingRecipeCategory category; + private final CustomRecipeResult visualResult; - protected CustomCraftingTableRecipe(Key id, boolean showNotification, CustomRecipeResult result, String group, CraftingRecipeCategory category) { + protected CustomCraftingTableRecipe(Key id, boolean showNotification, CustomRecipeResult result, @Nullable CustomRecipeResult visualResult, String group, CraftingRecipeCategory category) { super(id, showNotification, result, group); this.category = category == null ? CraftingRecipeCategory.MISC : category; + this.visualResult = visualResult; } public CraftingRecipeCategory category() { @@ -19,4 +25,28 @@ public abstract class CustomCraftingTableRecipe extends AbstractGroupedRecipe public RecipeType type() { return RecipeType.CRAFTING; } + + public CustomRecipeResult visualResult() { + return visualResult; + } + + public boolean hasVisualResult() { + return visualResult != null; + } + + public T assembleVisual(RecipeInput input, ItemBuildContext context) { + if (this.visualResult != null) { + return this.visualResult.buildItemStack(context); + } else { + throw new IllegalStateException("No visual result available"); + } + } + + public Item buildVisualOrActualResult(ItemBuildContext context) { + if (this.visualResult != null) { + return this.visualResult.buildItem(context); + } else { + return super.result.buildItem(context); + } + } } 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 70faa1450..40af56a0f 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 @@ -21,10 +21,11 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { public CustomShapedRecipe(Key id, boolean showNotification, CustomRecipeResult result, + CustomRecipeResult visualResult, String group, CraftingRecipeCategory category, Pattern pattern) { - super(id, showNotification, result, group, category); + super(id, showNotification, result, visualResult, group, category); this.pattern = pattern; this.parsedPattern = pattern.parse(); } @@ -165,7 +166,9 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { } return new CustomShapedRecipe(id, showNotification(arguments), - parseResult(arguments), arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), + parseResult(arguments), + parseVisualResult(arguments), + arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), new Pattern<>(pattern.toArray(new String[0]), ingredients) ); } @@ -175,7 +178,10 @@ public class CustomShapedRecipe extends CustomCraftingTableRecipe { Map> ingredients = Maps.transformValues(VANILLA_RECIPE_HELPER.shapedIngredientMap(json.getAsJsonObject("key")), this::toIngredient); return new CustomShapedRecipe<>(id, true, - parseResult(VANILLA_RECIPE_HELPER.craftingResult(json.getAsJsonObject("result"))), VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json), + parseResult(VANILLA_RECIPE_HELPER.craftingResult(json.getAsJsonObject("result"))), + null, + VANILLA_RECIPE_HELPER.readGroup(json), + VANILLA_RECIPE_HELPER.craftingCategory(json), new Pattern<>(VANILLA_RECIPE_HELPER.craftingShapedPattern(json), ingredients) ); } 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 d8ca669ef..678c75543 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 @@ -20,10 +20,11 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { public CustomShapelessRecipe(Key id, boolean showNotification, CustomRecipeResult result, + CustomRecipeResult visualResult, String group, CraftingRecipeCategory category, List> ingredients) { - super(id, showNotification, result, group, category); + super(id, showNotification, result, visualResult, group, category); this.ingredients = ingredients; this.placementInfo = PlacementInfo.create(ingredients); } @@ -84,7 +85,9 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { } return new CustomShapelessRecipe(id, showNotification(arguments), - parseResult(arguments), arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), + parseResult(arguments), + parseVisualResult(arguments), + arguments.containsKey("group") ? arguments.get("group").toString() : null, craftingRecipeCategory(arguments), ingredients ); } @@ -93,7 +96,9 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { public CustomShapelessRecipe readJson(Key id, JsonObject json) { return new CustomShapelessRecipe<>(id, true, - parseResult(VANILLA_RECIPE_HELPER.craftingResult(json.getAsJsonObject("result"))), VANILLA_RECIPE_HELPER.readGroup(json), VANILLA_RECIPE_HELPER.craftingCategory(json), + 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() ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/UniqueIdItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/UniqueIdItem.java index 7e60308d1..9594a764e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/UniqueIdItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/UniqueIdItem.java @@ -19,7 +19,7 @@ public class UniqueIdItem { @NotNull public UniqueKey id() { - return uniqueId; + return this.uniqueId; } @NotNull @@ -37,6 +37,6 @@ public class UniqueIdItem { @Override public String toString() { - return "UniqueIdItem[" + "uniqueId=" + uniqueId + ", item=" + rawItem.getItem() + ']'; + return "UniqueIdItem[" + "uniqueId=" + this.uniqueId + ", item=" + this.rawItem.getItem() + ']'; } } 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 2eb14be4f..37eae795e 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 @@ -420,7 +420,6 @@ public class Dependencies { List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) ); - public static final Dependency AMAZON_AWSSDK_S3 = new Dependency( "amazon-sdk-s3", "software{}amazon{}awssdk", diff --git a/gradle.properties b/gradle.properties index f91eb949b..3c65f77b5 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.4 +project_version=0.0.62.5 config_version=45 lang_version=25 project_group=net.momirealms @@ -42,7 +42,7 @@ commons_lang3_version=3.17.0 sparrow_nbt_version=0.9.4 sparrow_util_version=0.50.9 fastutil_version=8.5.15 -netty_version=4.1.121.Final +netty_version=4.1.124.Final joml_version=1.10.8 datafixerupper_version=8.0.16 mojang_brigadier_version=1.0.18 @@ -50,7 +50,7 @@ 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.66 +nms_helper_version=1.0.67 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From 36180e31d5447c57f31d30dafcf43ac742e49914 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 27 Aug 2025 21:06:58 +0800 Subject: [PATCH 044/177] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=AB=98=E6=96=AF?= =?UTF-8?q?=E5=88=86=E5=B8=83=E5=92=8C=E6=9C=80=E5=A4=A7=E8=80=90=E4=B9=85?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/translations/en.yml | 2 + .../src/main/resources/translations/zh_cn.yml | 2 + .../core/item/modifier/ItemDataModifiers.java | 2 + .../core/item/modifier/MaxDamageModifier.java | 45 ++++++++ .../item/recipe/AbstractRecipeSerializer.java | 6 +- .../number/GaussianNumberProvider.java | 104 ++++++++++++++++++ .../context/number/NumberProviders.java | 2 + 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 8e5f7be8b..efd506c7d 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -83,6 +83,8 @@ warning.config.number.fixed.invalid_value: "Issue found in file warning.config.number.expression.missing_expression: "Issue found in file - The config '' is missing the required 'expression' argument for 'expression' number." warning.config.number.uniform.missing_min: "Issue found in file - The config '' is missing the required 'min' argument for 'uniform' number." 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.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 cbd2408d8..239100483 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -83,6 +83,8 @@ warning.config.number.fixed.invalid_value: "在文件 发现问 warning.config.number.expression.missing_expression: "在文件 发现问题 - 配置项 '' 缺少 'expression' 数字类型所需的 'expression' 参数" warning.config.number.uniform.missing_min: "在文件 发现问题 - 配置项 '' 缺少 'uniform' 数字类型所需的 '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.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/item/modifier/ItemDataModifiers.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java index 2b6672de8..82eafdef4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java @@ -45,6 +45,7 @@ public final class ItemDataModifiers { public static final Key UNBREAKABLE = Key.of("craftengine:unbreakable"); public static final Key DYNAMIC_LORE = Key.of("craftengine:dynamic-lore"); public static final Key OVERWRITABLE_LORE = Key.of("craftengine:overwritable-lore"); + public static final Key MAX_DAMAGE = Key.of("craftengine:max-damage"); public static void register(Key key, ItemDataModifierFactory factory) { ((WritableRegistry>) BuiltInRegistries.ITEM_DATA_MODIFIER_FACTORY) @@ -83,6 +84,7 @@ public final class ItemDataModifiers { register(COMPONENTS, ComponentsModifier.FACTORY); register(REMOVE_COMPONENTS, RemoveComponentModifier.FACTORY); register(FOOD, FoodModifier.FACTORY); + register(MAX_DAMAGE, MaxDamageModifier.FACTORY); } else { register(CUSTOM_NAME, CustomNameModifier.FACTORY); register(ITEM_NAME, CustomNameModifier.FACTORY); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java new file mode 100644 index 000000000..277b42ae4 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java @@ -0,0 +1,45 @@ +package net.momirealms.craftengine.core.item.modifier; + +import net.momirealms.craftengine.core.item.ComponentKeys; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.ItemDataModifierFactory; +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 org.jetbrains.annotations.Nullable; + +public class MaxDamageModifier implements SimpleNetworkItemDataModifier { + public static final Factory FACTORY = new Factory<>(); + private final NumberProvider argument; + + public MaxDamageModifier(NumberProvider argument) { + this.argument = argument; + } + + @Override + public Key type() { + return ItemDataModifiers.MAX_DAMAGE; + } + + @Override + public Item apply(Item item, ItemBuildContext context) { + item.maxDamage(argument.getInt(context)); + return item; + } + + @Override + public @Nullable Key componentType(Item item, ItemBuildContext context) { + return ComponentKeys.MAX_DAMAGE; + } + + public static class Factory implements ItemDataModifierFactory { + + @Override + public ItemDataModifier create(Object arg) { + NumberProvider numberProvider = NumberProviders.fromObject(arg); + return new MaxDamageModifier<>(numberProvider); + } + } +} 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 6d9162d8d..d6a3460f5 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 @@ -90,11 +90,11 @@ public abstract class AbstractRecipeSerializer> implement if (resultMap == null) { return null; } - String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(resultMap.get("id"), "warning.config.recipe.visual_result.missing_id"); + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(resultMap.get("id"), "warning.config.recipe.result.missing_id"); int count = ResourceConfigUtils.getAsInt(resultMap.getOrDefault("count", 1), "count"); - BuildableItem resultItem = (BuildableItem) CraftEngine.instance().itemManager().getBuildableItem(Key.of(id)).orElseThrow(() -> new LocalizedResourceConfigException("warning.config.recipe.invalid_visual_result", id)); + BuildableItem resultItem = (BuildableItem) CraftEngine.instance().itemManager().getBuildableItem(Key.of(id)).orElseThrow(() -> new LocalizedResourceConfigException("warning.config.recipe.invalid_result", id)); if (resultItem.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.recipe.invalid_visual_result", id); + throw new LocalizedResourceConfigException("warning.config.recipe.invalid_result", id); } List> processors = ResourceConfigUtils.parseConfigAsList(resultMap.get("post-processors"), PostProcessors::fromMap); return new CustomRecipeResult<>( 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 new file mode 100644 index 000000000..6ca708f2b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/GaussianNumberProvider.java @@ -0,0 +1,104 @@ +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.ResourceConfigUtils; + +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class GaussianNumberProvider implements NumberProvider { + public static final FactoryImpl FACTORY = new FactoryImpl(); + + private final double min; + private final double max; + private final double mean; + private final double stdDev; + private final int maxAttempts; + + public GaussianNumberProvider(double min, double max, double mean, double stdDev, int maxAttempts) { + this.min = min; + this.max = max; + this.mean = mean; + this.stdDev = stdDev; + this.maxAttempts = maxAttempts; + validateParameters(); + } + + private void validateParameters() { + if (this.min >= this.max) { + throw new IllegalArgumentException("min must be less than max"); + } + if (this.stdDev <= 0) { + throw new IllegalArgumentException("std-dev must be greater than 0"); + } + if (this.maxAttempts <= 0) { + throw new IllegalArgumentException("max-attempts must be greater than 0"); + } + } + + @Override + public float getFloat(Context context) { + return (float) getDouble(context); + } + + @Override + public double getDouble(Context context) { + Random random = ThreadLocalRandom.current(); + int attempts = 0; + while (attempts < maxAttempts) { + double value = random.nextGaussian() * stdDev + mean; + if (value >= min && value <= max) { + return value; + } + attempts++; + } + return MCUtils.clamp(this.mean, this.min, this.max); + } + + @Override + public Key type() { + return NumberProviders.GAUSSIAN; + } + + public double min() { + return min; + } + + public double max() { + return max; + } + + public int maxAttempts() { + return maxAttempts; + } + + public double mean() { + return mean; + } + + public double stdDev() { + return stdDev; + } + + public static class FactoryImpl implements NumberProviderFactory { + + @Override + public NumberProvider create(Map arguments) { + double min = ResourceConfigUtils.getAsDouble(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("min"), "warning.config.number.gaussian.missing_min"), "min"); + double max = ResourceConfigUtils.getAsDouble(ResourceConfigUtils.requireNonNullOrThrow(arguments.get("max"), "warning.config.number.gaussian.missing_max"), "max"); + double mean = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("mean", (min + max) / 2.0), "mean"); + double stdDev = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("std-dev", (max - min) / 6.0), "std-dev"); + int maxAttempts = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-attempts", 128), "max-attempts"); + return new GaussianNumberProvider(min, max, mean, stdDev, maxAttempts); + } + } + + @Override + public String toString() { + return String.format("GaussianNumberProvider{min=%.2f, max=%.2f, mean=%.2f, stdDev=%.2f, maxAttempts=%d}", + min, max, mean, stdDev, maxAttempts); + } +} \ No newline at end of file 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 22b698c86..49ce77b1a 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 @@ -17,11 +17,13 @@ public class NumberProviders { public static final Key CONSTANT = Key.of("craftengine:constant"); 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"); static { register(FIXED, FixedNumberProvider.FACTORY); register(CONSTANT, FixedNumberProvider.FACTORY); register(UNIFORM, UniformNumberProvider.FACTORY); + register(GAUSSIAN, GaussianNumberProvider.FACTORY); register(EXPRESSION, ExpressionNumberProvider.FACTORY); } From 2d4a3c2a481ee6a97b00ba6c8c3ed0dff6a867c8 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 29 Aug 2025 20:06:32 +0800 Subject: [PATCH 045/177] =?UTF-8?q?=E5=AD=97=E7=AC=A6=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=AC=AC=E4=B8=80=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../bukkit/item/LegacyNetworkItemHandler.java | 30 ++-- .../bukkit/item/ModernNetworkItemHandler.java | 70 ++++---- .../plugin/network/PacketConsumers.java | 158 +++++++++--------- .../AbstractMinecartPacketHandler.java | 12 +- .../handler/ArmorStandPacketHandler.java | 13 +- .../handler/BlockDisplayPacketHandler.java | 13 +- .../handler/CommonItemPacketHandler.java | 4 +- .../handler/EndermanPacketHandler.java | 13 +- .../handler/ItemDisplayPacketHandler.java | 4 +- .../handler/ItemFramePacketHandler.java | 5 +- .../handler/PrimedTNTPacketHandler.java | 13 +- .../handler/ProjectilePacketHandler.java | 3 +- .../handler/TextDisplayPacketHandler.java | 13 +- .../network/AdvancementDisplay.java | 42 ++--- .../core/font/AbstractFontManager.java | 23 +-- .../craftengine/core/font/FontManager.java | 3 +- .../core/item/modifier/MaxDamageModifier.java | 1 - .../ResourcePackGenerationException.java | 4 + .../context/NetworkTextReplaceContext.java | 34 ++++ .../plugin/network/EntityPacketHandler.java | 3 +- .../text/component/ComponentProvider.java | 51 ++++++ .../core/util/AdventureHelper.java | 7 +- gradle.properties | 4 +- 24 files changed, 306 insertions(+), 221 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/context/NetworkTextReplaceContext.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/text/component/ComponentProvider.java diff --git a/README.md b/README.md index 10bf509de..836dcd8f0 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ repositories { ``` ```kotlin dependencies { - compileOnly("net.momirealms:craft-engine-core:0.0.61") - compileOnly("net.momirealms:craft-engine-bukkit:0.0.61") + compileOnly("net.momirealms:craft-engine-core:0.0.63") + compileOnly("net.momirealms:craft-engine-bukkit:0.0.63") } ``` \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java index aa98ccc73..7c6df9cdd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.item; -import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.CustomItem; @@ -11,8 +10,11 @@ import net.momirealms.craftengine.core.item.modifier.ArgumentsModifier; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +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.NetworkTextReplaceContext; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.ListTag; @@ -61,7 +63,7 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler> optionalCustomItem = wrapped.getCustomItem(); if (optionalCustomItem.isEmpty()) { if (!Config.interceptItem()) return Optional.empty(); - return new OtherItem(wrapped, false).process(); + return new OtherItem(wrapped, false).process(NetworkTextReplaceContext.of(player)); } else { BukkitCustomItem customItem = (BukkitCustomItem) optionalCustomItem.get(); Object serverItem = FastNMS.INSTANCE.method$ItemStack$getItem(wrapped.getLiteralObject()); @@ -71,7 +73,7 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler item, BiConsumer callback) { + public static boolean processCustomName(Item item, BiConsumer callback, Context context) { Optional optionalCustomName = item.customNameJson(); if (optionalCustomName.isPresent()) { String line = optionalCustomName.get(); - Map tokens = CraftEngine.instance().fontManager().matchTags(line); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); if (!tokens.isEmpty()) { - item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens, context))); callback.accept("display.Name", NetworkItemHandler.pack(Operation.ADD, new StringTag(line))); return true; } @@ -125,18 +127,18 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler item, BiConsumer callback) { + private static boolean processLore(Item item, BiConsumer callback, Context context) { Optional> optionalLore = item.loreJson(); if (optionalLore.isPresent()) { boolean changed = false; List lore = optionalLore.get(); List newLore = new ArrayList<>(lore.size()); for (String line : lore) { - Map tokens = CraftEngine.instance().fontManager().matchTags(line); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); if (tokens.isEmpty()) { newLore.add(line); } else { - newLore.add(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + newLore.add(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens, context))); changed = true; } } @@ -164,11 +166,11 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler> process() { - if (processLore(this.item, (s, c) -> networkTag().put(s, c))) { + public Optional> process(Context context) { + if (processLore(this.item, (s, c) -> networkTag().put(s, c), context)) { this.globalChanged = true; } - if (processCustomName(this.item, (s, c) -> networkTag().put(s, c))) { + if (processCustomName(this.item, (s, c) -> networkTag().put(s, c), context)) { this.globalChanged = true; } if (this.globalChanged) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java index a1be6a2f5..41bad6664 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.item; -import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.*; @@ -8,8 +7,11 @@ import net.momirealms.craftengine.core.item.modifier.ArgumentsModifier; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +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.NetworkTextReplaceContext; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.sparrow.nbt.CompoundTag; @@ -64,7 +66,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler> optionalCustomItem = wrapped.getCustomItem(); if (optionalCustomItem.isEmpty()) { if (!Config.interceptItem()) return Optional.empty(); - return new OtherItem(wrapped, false).process(); + return new OtherItem(wrapped, false).process(NetworkTextReplaceContext.of(player)); } else { BukkitCustomItem customItem = (BukkitCustomItem) optionalCustomItem.get(); Object serverItem = FastNMS.INSTANCE.method$ItemStack$getItem(wrapped.getLiteralObject()); @@ -74,7 +76,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler tag); - else processLegacyItemName(wrapped, () -> tag); + if (VersionHelper.isOrAbove1_21_5()) processModernItemName(wrapped, () -> tag, context); + else processLegacyItemName(wrapped, () -> tag, context); } if (!tag.containsKey(ComponentIds.CUSTOM_NAME)) { - if (VersionHelper.isOrAbove1_21_5()) processModernCustomName(wrapped, () -> tag); - else processLegacyCustomName(wrapped, () -> tag); + if (VersionHelper.isOrAbove1_21_5()) processModernCustomName(wrapped, () -> tag, context); + else processLegacyCustomName(wrapped, () -> tag, context); } if (!tag.containsKey(ComponentIds.LORE)) { - if (VersionHelper.isOrAbove1_21_5()) processModernLore(wrapped, () -> tag); - else processLegacyLore(wrapped, () -> tag); + if (VersionHelper.isOrAbove1_21_5()) processModernLore(wrapped, () -> tag, context); + else processLegacyLore(wrapped, () -> tag, context); } } if (tag.isEmpty()) { @@ -120,18 +122,18 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler item, Supplier tag) { + public static boolean processLegacyLore(Item item, Supplier tag, Context context) { Optional> optionalLore = item.loreJson(); if (optionalLore.isPresent()) { boolean changed = false; List lore = optionalLore.get(); List newLore = new ArrayList<>(lore.size()); for (String line : lore) { - Map tokens = CraftEngine.instance().fontManager().matchTags(line); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); if (tokens.isEmpty()) { newLore.add(line); } else { - newLore.add(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + newLore.add(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens, context))); changed = true; } } @@ -148,13 +150,13 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler item, Supplier tag) { + public static boolean processLegacyCustomName(Item item, Supplier tag, Context context) { Optional optionalCustomName = item.customNameJson(); if (optionalCustomName.isPresent()) { String line = optionalCustomName.get(); - Map tokens = CraftEngine.instance().fontManager().matchTags(line); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); if (!tokens.isEmpty()) { - item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens, context))); tag.get().put(ComponentIds.CUSTOM_NAME, NetworkItemHandler.pack(Operation.ADD, new StringTag(line))); return true; } @@ -162,13 +164,13 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler item, Supplier tag) { + public static boolean processLegacyItemName(Item item, Supplier tag, Context context) { Optional optionalItemName = item.itemNameJson(); if (optionalItemName.isPresent()) { String line = optionalItemName.get(); - Map tokens = CraftEngine.instance().fontManager().matchTags(line); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); if (!tokens.isEmpty()) { - item.itemNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + item.itemNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens, context))); tag.get().put(ComponentIds.ITEM_NAME, NetworkItemHandler.pack(Operation.ADD, new StringTag(line))); return true; } @@ -176,33 +178,33 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler item, Supplier tag) { + public static boolean processModernItemName(Item item, Supplier tag, Context context) { Tag nameTag = item.getSparrowNBTComponent(ComponentTypes.ITEM_NAME); if (nameTag == null) return false; String tagStr = nameTag.getAsString(); - Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); + Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); if (!tokens.isEmpty()) { - item.setNBTComponent(ComponentKeys.ITEM_NAME, AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(nameTag), tokens))); + item.setNBTComponent(ComponentKeys.ITEM_NAME, AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(nameTag), tokens, context))); tag.get().put(ComponentIds.ITEM_NAME, NetworkItemHandler.pack(Operation.ADD, nameTag)); return true; } return false; } - public static boolean processModernCustomName(Item item, Supplier tag) { + public static boolean processModernCustomName(Item item, Supplier tag, Context context) { Tag nameTag = item.getSparrowNBTComponent(ComponentTypes.CUSTOM_NAME); if (nameTag == null) return false; String tagStr = nameTag.getAsString(); - Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); + Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); if (!tokens.isEmpty()) { - item.setNBTComponent(ComponentKeys.CUSTOM_NAME, AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(nameTag), tokens))); + item.setNBTComponent(ComponentKeys.CUSTOM_NAME, AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(nameTag), tokens, context))); tag.get().put(ComponentIds.CUSTOM_NAME, NetworkItemHandler.pack(Operation.ADD, nameTag)); return true; } return false; } - public static boolean processModernLore(Item item, Supplier tagSupplier) { + public static boolean processModernLore(Item item, Supplier tagSupplier, Context context) { Tag loreTag = item.getSparrowNBTComponent(ComponentTypes.LORE); boolean changed = false; if (!(loreTag instanceof ListTag listTag)) { @@ -211,11 +213,11 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler tokens = CraftEngine.instance().fontManager().matchTags(tagStr); + Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); if (tokens.isEmpty()) { newLore.add(tag); } else { - newLore.add(AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(tag), tokens))); + newLore.add(AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(tag), tokens, context))); changed = true; } } @@ -238,20 +240,20 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler> process() { + public Optional> process(Context context) { if (VersionHelper.isOrAbove1_21_5()) { - if (processModernLore(this.item, this::getOrCreateTag)) + if (processModernLore(this.item, this::getOrCreateTag, context)) this.globalChanged = true; - if (processModernCustomName(this.item, this::getOrCreateTag)) + if (processModernCustomName(this.item, this::getOrCreateTag, context)) this.globalChanged = true; - if (processModernItemName(this.item, this::getOrCreateTag)) + if (processModernItemName(this.item, this::getOrCreateTag, context)) this.globalChanged = true; } else { - if (processLegacyLore(this.item, this::getOrCreateTag)) + if (processLegacyLore(this.item, this::getOrCreateTag, context)) this.globalChanged = true; - if (processLegacyCustomName(this.item, this::getOrCreateTag)) + if (processLegacyCustomName(this.item, this::getOrCreateTag, context)) this.globalChanged = true; - if (processLegacyItemName(this.item, this::getOrCreateTag)) + if (processLegacyItemName(this.item, this::getOrCreateTag, context)) this.globalChanged = true; } if (this.globalChanged) { 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 0409fdf9d..04e46152c 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 @@ -56,11 +56,13 @@ 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.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.BlockHitResult; import net.momirealms.craftengine.core.world.BlockPos; @@ -494,23 +496,24 @@ public class PacketConsumers { 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()); + 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)), false); + 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)), false); - buf.writeNbt(tokens3.isEmpty() ? suffix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(suffix), tokens3)), false); + 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); } @@ -544,11 +547,12 @@ public class PacketConsumers { newEntries.add(entry); } else { String json = ComponentUtils.minecraftToJson(mcComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + 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))); + 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; } @@ -578,24 +582,25 @@ public class PacketConsumers { 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); + 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))); + 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))); - buf.writeUtf(tokens3.isEmpty() ? suffix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(suffix), tokens3))); + 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); } @@ -612,7 +617,7 @@ public class PacketConsumers { int actionType = buf.readVarInt(); if (actionType == 0) { String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; float health = buf.readFloat(); int color = buf.readVarInt(); @@ -623,21 +628,21 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeUUID(uuid); buf.writeVarInt(actionType); - buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); + 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); + 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))); + 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); @@ -653,7 +658,7 @@ public class PacketConsumers { if (actionType == 0) { Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; float health = buf.readFloat(); int color = buf.readVarInt(); @@ -664,7 +669,7 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeUUID(uuid); buf.writeVarInt(actionType); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); buf.writeFloat(health); buf.writeVarInt(color); buf.writeVarInt(division); @@ -672,14 +677,14 @@ public class PacketConsumers { } else if (actionType == 3) { Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + 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)), false); + 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); @@ -695,14 +700,14 @@ public class PacketConsumers { if (mode != 0 && mode != 2) return; String displayName = buf.readUtf(); int renderType = buf.readVarInt(); - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName); + 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))); + 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); @@ -723,19 +728,19 @@ public class PacketConsumers { if (optionalNumberFormat) { int format = buf.readVarInt(); if (format == 0) { - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + 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)), false); + 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()); + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); if (tokens.isEmpty()) return; Tag style = buf.readNbt(false); event.setChanged(true); @@ -743,7 +748,7 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeUtf(objective); buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens)), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); buf.writeVarInt(renderType); buf.writeBoolean(true); buf.writeVarInt(1); @@ -751,29 +756,29 @@ public class PacketConsumers { } 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()); + 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)), false); + 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)), false); + 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()); + 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)), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens, NetworkTextReplaceContext.of((BukkitServerPlayer) user))), false); buf.writeVarInt(renderType); buf.writeBoolean(false); } @@ -787,13 +792,13 @@ public class PacketConsumers { try { FriendlyByteBuf buf = event.getBuffer(); String jsonOrPlainString = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(jsonOrPlainString); + 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))); + 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); @@ -806,13 +811,13 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + 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)), false); + 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); @@ -824,12 +829,12 @@ public class PacketConsumers { try { FriendlyByteBuf buf = event.getBuffer(); String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + 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))); + 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); } @@ -841,12 +846,12 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + 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)), false); + 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); } @@ -857,12 +862,12 @@ public class PacketConsumers { try { FriendlyByteBuf buf = event.getBuffer(); String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + 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))); + 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); } @@ -874,12 +879,12 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + 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)), false); + 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); } @@ -890,12 +895,12 @@ public class PacketConsumers { try { FriendlyByteBuf buf = event.getBuffer(); String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + 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))); + 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); } @@ -907,12 +912,12 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + 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)), false); + 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); } @@ -924,14 +929,15 @@ public class PacketConsumers { 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); + 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()); - buf.writeUtf(tokens1.isEmpty() ? json1 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json1), tokens1))); - buf.writeUtf(tokens2.isEmpty() ? json2 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json2), tokens2))); + 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); } @@ -945,14 +951,15 @@ public class PacketConsumers { 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()); + 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()); - buf.writeNbt(tokens1.isEmpty() ? nbt1 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt1), tokens1)), false); - buf.writeNbt(tokens2.isEmpty() ? nbt2 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt2), tokens2)), false); + 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); } @@ -965,14 +972,14 @@ public class PacketConsumers { int containerId = buf.readVarInt(); int type = buf.readVarInt(); String json = buf.readUtf(); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + 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))); + 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); } @@ -986,13 +993,13 @@ public class PacketConsumers { int type = buf.readVarInt(); Tag nbt = buf.readNbt(false); if (nbt == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); + 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)), false); + 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); } @@ -1501,7 +1508,7 @@ public class PacketConsumers { int entityId = FastNMS.INSTANCE.method$ClientboundEntityPositionSyncPacket$id(packet); EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); if (handler != null) { - handler.handleSyncEntityPosition(user, event, packet); + handler.handleSyncEntityPosition((BukkitServerPlayer) user, event, packet); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundEntityPositionSyncPacket", e); @@ -1940,11 +1947,12 @@ public class PacketConsumers { @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(user, event); + handler.handleSetEntityData(serverPlayer, event); return; } if (Config.interceptEntityName()) { @@ -1958,12 +1966,10 @@ public class PacketConsumers { if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + 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; @@ -1985,6 +1991,7 @@ public class PacketConsumers { 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(); @@ -1997,12 +2004,10 @@ public class PacketConsumers { } outside: if (displayName != null) { - Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); + Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); if (tokens.isEmpty()) break outside; Component component = AdventureHelper.tagToComponent(displayName); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); displayName = AdventureHelper.componentToTag(component); isChanged = true; } @@ -2020,13 +2025,11 @@ public class PacketConsumers { } else if (format == 2) { fixed = buf.readNbt(false); if (fixed == null) return; - Map tokens = CraftEngine.instance().fontManager().matchTags(fixed.getAsString()); + Map tokens = CraftEngine.instance().fontManager().matchTags(fixed.getAsString()); if (tokens.isEmpty() && !isChanged) return; if (!tokens.isEmpty()) { Component component = AdventureHelper.tagToComponent(fixed); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + component = AdventureHelper.replaceText(component, tokens, NetworkTextReplaceContext.of(serverPlayer)); fixed = AdventureHelper.componentToTag(component); isChanged = true; } @@ -2569,11 +2572,12 @@ public class PacketConsumers { 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((BukkitServerPlayer) user); + holder.applyClientboundData(serverPlayer); return holder; }); Set removed = buf.readCollection(Sets::newLinkedHashSetWithExpectedSize, FriendlyByteBuf::readKey); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java index 7e087f395..67774af83 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java @@ -7,11 +7,14 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.VersionHelper; @@ -25,7 +28,7 @@ public class AbstractMinecartPacketHandler implements EntityPacketHandler { private static final BlockStateHandler HANDLER = VersionHelper.isOrAbove1_21_3() ? BlockStateHandler_1_21_3.INSTANCE : BlockStateHandler_1_20.INSTANCE; @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; @@ -43,12 +46,9 @@ public class AbstractMinecartPacketHandler implements EntityPacketHandler { if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + Component component = AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of(user)); Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component)) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java index d7aa19418..672394811 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java @@ -4,11 +4,13 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; @@ -21,7 +23,7 @@ public class ArmorStandPacketHandler implements EntityPacketHandler { public static final ArmorStandPacketHandler INSTANCE = new ArmorStandPacketHandler(); @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { if (!Config.interceptArmorStand()) { return; } @@ -38,12 +40,9 @@ public class ArmorStandPacketHandler implements EntityPacketHandler { if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + Component component = AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of(user)); 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; 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 3f9d13d5d..953f45bf5 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 @@ -7,11 +7,13 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; @@ -23,7 +25,7 @@ public class BlockDisplayPacketHandler implements EntityPacketHandler { public static final BlockDisplayPacketHandler INSTANCE = new BlockDisplayPacketHandler(); @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; @@ -52,12 +54,9 @@ public class BlockDisplayPacketHandler implements EntityPacketHandler { if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + Component component = AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of(user)); Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component)) 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 17bba65f8..80a4c20cd 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 @@ -5,10 +5,10 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.EntityDataUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import org.bukkit.inventory.ItemStack; @@ -20,7 +20,7 @@ public class CommonItemPacketHandler implements EntityPacketHandler { private static long lastWarningTime = 0; @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; 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 2d1f7f43a..3d5561c01 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 @@ -7,11 +7,13 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; @@ -23,7 +25,7 @@ public class EndermanPacketHandler implements EntityPacketHandler { public static final EndermanPacketHandler INSTANCE = new EndermanPacketHandler(); @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; @@ -54,12 +56,9 @@ public class EndermanPacketHandler implements EntityPacketHandler { if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + Component component = AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of(user)); Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component)) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java index 77dff78ab..a84770a74 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java @@ -4,9 +4,9 @@ import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import org.bukkit.inventory.ItemStack; @@ -17,7 +17,7 @@ public class ItemDisplayPacketHandler implements EntityPacketHandler { public static final ItemDisplayPacketHandler INSTANCE = new ItemDisplayPacketHandler(); @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java index 25c4f3871..caf19a28d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java @@ -5,6 +5,7 @@ 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.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; @@ -20,7 +21,7 @@ public class ItemFramePacketHandler implements EntityPacketHandler { private static long lastWarningTime = 0; @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; @@ -41,7 +42,7 @@ public class ItemFramePacketHandler 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/PrimedTNTPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/PrimedTNTPacketHandler.java index f352c9620..ef3178e21 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 @@ -7,11 +7,13 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; @@ -23,7 +25,7 @@ public class PrimedTNTPacketHandler implements EntityPacketHandler { public static final PrimedTNTPacketHandler INSTANCE = new PrimedTNTPacketHandler(); @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); boolean isChanged = false; @@ -52,12 +54,9 @@ public class PrimedTNTPacketHandler implements EntityPacketHandler { if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + Component component = AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of(user)); Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component)) 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 9c217435d..5c7d29b86 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 @@ -6,6 +6,7 @@ import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.injector.ProtectedFieldVisitor; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.projectile.ProjectileMeta; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; @@ -38,7 +39,7 @@ public class ProjectilePacketHandler implements EntityPacketHandler { } @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); event.setChanged(true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/TextDisplayPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/TextDisplayPacketHandler.java index a02346b58..8b7fc31b9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/TextDisplayPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/TextDisplayPacketHandler.java @@ -5,11 +5,13 @@ import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; @@ -20,7 +22,7 @@ public class TextDisplayPacketHandler implements EntityPacketHandler { public static final TextDisplayPacketHandler INSTANCE = new TextDisplayPacketHandler(); @Override - public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { if (!Config.interceptTextDisplay()) { return; } @@ -35,12 +37,9 @@ public class TextDisplayPacketHandler implements EntityPacketHandler { Object textComponent = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); if (textComponent == CoreReflections.instance$Component$empty) break; String json = ComponentUtils.minecraftToJson(textComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) continue; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } + Component component = AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens, NetworkTextReplaceContext.of(user)); Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, ComponentUtils.adventureToMinecraft(component))); isChanged = true; diff --git a/core/src/main/java/net/momirealms/craftengine/core/advancement/network/AdvancementDisplay.java b/core/src/main/java/net/momirealms/craftengine/core/advancement/network/AdvancementDisplay.java index 93924daf9..b2e22857d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/advancement/network/AdvancementDisplay.java +++ b/core/src/main/java/net/momirealms/craftengine/core/advancement/network/AdvancementDisplay.java @@ -6,11 +6,11 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.sparrow.nbt.Tag; import java.util.Map; import java.util.Optional; @@ -51,6 +51,16 @@ public class AdvancementDisplay { public void applyClientboundData(Player player) { this.icon = CraftEngine.instance().itemManager().s2c(this.icon, player); + if (Config.interceptAdvancement()) { + Map tokens1 = CraftEngine.instance().fontManager().matchTags(AdventureHelper.componentToJson(this.title)); + if (!tokens1.isEmpty()) { + this.title = AdventureHelper.replaceText(this.title, tokens1, NetworkTextReplaceContext.of(player)); + } + Map tokens2 = CraftEngine.instance().fontManager().matchTags(AdventureHelper.componentToJson(this.description)); + if (!tokens2.isEmpty()) { + this.description = AdventureHelper.replaceText(this.description, tokens2, NetworkTextReplaceContext.of(player)); + } + } } public void write(FriendlyByteBuf buf) { @@ -75,8 +85,8 @@ public class AdvancementDisplay { } public static AdvancementDisplay read(FriendlyByteBuf buf) { - Component title = readComponent(buf); - Component description = readComponent(buf); + Component title = buf.readComponent(); + Component description = buf.readComponent(); Item icon = CraftEngine.instance().itemManager().decode(buf); AdvancementType type = AdvancementType.byId(buf.readVarInt()); int flags = buf.readInt(); @@ -88,28 +98,4 @@ public class AdvancementDisplay { float y = buf.readFloat(); return new AdvancementDisplay(title, description, icon, background, type, showToast, hidden, x, y); } - - private static Component readComponent(FriendlyByteBuf buf) { - if (Config.interceptAdvancement()) { - if (VersionHelper.isOrAbove1_20_3()) { - Tag nbt = buf.readNbt(false); - Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); - Component component = AdventureHelper.nbtToComponent(nbt); - if (!tokens.isEmpty()) { - component = AdventureHelper.replaceText(component, tokens); - } - return component; - } else { - String json = buf.readUtf(); - Component component = AdventureHelper.jsonToComponent(json); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (!tokens.isEmpty()) { - component = AdventureHelper.replaceText(component, tokens); - } - return component; - } - } else { - return buf.readComponent(); - } - } } 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 bb4c46dd2..3a5cef4e6 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 @@ -10,6 +10,7 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import org.ahocorasick.trie.Token; import org.ahocorasick.trie.Trie; @@ -41,7 +42,7 @@ public abstract class AbstractFontManager implements FontManager { protected Trie imageTagTrie; protected Trie emojiKeywordTrie; - protected Map tagMapper; + protected Map tagMapper; protected Map emojiMapper; protected List emojiList; protected List allEmojiSuggestions; @@ -89,11 +90,11 @@ public abstract class AbstractFontManager implements FontManager { } @Override - public Map matchTags(String json) { + public Map matchTags(String json) { if (this.imageTagTrie == null) { return Collections.emptyMap(); } - Map tags = new HashMap<>(); + Map tags = new HashMap<>(); for (Token token : this.imageTagTrie.tokenize(json)) { if (token.isMatch()) { tags.put(token.getFragment(), this.tagMapper.get(token.getFragment())); @@ -218,9 +219,9 @@ public abstract class AbstractFontManager implements FontManager { public IllegalCharacterProcessResult processIllegalCharacters(String raw, char replacement) { boolean hasIllegal = false; // replace illegal image usage - Map tokens = matchTags(raw); + Map tokens = matchTags(raw); if (!tokens.isEmpty()) { - for (Map.Entry entry : tokens.entrySet()) { + for (Map.Entry entry : tokens.entrySet()) { raw = raw.replace(entry.getKey(), String.valueOf(replacement)); hasIllegal = true; } @@ -269,21 +270,21 @@ public abstract class AbstractFontManager implements FontManager { for (BitmapImage image : this.images.values()) { String id = image.id().toString(); String simpleImageTag = imageTag(id); - this.tagMapper.put(simpleImageTag, image.componentAt(0, 0)); - this.tagMapper.put("\\" + simpleImageTag, Component.text(simpleImageTag)); + this.tagMapper.put(simpleImageTag, ComponentProvider.constant(image.componentAt(0, 0))); + this.tagMapper.put("\\" + simpleImageTag, ComponentProvider.constant(Component.text(simpleImageTag))); for (int i = 0; i < image.rows(); i++) { for (int j = 0; j < image.columns(); j++) { String imageArgs = id + ":" + i + ":" + j; String imageTag = imageTag(imageArgs); - this.tagMapper.put(imageTag, image.componentAt(i, j)); - this.tagMapper.put("\\" + imageTag, Component.text(imageTag)); + this.tagMapper.put(imageTag, ComponentProvider.constant(image.componentAt(i, j))); + this.tagMapper.put("\\" + imageTag, ComponentProvider.constant(Component.text(imageTag))); } } } for (int i = -256; i <= 256; i++) { String shiftTag = ""; - this.tagMapper.put(shiftTag, this.offsetFont.createOffset(i)); - this.tagMapper.put("\\" + shiftTag, Component.text(shiftTag)); + this.tagMapper.put(shiftTag, ComponentProvider.constant(this.offsetFont.createOffset(i))); + this.tagMapper.put("\\" + shiftTag, ComponentProvider.constant(Component.text(shiftTag))); } this.imageTagTrie = Trie.builder() .ignoreOverlaps() 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 307350eea..aca5c9dd6 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 @@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.FormatUtils; @@ -103,7 +104,7 @@ public interface FontManager extends Manageable { return createOffsets(offset, (raw, font) -> raw); } - Map matchTags(String json); + Map matchTags(String json); void refreshEmojiSuggestions(UUID uuid); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java index 277b42ae4..842e2892b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/MaxDamageModifier.java @@ -7,7 +7,6 @@ import net.momirealms.craftengine.core.item.ItemDataModifierFactory; 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 org.jetbrains.annotations.Nullable; public class MaxDamageModifier implements SimpleNetworkItemDataModifier { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ResourcePackGenerationException.java b/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ResourcePackGenerationException.java index adebeba89..9056ed3cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ResourcePackGenerationException.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ResourcePackGenerationException.java @@ -5,4 +5,8 @@ public class ResourcePackGenerationException extends RuntimeException { public ResourcePackGenerationException(String message) { super(message); } + + public ResourcePackGenerationException(String message, Throwable cause) { + super(message, cause); + } } 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 new file mode 100644 index 000000000..42f7c5984 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/NetworkTextReplaceContext.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.plugin.context; + +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.text.minimessage.*; + +import java.util.Map; + +public final class NetworkTextReplaceContext extends AbstractChainParameterContext { + private final Player player; + + public NetworkTextReplaceContext(Player player) { + super(new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player))); + this.player = player; + } + + public static NetworkTextReplaceContext of(Player player) { + return new NetworkTextReplaceContext(player); + } + + public Player player() { + return player; + } + + @Override + public TagResolver[] tagResolvers() { + if (this.tagResolvers == null) { + this.tagResolvers = new TagResolver[]{ShiftTag.INSTANCE, ImageTag.INSTANCE, new PlaceholderTag(this), new I18NTag(this), + new NamedArgumentTag(this), new ExpressionTag(this), new GlobalVariableTag(this)}; + } + return this.tagResolvers; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java index 15e0aa919..57fb85de9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.plugin.network; import it.unimi.dsi.fastutil.ints.IntList; +import net.momirealms.craftengine.core.entity.player.Player; public interface EntityPacketHandler { @@ -8,7 +9,7 @@ public interface EntityPacketHandler { return false; } - default void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + default void handleSetEntityData(Player user, ByteBufPacketEvent event) { } default void handleSyncEntityPosition(NetWorkUser user, NMSPacketEvent event, Object packet) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/component/ComponentProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/component/ComponentProvider.java new file mode 100644 index 000000000..87b021160 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/component/ComponentProvider.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.plugin.text.component; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.util.AdventureHelper; + +import java.util.function.Function; + +import static net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine.CUSTOM_RESOLVERS; + +public sealed interface ComponentProvider extends Function + permits ComponentProvider.Constant, ComponentProvider.MiniMessage { + + static ComponentProvider constant(Component component) { + return new Constant(component); + } + + static ComponentProvider miniMessageOrConstant(String line) { + if (line.equals(AdventureHelper.customMiniMessage().stripTags(line, CUSTOM_RESOLVERS))) { + return constant(AdventureHelper.miniMessage().deserialize(line)); + } else { + return new MiniMessage(line); + } + } + + non-sealed class Constant implements ComponentProvider { + private final Component value; + + public Constant(final Component value) { + this.value = value; + } + + @Override + public Component apply(Context context) { + return this.value; + } + } + + non-sealed class MiniMessage implements ComponentProvider { + private final String value; + + public MiniMessage(final String value) { + this.value = value; + } + + @Override + public Component apply(Context context) { + return AdventureHelper.miniMessage().deserialize(this.value, context.tagResolvers()); + } + } +} \ No newline at end of file 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 e988d1f68..d24d8d4ca 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 @@ -13,6 +13,8 @@ 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.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.sparrow.nbt.Tag; import net.momirealms.sparrow.nbt.adventure.NBTComponentSerializer; import net.momirealms.sparrow.nbt.adventure.NBTSerializerOptions; @@ -356,12 +358,13 @@ public class AdventureHelper { return AdventureHelper.plainTextContent(resultComponent); } - public static Component replaceText(Component text, Map replacements) { + public static Component replaceText(Component text, Map replacements, Context context) { String patternString = replacements.keySet().stream() .map(Pattern::quote) .collect(Collectors.joining("|")); return text.replaceText(builder -> builder.match(Pattern.compile(patternString)) - .replacement((result, b) -> replacements.get(result.group()))); + .replacement((result, b) -> + replacements.get(result.group()).apply(context))); } } diff --git a/gradle.properties b/gradle.properties index 3c65f77b5..a99f6d22e 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.5 +project_version=0.0.62.6 config_version=45 lang_version=25 project_group=net.momirealms @@ -40,7 +40,7 @@ commons_io_version=2.18.0 commons_imaging_version=1.0.0-alpha6 commons_lang3_version=3.17.0 sparrow_nbt_version=0.9.4 -sparrow_util_version=0.50.9 +sparrow_util_version=0.51 fastutil_version=8.5.15 netty_version=4.1.124.Final joml_version=1.10.8 From 01f2d81b5d8f080a8ea6e5218354846775ac0fc0 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 30 Aug 2025 02:43:37 +0800 Subject: [PATCH 046/177] =?UTF-8?q?=E9=9A=8F=E5=A4=84=E5=8F=AF=E7=94=A8glo?= =?UTF-8?q?bal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/PacketConsumers.java | 15 ++-- .../handler/ItemFramePacketHandler.java | 1 - ...andler.java => MinecartPacketHandler.java} | 8 +-- .../core/font/AbstractFontManager.java | 70 +++++++++++++------ .../plugin/context/GlobalVariableManager.java | 5 ++ .../craftengine/core/util/MutableBoolean.java | 17 +++++ 6 files changed, 80 insertions(+), 36 deletions(-) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/{AbstractMinecartPacketHandler.java => MinecartPacketHandler.java} (93%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/MutableBoolean.java 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 04e46152c..cda262388 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 @@ -142,13 +142,13 @@ public class PacketConsumers { 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(AbstractMinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.COMMAND_BLOCK_MINECART$registryId] = simpleAddEntityHandler(AbstractMinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.FURNACE_MINECART$registryId] = simpleAddEntityHandler(AbstractMinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.HOPPER_MINECART$registryId] = simpleAddEntityHandler(AbstractMinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.MINECART$registryId] = simpleAddEntityHandler(AbstractMinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.SPAWNER_MINECART$registryId] = simpleAddEntityHandler(AbstractMinecartPacketHandler.INSTANCE); - ADD_ENTITY_HANDLERS[MEntityTypes.TNT_MINECART$registryId] = simpleAddEntityHandler(AbstractMinecartPacketHandler.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); @@ -495,7 +495,6 @@ public class PacketConsumers { 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()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java index caf19a28d..eeefbcc48 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemFramePacketHandler.java @@ -9,7 +9,6 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; -import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.FriendlyByteBuf; import org.bukkit.inventory.ItemStack; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java similarity index 93% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java index 67774af83..653b1f18a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/AbstractMinecartPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/MinecartPacketHandler.java @@ -23,9 +23,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; -public class AbstractMinecartPacketHandler implements EntityPacketHandler { - public static final AbstractMinecartPacketHandler INSTANCE = new AbstractMinecartPacketHandler(); - private static final BlockStateHandler HANDLER = VersionHelper.isOrAbove1_21_3() ? BlockStateHandler_1_21_3.INSTANCE : BlockStateHandler_1_20.INSTANCE; +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; @Override public void handleSetEntityData(Player user, ByteBufPacketEvent event) { @@ -36,7 +36,7 @@ public class AbstractMinecartPacketHandler implements EntityPacketHandler { for (int i = 0; i < packedItems.size(); i++) { Object packedItem = packedItems.get(i); int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); - Object blockState = HANDLER.handle(user, packedItem, entityDataId); + Object blockState = BLOCK_STATE_HANDLER.handle(user, packedItem, entityDataId); if (blockState != null) { packedItems.set(i, blockState); isChanged = true; 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 3a5cef4e6..adfbebdef 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 @@ -42,7 +42,7 @@ public abstract class AbstractFontManager implements FontManager { protected Trie imageTagTrie; protected Trie emojiKeywordTrie; - protected Map tagMapper; + protected Map networkTagMapper; protected Map emojiMapper; protected List emojiList; protected List allEmojiSuggestions; @@ -58,6 +58,7 @@ public abstract class AbstractFontManager implements FontManager { this.offsetFont = Optional.ofNullable(plugin.config().settings().getSection("image.offset-characters")) .map(OffsetFont::new) .orElse(null); + this.networkTagMapper = new HashMap<>(1024); } @Override @@ -66,6 +67,9 @@ public abstract class AbstractFontManager implements FontManager { this.images.clear(); this.illegalChars.clear(); this.emojis.clear(); + if (this.networkTagMapper != null) { + this.networkTagMapper.clear(); + } } @Override @@ -81,6 +85,9 @@ public abstract class AbstractFontManager implements FontManager { @Override public void delayedLoad() { Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> this.illegalChars.addAll(font.codepointsInUse())); + this.registerImageTags(); + this.registerShiftTags(); + this.registerGlobalTags(); this.buildImageTagTrie(); this.buildEmojiKeywordsTrie(); this.emojiList = new ArrayList<>(this.emojis.values()); @@ -89,6 +96,39 @@ public abstract class AbstractFontManager implements FontManager { .collect(Collectors.toList()); } + private void registerGlobalTags() { + for (Map.Entry entry : this.plugin.globalVariableManager().globalVariables().entrySet()) { + String globalTag = globalTag(entry.getKey()); + this.networkTagMapper.put(globalTag, ComponentProvider.miniMessageOrConstant(entry.getValue())); + this.networkTagMapper.put("\\" + globalTag, ComponentProvider.constant(Component.text(entry.getValue()))); + } + } + + private void registerShiftTags() { + for (int i = -256; i <= 256; i++) { + String shiftTag = ""; + this.networkTagMapper.put(shiftTag, ComponentProvider.constant(this.offsetFont.createOffset(i))); + this.networkTagMapper.put("\\" + shiftTag, ComponentProvider.constant(Component.text(shiftTag))); + } + } + + private void registerImageTags() { + for (BitmapImage image : this.images.values()) { + String id = image.id().toString(); + String simpleImageTag = imageTag(id); + this.networkTagMapper.put(simpleImageTag, ComponentProvider.constant(image.componentAt(0, 0))); + this.networkTagMapper.put("\\" + simpleImageTag, ComponentProvider.constant(Component.text(simpleImageTag))); + for (int i = 0; i < image.rows(); i++) { + for (int j = 0; j < image.columns(); j++) { + String imageArgs = id + ":" + i + ":" + j; + String imageTag = imageTag(imageArgs); + this.networkTagMapper.put(imageTag, ComponentProvider.constant(image.componentAt(i, j))); + this.networkTagMapper.put("\\" + imageTag, ComponentProvider.constant(Component.text(imageTag))); + } + } + } + } + @Override public Map matchTags(String json) { if (this.imageTagTrie == null) { @@ -97,7 +137,7 @@ public abstract class AbstractFontManager implements FontManager { Map tags = new HashMap<>(); for (Token token : this.imageTagTrie.tokenize(json)) { if (token.isMatch()) { - tags.put(token.getFragment(), this.tagMapper.get(token.getFragment())); + tags.put(token.getFragment(), this.networkTagMapper.get(token.getFragment())); } } return tags; @@ -266,29 +306,9 @@ public abstract class AbstractFontManager implements FontManager { } private void buildImageTagTrie() { - this.tagMapper = new HashMap<>(1024); - for (BitmapImage image : this.images.values()) { - String id = image.id().toString(); - String simpleImageTag = imageTag(id); - this.tagMapper.put(simpleImageTag, ComponentProvider.constant(image.componentAt(0, 0))); - this.tagMapper.put("\\" + simpleImageTag, ComponentProvider.constant(Component.text(simpleImageTag))); - for (int i = 0; i < image.rows(); i++) { - for (int j = 0; j < image.columns(); j++) { - String imageArgs = id + ":" + i + ":" + j; - String imageTag = imageTag(imageArgs); - this.tagMapper.put(imageTag, ComponentProvider.constant(image.componentAt(i, j))); - this.tagMapper.put("\\" + imageTag, ComponentProvider.constant(Component.text(imageTag))); - } - } - } - for (int i = -256; i <= 256; i++) { - String shiftTag = ""; - this.tagMapper.put(shiftTag, ComponentProvider.constant(this.offsetFont.createOffset(i))); - this.tagMapper.put("\\" + shiftTag, ComponentProvider.constant(Component.text(shiftTag))); - } this.imageTagTrie = Trie.builder() .ignoreOverlaps() - .addKeywords(this.tagMapper.keySet()) + .addKeywords(this.networkTagMapper.keySet()) .build(); } @@ -296,6 +316,10 @@ public abstract class AbstractFontManager implements FontManager { return ""; } + private static String globalTag(String text) { + return ""; + } + @Override public boolean isDefaultFontInUse() { return !this.illegalChars.isEmpty(); 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 5c29100a9..eb0b3ae25 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 @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import org.jetbrains.annotations.Nullable; import java.nio.file.Path; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,6 +26,10 @@ public class GlobalVariableManager implements Manageable { this.globalVariables.clear(); } + public Map globalVariables() { + return Collections.unmodifiableMap(this.globalVariables); + } + public ConfigParser parser() { return this.parser; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MutableBoolean.java b/core/src/main/java/net/momirealms/craftengine/core/util/MutableBoolean.java new file mode 100644 index 000000000..dfe5b977a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MutableBoolean.java @@ -0,0 +1,17 @@ +package net.momirealms.craftengine.core.util; + +public class MutableBoolean { + private boolean value; + + public MutableBoolean(boolean value) { + this.value = value; + } + + public boolean booleanValue() { + return value; + } + + public void set(boolean value) { + this.value = value; + } +} From 7cbf58e3d020d234ac364320880d2394166a392a Mon Sep 17 00:00:00 2001 From: iiabc <404e.abc@gmail.com> Date: Sat, 30 Aug 2025 10:45:40 +0800 Subject: [PATCH 047/177] =?UTF-8?q?feat(compatibility):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20Zaphkiel=20=E7=89=A9=E5=93=81=E6=9D=A5=E6=BA=90?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ZaphkielSource 类实现 ExternalItemSource 接口 - 在 BukkitCompatibilityManager 中注册 ZaphkielSource - 更新 build.gradle.kts 文件,添加 Zaphkiel API 依赖 - 在 paper-loader 中添加 Zaphkiel --- bukkit/compatibility/build.gradle.kts | 3 ++ .../BukkitCompatibilityManager.java | 9 ++--- .../compatibility/item/ZaphkielSource.java | 34 +++++++++++++++++++ bukkit/paper-loader/build.gradle.kts | 1 + 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 110a0b229..f6795ac32 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -14,6 +14,7 @@ repositories { maven("https://nexus.neetgames.com/repository/maven-releases/") // mcmmo 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 } dependencies { @@ -67,6 +68,8 @@ dependencies { compileOnly("com.willfp:libreforge:4.58.1") // AureliumSkills compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21") + // Zaphkiel + compileOnly("ink.ptms:ZaphkielAPI:2.1.0") } 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 5e752460f..303f3790b 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 @@ -1,10 +1,7 @@ package net.momirealms.craftengine.bukkit.compatibility; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.compatibility.item.CustomFishingSource; -import net.momirealms.craftengine.bukkit.compatibility.item.MMOItemsSource; -import net.momirealms.craftengine.bukkit.compatibility.item.MythicMobsSource; -import net.momirealms.craftengine.bukkit.compatibility.item.NeigeItemsSource; +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; @@ -249,6 +246,10 @@ public class BukkitCompatibilityManager implements CompatibilityManager { itemManager.registerExternalItemSource(new CustomFishingSource()); logHook("CustomFishing"); } + if (this.isPluginEnabled("Zaphkiel")) { + itemManager.registerExternalItemSource(new ZaphkielSource()); + logHook("Zaphkiel"); + } } private Plugin getPlugin(String name) { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java new file mode 100644 index 000000000..25cb5917b --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.bukkit.compatibility.item; + +import ink.ptms.zaphkiel.Zaphkiel; +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 javax.annotation.Nullable; +import java.util.Optional; + +/** + * @author iiabc + * @since 2025/8/30 09:39 + */ +public class ZaphkielSource implements ExternalItemSource { + + @Override + public String plugin() { + return "zaphkiel"; + } + + @Override + public @Nullable ItemStack build(String id, ItemBuildContext context) { + Player player = Optional.ofNullable(context.player()).map(it -> (Player) it.platformPlayer()).orElse(null); + return Zaphkiel.INSTANCE.api().getItemManager().generateItemStack(id, player); + } + + @Override + public String id(ItemStack item) { + return Zaphkiel.INSTANCE.api().getItemHandler().getItemId(item); + } + +} diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index a5a269d3b..16a4caf37 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -82,6 +82,7 @@ paper { register("MMOItems") { required = false } register("MythicMobs") { required = false } register("CustomFishing") { required = false } + register("Zaphkiel") { required = false } // leveler register("AuraSkills") { required = false } From a3c7eeebc6a20346eeafc5a841b04e55da46467b Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 30 Aug 2025 18:00:25 +0800 Subject: [PATCH 048/177] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/config.yml | 2 -- .../net/momirealms/craftengine/core/plugin/config/Config.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index cc87f1c59..b29463033 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -29,7 +29,6 @@ resource-pack: seed: 0 # 0 = random seed fake-directory: false escape-unicode: false - break-json: false resource-location: enable: true random-namespace: @@ -41,7 +40,6 @@ resource-pack: anti-unzip: false random-atlas: images-per-canvas: 32 # 0 = disable - use-double: true # Sometimes, some vanilla files that have been overwritten might be mistakenly obfuscated. # Please add the ignored textures/models/sounds here. bypass-textures: 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 d1268aca1..8956ca100 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 @@ -284,7 +284,7 @@ public class Config { resource_pack$protection$obfuscation$resource_location$random_path$source = config.getString("resource-pack.protection.obfuscation.resource-location.random-path.source", "obf"); resource_pack$protection$obfuscation$resource_location$random_path$anti_unzip = config.getBoolean("resource-pack.protection.obfuscation.resource-location.random-path.anti-unzip", false); resource_pack$protection$obfuscation$resource_location$random_atlas$images_per_canvas = config.getInt("resource-pack.protection.obfuscation.resource-location.random-atlas.images-per-canvas", 16); - resource_pack$protection$obfuscation$resource_location$random_atlas$use_double = config.getBoolean("resource-pack.protection.obfuscation.resource-location.random-atlas.use-double", true); + resource_pack$protection$obfuscation$resource_location$random_atlas$use_double = config.getBoolean("resource-pack.protection.obfuscation.resource-location.random-atlas.use-double", false); resource_pack$protection$obfuscation$resource_location$bypass_textures = config.getStringList("resource-pack.protection.obfuscation.resource-location.bypass-textures"); 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"); From 16334e0c88d3ba5cf5cb8f97af1c827c347fa2c2 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 30 Aug 2025 20:22:44 +0800 Subject: [PATCH 049/177] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8C=BA=E5=9D=97?= =?UTF-8?q?=E8=AF=BB=E5=86=99=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/build.gradle.kts | 4 +- .../compatibility/item/ZaphkielSource.java | 1 - bukkit/loader/build.gradle.kts | 4 ++ bukkit/paper-loader/build.gradle.kts | 4 ++ .../plugin/network/BukkitNetworkManager.java | 3 +- .../plugin/network/PacketConsumers.java | 24 +++++++-- .../bukkit/plugin/network/PacketIds.java | 2 + .../plugin/network/id/PacketIds1_20.java | 5 ++ .../plugin/network/id/PacketIds1_20_5.java | 5 ++ .../LeavesReflections.java | 2 +- .../minecraft/NetworkReflections.java | 7 +++ .../plugin/user/BukkitServerPlayer.java | 29 ++++++++-- .../bukkit/world/BukkitWorldManager.java | 4 +- core/build.gradle.kts | 2 + .../core/entity/player/Player.java | 6 --- .../core/plugin/network/NetWorkUser.java | 12 +++++ .../craftengine/core/world/CEWorld.java | 50 +++++------------- gradle.properties | 1 + libs/boosted-yaml-1.3.7.jar | Bin 447231 -> 438382 bytes libs/concurrentutil-0.0.3.jar | Bin 0 -> 159682 bytes 20 files changed, 107 insertions(+), 58 deletions(-) rename bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/{minecraft => leaves}/LeavesReflections.java (97%) create mode 100644 libs/concurrentutil-0.0.3.jar diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index c9bf92f91..7519028f8 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -4,11 +4,11 @@ plugins { } repositories { + mavenCentral() maven("https://jitpack.io/") maven("https://repo.momirealms.net/releases/") maven("https://libraries.minecraft.net/") maven("https://repo.papermc.io/repository/maven-public/") - mavenCentral() } dependencies { @@ -60,6 +60,8 @@ dependencies { compileOnly("org.ahocorasick:ahocorasick:${rootProject.properties["ahocorasick_version"]}") // authlib compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") + // concurrentutil + compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}") } java { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java index 25cb5917b..3c9329b11 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/ZaphkielSource.java @@ -30,5 +30,4 @@ public class ZaphkielSource implements ExternalItemSource { public String id(ItemStack item) { return Zaphkiel.INSTANCE.api().getItemHandler().getItemId(item); } - } diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index c7ef218b6..53736c927 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -21,6 +21,9 @@ dependencies { implementation(project(":bukkit:compatibility:legacy")) implementation(project(":common-files")) + // concurrentutil + implementation(files("${rootProject.rootDir}/libs/concurrentutil-${rootProject.properties["concurrent_util_version"]}.jar")) + implementation("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") implementation("net.momirealms:antigrieflib:${rootProject.properties["anti_grief_version"]}") implementation("net.momirealms:craft-engine-nms-helper:${rootProject.properties["nms_helper_version"]}") @@ -77,5 +80,6 @@ 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("ca.spottedleaf.concurrentutil", "net.momirealms.craftengine.libraries.concurrentutil") } } diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index 16a4caf37..8929b281f 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -23,6 +23,9 @@ dependencies { implementation(project(":bukkit:compatibility:legacy")) implementation(project(":common-files")) + // concurrentutil + implementation(files("${rootProject.rootDir}/libs/concurrentutil-${rootProject.properties["concurrent_util_version"]}.jar")) + implementation("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") implementation("net.momirealms:antigrieflib:${rootProject.properties["anti_grief_version"]}") implementation("net.momirealms:craft-engine-nms-helper-mojmap:${rootProject.properties["nms_helper_version"]}") @@ -150,5 +153,6 @@ 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("ca.spottedleaf.concurrentutil", "net.momirealms.craftengine.libraries.concurrentutil") } } 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 e98de26d8..8a3f5d23f 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,8 +11,8 @@ 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.reflection.leaves.LeavesReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.LeavesReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.LibraryReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; @@ -213,6 +213,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes 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()); 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 cda262388..f3897953b 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,10 +64,7 @@ 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.BlockHitResult; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.EntityHitResult; -import net.momirealms.craftengine.core.world.WorldEvents; +import net.momirealms.craftengine.core.world.*; 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; @@ -75,6 +72,7 @@ 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; @@ -261,12 +259,29 @@ public class PacketConsumers { return MOD_BLOCK_STATE_MAPPINGS[stateId]; } + public static final BiConsumer FORGET_LEVEL_CHUNK = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + if (VersionHelper.isOrAbove1_20_2()) { + long chunkPos = buf.readLong(); + user.setChunkTrackStatus(new ChunkPos(chunkPos), false); + } else { + int x = buf.readInt(); + int y = buf.readInt(); + user.setChunkTrackStatus(ChunkPos.of(x, y), false); + } + } 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(); + player.setChunkTrackStatus(ChunkPos.of(chunkX, chunkZ), true); boolean named = !VersionHelper.isOrAbove1_20_2(); // ClientboundLevelChunkPacketData int heightmapsCount = 0; @@ -1262,6 +1277,7 @@ public class PacketConsumers { 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"); } 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 bef170327..dd41cb280 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 @@ -67,4 +67,6 @@ public interface PacketIds { int serverboundInteractPacket(); int clientboundUpdateRecipesPacket(); + + int clientboundForgetLevelChunkPacket(); } 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 34981a06b..7a3b81947 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 @@ -169,4 +169,9 @@ public class PacketIds1_20 implements PacketIds { public int clientboundUpdateAdvancementsPacket() { return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundUpdateAdvancementsPacket); } + + @Override + public int clientboundForgetLevelChunkPacket() { + return PacketIdFinder.clientboundByClazz(NetworkReflections.clazz$ClientboundForgetLevelChunkPacket); + } } 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 216b11d56..b3a9b624a 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 @@ -168,4 +168,9 @@ public class PacketIds1_20_5 implements PacketIds { public int serverboundInteractPacket() { return PacketIdFinder.serverboundByName("minecraft:interact"); } + + @Override + public int clientboundForgetLevelChunkPacket() { + return PacketIdFinder.clientboundByName("minecraft:forget_level_chunk"); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/LeavesReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/leaves/LeavesReflections.java similarity index 97% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/LeavesReflections.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/leaves/LeavesReflections.java index b7b89995c..dd5522890 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/LeavesReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/leaves/LeavesReflections.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; +package net.momirealms.craftengine.bukkit.plugin.reflection.leaves; //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/plugin/reflection/minecraft/NetworkReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java index 48bb2b8cd..4e421cff8 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 @@ -1665,4 +1665,11 @@ public final class NetworkReflections { ), VersionHelper.isOrAbove1_21_5() ); + + public static final Class clazz$ClientboundForgetLevelChunkPacket = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "network.protocol.game.PacketPlayOutUnloadChunk", + "network.protocol.game.ClientboundForgetLevelChunkPacket" + ) + ); } 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 2d13134ce..9d4ebc8ee 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 @@ -34,6 +34,7 @@ import net.momirealms.craftengine.core.util.Direction; 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 org.bukkit.*; @@ -109,7 +110,11 @@ public class BukkitServerPlayer extends Player { private double cachedInteractionRange; // 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<>()); + // entity view private final Map entityTypeView = new ConcurrentHashMap<>(); public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { @@ -497,9 +502,6 @@ public class BukkitServerPlayer extends Player { Item tool = getItemInHand(InteractionHand.MAIN_HAND); boolean isCorrectTool = FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(tool.getLiteralObject(), blockState); // 如果自定义方块在服务端侧未使用正确的工具,那么需要还原挖掘速度 - if (!isCorrectTool) { - progress *= (10f / 3f); - } if (!BlockStateUtils.isCorrectTool(customState, tool)) { progress *= customState.settings().incorrectToolSpeed(); } @@ -1032,4 +1034,23 @@ public class BukkitServerPlayer extends Player { public CooldownData cooldown() { return this.cooldownData; } + + @Override + public boolean isChunkTracked(ChunkPos chunkPos) { + return this.trackedChunks.contains(chunkPos); + } + + @Override + public void setChunkTrackStatus(ChunkPos chunkPos, boolean tracked) { + if (tracked) { + this.trackedChunks.add(chunkPos); + } else { + this.trackedChunks.remove(chunkPos); + } + } + + @Override + public void clearTrackedChunks() { + this.trackedChunks.clear(); + } } 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 6f2c47628..134e2ee7f 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,5 +1,6 @@ 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; @@ -29,7 +30,6 @@ import org.bukkit.event.world.*; import org.jetbrains.annotations.NotNull; import java.io.IOException; -import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -53,7 +53,7 @@ public class BukkitWorldManager implements WorldManager, Listener { public BukkitWorldManager(BukkitCraftEngine plugin) { instance = this; this.plugin = plugin; - this.worlds = new HashMap<>(); + this.worlds = new Object2ObjectOpenHashMap<>(32, 0.5f); this.storageAdaptor = new DefaultStorageAdaptor(); for (World world : Bukkit.getWorlds()) { this.worlds.put(world.getUID(), new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); diff --git a/core/build.gradle.kts b/core/build.gradle.kts index df535d1cd..6a172d1c3 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -65,6 +65,8 @@ dependencies { compileOnly("com.mojang:brigadier:${rootProject.properties["mojang_brigadier_version"]}") // authlib compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") + // concurrentutil + compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}") } java { 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 b0adb4685..6675659ba 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 @@ -10,8 +10,6 @@ import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; import org.jetbrains.annotations.NotNull; -import java.util.List; - public abstract class Player extends AbstractEntity implements NetWorkUser { private static final Key TYPE = Key.of("minecraft:player"); @@ -26,10 +24,6 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { @Override public abstract Object serverPlayer(); - public abstract void sendPackets(List packet, boolean immediately); - - public abstract void sendPackets(List packet, boolean immediately, Runnable sendListener); - public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract void setClientSideCanBreakBlock(boolean canBreak); 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 453b8e055..83661fd2f 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 @@ -5,9 +5,11 @@ import io.netty.channel.ChannelHandler; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.world.ChunkPos; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -38,6 +40,10 @@ public interface NetWorkUser { void sendPacket(Object packet, boolean immediately, Runnable sendListener); + void sendPackets(List packet, boolean immediately); + + void sendPackets(List packet, boolean immediately, Runnable sendListener); + void sendCustomPayload(Key channel, byte[] data); void kick(Component message); @@ -71,4 +77,10 @@ public interface NetWorkUser { void setShouldProcessFinishConfiguration(boolean shouldProcess); boolean shouldProcessFinishConfiguration(); + + boolean isChunkTracked(ChunkPos chunkPos); + + void setChunkTrackStatus(ChunkPos chunkPos, boolean tracked); + + void clearTrackedChunks(); } 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 2a636bd69..62dfa77da 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,6 @@ package net.momirealms.craftengine.core.world; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.world.chunk.CEChunk; @@ -10,17 +10,14 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Collection; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; public abstract class CEWorld { public static final String REGION_DIRECTORY = "craftengine"; protected final World world; - protected final Map loadedChunkMap; + protected final ConcurrentLong2ReferenceChainedHashTable loadedChunkMap; protected final WorldDataStorage worldDataStorage; - protected final ReentrantReadWriteLock loadedChunkMapLock = new ReentrantReadWriteLock(); protected final WorldHeight worldHeightAccessor; protected final Set updatedSectionSet = ConcurrentHashMap.newKeySet(128); @@ -29,7 +26,7 @@ public abstract class CEWorld { public CEWorld(World world, StorageAdaptor adaptor) { this.world = world; - this.loadedChunkMap = new Long2ObjectOpenHashMap<>(1024, 0.5f); + this.loadedChunkMap = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(1024, 0.5f); this.worldDataStorage = adaptor.adapt(world); this.worldHeightAccessor = world.worldHeight(); this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; @@ -37,16 +34,15 @@ public abstract class CEWorld { public CEWorld(World world, WorldDataStorage dataStorage) { this.world = world; - this.loadedChunkMap = new Long2ObjectOpenHashMap<>(1024, 0.5f); + this.loadedChunkMap = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(1024, 0.5f); this.worldDataStorage = dataStorage; this.worldHeightAccessor = world.worldHeight(); this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; } public void save() { - this.loadedChunkMapLock.readLock().lock(); try { - for (Map.Entry entry : this.loadedChunkMap.entrySet()) { + for (ConcurrentLong2ReferenceChainedHashTable.TableEntry entry : this.loadedChunkMap.entrySet()) { CEChunk chunk = entry.getValue(); if (chunk.dirty()) { worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), chunk); @@ -55,8 +51,6 @@ public abstract class CEWorld { } } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to save world chunks", e); - } finally { - this.loadedChunkMapLock.readLock().unlock(); } } @@ -65,44 +59,24 @@ public abstract class CEWorld { } public boolean isChunkLoaded(final long chunkPos) { - this.loadedChunkMapLock.readLock().lock(); - try { - return loadedChunkMap.containsKey(chunkPos); - } finally { - this.loadedChunkMapLock.readLock().unlock(); - } + return loadedChunkMap.containsKey(chunkPos); } public void addLoadedChunk(CEChunk chunk) { - this.loadedChunkMapLock.writeLock().lock(); - try { - this.loadedChunkMap.put(chunk.chunkPos().longKey(), chunk); - } finally { - this.loadedChunkMapLock.writeLock().unlock(); - } + this.loadedChunkMap.put(chunk.chunkPos().longKey(), chunk); } public void removeLoadedChunk(CEChunk chunk) { - this.loadedChunkMapLock.writeLock().lock(); - try { - this.loadedChunkMap.remove(chunk.chunkPos().longKey()); - if (this.lastChunk == chunk) { - this.lastChunk = null; - this.lastChunkPos = ChunkPos.INVALID_CHUNK_POS; - } - } finally { - this.loadedChunkMapLock.writeLock().unlock(); + 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) { - this.loadedChunkMapLock.readLock().lock(); - try { - return getChunkAtIfLoadedMainThread(chunkPos); - } finally { - this.loadedChunkMapLock.readLock().unlock(); - } + return getChunkAtIfLoadedMainThread(chunkPos); } @Nullable diff --git a/gradle.properties b/gradle.properties index a99f6d22e..2bc460d2f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -57,6 +57,7 @@ amazon_awssdk_version=2.31.23 amazon_awssdk_eventstream_version=1.0.1 jimfs_version=1.3.0 authlib_version=6.0.58 +concurrent_util_version=0.0.3 # Proxy settings #systemProp.socks.proxyHost=127.0.0.1 diff --git a/libs/boosted-yaml-1.3.7.jar b/libs/boosted-yaml-1.3.7.jar index ea489cb340e2f441efcc396afc0b675b9750ab3c..b6481f3fa6d38ff7459b17679b99af86a79f3da0 100644 GIT binary patch delta 53 zcmeyrRQlZmsfHHD7N!>F7M2#)Eo`6JrzdN$No`-E%I3R5gq4i}2;v#z8PiuVFnnYO FaRK}>4=4Zt delta 8834 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

qd+%ILsE*< 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 Date: Mon, 1 Sep 2025 01:46:38 +0800 Subject: [PATCH 050/177] =?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 051/177] =?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 052/177] =?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 053/177] =?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 054/177] =?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 055/177] =?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 056/177] =?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 057/177] =?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 058/177] =?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 059/177] =?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 060/177] =?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 061/177] =?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 062/177] =?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 063/177] =?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 064/177] =?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 065/177] =?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 066/177] =?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 067/177] =?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 068/177] =?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 069/177] 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 070/177] 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 071/177] =?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 072/177] =?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 073/177] =?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 074/177] =?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 075/177] =?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 076/177] =?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 077/177] =?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 078/177] =?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 079/177] =?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 080/177] =?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 081/177] =?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 082/177] =?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 083/177] =?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 084/177] 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 085/177] 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 086/177] =?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 087/177] =?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 088/177] =?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 089/177] =?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 090/177] =?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 091/177] =?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 092/177] =?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 093/177] =?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 094/177] =?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 095/177] =?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 096/177] =?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 097/177] =?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 098/177] =?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 099/177] =?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 100/177] =?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 101/177] =?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 102/177] =?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 103/177] =?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 104/177] =?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 105/177] =?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 106/177] =?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 107/177] =?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 108/177] =?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 109/177] =?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 110/177] =?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 111/177] =?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 112/177] =?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 113/177] =?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 114/177] =?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 115/177] =?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 116/177] =?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 117/177] =?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 118/177] =?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 119/177] =?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 120/177] =?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 121/177] 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 122/177] =?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 123/177] =?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 124/177] =?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 125/177] =?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 126/177] =?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 127/177] =?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 128/177] =?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 129/177] =?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 130/177] 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 131/177] =?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 132/177] =?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 133/177] =?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 134/177] =?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 135/177] =?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 136/177] =?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 137/177] =?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 138/177] =?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 139/177] =?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 140/177] =?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 141/177] =?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 142/177] =?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 143/177] =?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 144/177] =?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 145/177] =?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 146/177] =?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 147/177] =?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 148/177] 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 149/177] =?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 150/177] =?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 151/177] =?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 152/177] =?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 153/177] =?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 154/177] =?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 155/177] =?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 156/177] =?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 157/177] =?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 158/177] =?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 159/177] =?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 160/177] 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 161/177] =?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 162/177] =?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 163/177] =?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 164/177] =?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 165/177] =?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 166/177] =?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 167/177] =?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 168/177] =?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 169/177] =?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 170/177] =?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 171/177] =?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 172/177] =?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 173/177] =?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 174/177] =?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 175/177] =?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 176/177] =?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 177/177] =?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

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