diff --git a/Allay-API/src/main/java/org/allaymc/api/block/component/common/BlockBaseComponent.java b/Allay-API/src/main/java/org/allaymc/api/block/component/common/BlockBaseComponent.java index 0855917e4..32b037862 100644 --- a/Allay-API/src/main/java/org/allaymc/api/block/component/common/BlockBaseComponent.java +++ b/Allay-API/src/main/java/org/allaymc/api/block/component/common/BlockBaseComponent.java @@ -14,13 +14,9 @@ import org.allaymc.api.item.ItemStack; import org.allaymc.api.item.enchantment.type.EnchantmentAquaAffinityType; import org.allaymc.api.item.enchantment.type.EnchantmentEfficiencyType; -import org.allaymc.api.item.tag.ItemTag; -import org.allaymc.api.item.type.ItemType; import org.allaymc.api.world.Dimension; -import static org.allaymc.api.data.VanillaItemTags.*; -import static org.allaymc.api.data.VanillaMaterialTypes.*; -import static org.allaymc.api.item.type.ItemTypes.*; +import static org.allaymc.api.item.ItemHelper.isSword; /** * Allay Project 2023/4/8 @@ -49,14 +45,11 @@ default void updateBlockProperty(BlockPropertyType property chunk.sendChunkPacket(Dimension.createBlockUpdatePacket(newBlockState, x, y, z, layer)); } - // TODO: 此方法为服务端权威方块破坏准备,目前不完善,等待后续完善 default double calculateBreakTime(BlockState blockState, ItemStack usedItem, Entity entity) { checkBlockType(blockState); double blockHardness = blockState.getBlockAttributes().hardness(); if (blockHardness == 0) return 0; - boolean isCorrectTool = isCorrectTool(blockState, usedItem); - //isAlwaysDestroyable为true时,表示方块可以被任何物品破坏并掉落物品 - //部分方块(例如石头,黑曜石)不可以被徒手挖去,强行挖取它们不会掉落任何物品,且挖掘速度会受到惩罚(baseTime增大5倍,正常是1.5倍) + boolean isCorrectTool = usedItem.isCorrectToolFor(blockState); boolean isAlwaysDestroyable = getBlockType().getMaterial().isAlwaysDestroyable(); boolean hasAquaAffinity = false; boolean isInWater = false; @@ -85,13 +78,17 @@ default double calculateBreakTime(BlockState blockState, ItemStack usedItem, Ent } // Calculate break time + // TODO: 需要进一步验证算法 double baseTime = ((isCorrectTool || isAlwaysDestroyable) ? 1.5 : 5.0) * blockHardness; double speed = 1.0d / baseTime; if (isCorrectTool) { // 工具等级(木制,石制,铁制,etc...)加成 - speed *= toolBreakTimeBonus(usedItem, blockState); + speed *= usedItem.getBreakTimeBonus(blockState); // 工具效率附魔加成 speed += speedBonusByEfficiency(efficiencyLevel); + } else if (isSword(usedItem.getItemType())) { + // 剑挖掘方块的最低速率是1.5倍 + speed *= 1.5; } // 实体急迫药水效果加成 speed *= 1.0d + (0.2d * hasteEffectLevel); @@ -109,57 +106,6 @@ private static double speedBonusByEfficiency(int efficiencyLevel) { return efficiencyLevel * efficiencyLevel + 1; } - // TODO: toolBreakTimeBonus()和isCorrectTool()需要完善,未覆盖所有方块 - private double toolBreakTimeBonus(ItemStack usedItem, BlockState blockState) { - var itemType = usedItem.getItemType(); - var materialType = blockState.getBlockType().getMaterial().materialType(); - // 剑破坏蜘蛛网加速 - if (itemType.hasItemTag(IS_SWORD)) return materialType == WEB ? 15.0 : 1.5; - if (itemType == SHEARS_TYPE) { - // 剪刀破坏羊毛和树叶加速 - if (materialType == CLOTH) { - return 5.0; - } else if (materialType == WEB || materialType == LEAVES) { - return 15.0; - } - return 1.0; - } - if (!itemType.hasItemTag(IS_TOOL)) return 1.0; - if (itemType.hasItemTag(GOLDEN_TIER)) return 12.0; - if (itemType.hasItemTag(NETHERITE_TIER)) return 9.0; - if (itemType.hasItemTag(DIAMOND_TIER)) return 8.0; - if (itemType.hasItemTag(IRON_TIER)) return 6.0; - if (itemType.hasItemTag(STONE_TIER)) return 4.0; - if (itemType.hasItemTag(WOODEN_TIER)) return 2.0; - return 1.0; - } - - default boolean isCorrectTool(BlockState blockState, ItemStack usedItem) { - checkBlockType(blockState); - // TODO: 实现挖掘正确工具判断 - return true; - } - - private boolean isItemTierBiggerThan(ItemType itemType, ItemTag tier) { - var itemTier = 0; - for (ItemTag itemTag : itemType.getItemTags()) { - var value = computeItemTier(itemTag); - if (value > itemTier) itemTier = value; - } - return itemTier > computeItemTier(tier); - } - - private int computeItemTier(ItemTag itemTag) { - int itemTier = 0; - if (itemTag == WOODEN_TIER) itemTier = 1; - if (itemTag == STONE_TIER) itemTier = 2; - if (itemTag == IRON_TIER) itemTier = 3; - if (itemTag == DIAMOND_TIER) itemTier = 4; - if (itemTag == NETHERITE_TIER) itemTier = 5; - if (itemTag == GOLDEN_TIER) itemTier = 6; - return itemTier; - } - private void checkBlockType(BlockState blockState) { if (blockState.getBlockType() != getBlockType()) throw new IllegalArgumentException("Block type is not match! Expected: " + getBlockType().getIdentifier() + ", actual: " + blockState.getBlockType().getIdentifier()); } diff --git a/Allay-API/src/main/java/org/allaymc/api/block/material/Material.java b/Allay-API/src/main/java/org/allaymc/api/block/material/Material.java index 12b2e1962..3fcf92aeb 100644 --- a/Allay-API/src/main/java/org/allaymc/api/block/material/Material.java +++ b/Allay-API/src/main/java/org/allaymc/api/block/material/Material.java @@ -24,6 +24,8 @@ public record Material( MaterialType materialType, boolean canBeMovingBlock, boolean canHavePrecipitation, + //isAlwaysDestroyable为true时,表示方块可以被任何物品破坏并掉落物品 + //部分方块(例如石头,黑曜石)不可以被徒手挖去,强行挖取它们不会掉落任何物品,且挖掘速度会受到惩罚(baseTime增大5倍,正常是1.5倍) boolean isAlwaysDestroyable, boolean isLiquid, boolean isSolid, diff --git a/Allay-API/src/main/java/org/allaymc/api/item/ItemHelper.java b/Allay-API/src/main/java/org/allaymc/api/item/ItemHelper.java index 48920b941..cff36c514 100644 --- a/Allay-API/src/main/java/org/allaymc/api/item/ItemHelper.java +++ b/Allay-API/src/main/java/org/allaymc/api/item/ItemHelper.java @@ -1,7 +1,9 @@ package org.allaymc.api.item; +import org.allaymc.api.data.VanillaItemTags; import org.allaymc.api.item.init.SimpleItemStackInitInfo; import org.allaymc.api.item.registry.ItemTypeRegistry; +import org.allaymc.api.item.type.ItemType; import org.allaymc.api.utils.Identifier; import org.cloudburstmc.nbt.NbtMap; @@ -26,4 +28,49 @@ public static ItemStack fromNBT(NbtMap nbt) { .extraTag(nbt.getCompound("tag")) .build()); } + + /** + * 获取物品的物品等级
+ * @return ItemTier对象, 或者null如果此物品不存在物品等级 + */ + public static ItemTier getItemTier(ItemType itemType) { + for (var tier : ItemTier.ORDER_OF_QUALITY) { + if (itemType.hasItemTag(tier.getItemTag())) { + return tier; + } + } + return null; + } + + public static boolean isTool(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_TOOL); + } + + public static boolean isSword(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_SWORD); + } + + public static boolean isPickaxe(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_PICKAXE); + } + + public static boolean isHoe(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_HOE); + } + + public static boolean isAxe(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_AXE); + } + + public static boolean isShovel(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_SHOVEL); + } + + public static boolean isArmor(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.IS_ARMOR); + } + + public static boolean isTrimmableArmor(ItemType itemType) { + return itemType.hasItemTag(VanillaItemTags.TRIMMABLE_ARMORS); + } } diff --git a/Allay-API/src/main/java/org/allaymc/api/item/ItemTier.java b/Allay-API/src/main/java/org/allaymc/api/item/ItemTier.java new file mode 100644 index 000000000..6d638bc22 --- /dev/null +++ b/Allay-API/src/main/java/org/allaymc/api/item/ItemTier.java @@ -0,0 +1,34 @@ +package org.allaymc.api.item; + +import lombok.Getter; +import org.allaymc.api.item.tag.ItemTag; + +import static org.allaymc.api.data.VanillaItemTags.*; + +/** + * Allay Project 2024/6/12 + * + * @author daoge_cmd + */ +public enum ItemTier { + WOODEN(WOODEN_TIER), + STONE(STONE_TIER), + IRON(IRON_TIER), + DIAMOND(DIAMOND_TIER), + NETHERITE(NETHERITE_TIER), + GOLD(GOLDEN_TIER); + + public static final ItemTier[] ORDER_OF_QUALITY = + {GOLD, NETHERITE, DIAMOND, IRON, STONE, WOODEN}; + + @Getter + private final ItemTag itemTag; + + ItemTier(ItemTag itemTag) { + this.itemTag = itemTag; + } + + public boolean isBetterThan(ItemTier tier) { + return this.ordinal() > tier.ordinal(); + } +} diff --git a/Allay-API/src/main/java/org/allaymc/api/item/component/common/ItemBaseComponent.java b/Allay-API/src/main/java/org/allaymc/api/item/component/common/ItemBaseComponent.java index 5fdd6cd9d..c8c8a655a 100644 --- a/Allay-API/src/main/java/org/allaymc/api/item/component/common/ItemBaseComponent.java +++ b/Allay-API/src/main/java/org/allaymc/api/item/component/common/ItemBaseComponent.java @@ -2,7 +2,10 @@ import org.allaymc.api.block.data.BlockFace; import org.allaymc.api.block.type.BlockState; +import org.allaymc.api.data.VanillaBlockId; +import org.allaymc.api.data.VanillaItemId; import org.allaymc.api.entity.interfaces.EntityPlayer; +import org.allaymc.api.item.ItemHelper; import org.allaymc.api.item.ItemStack; import org.allaymc.api.item.component.ItemComponent; import org.allaymc.api.item.enchantment.EnchantmentType; @@ -13,8 +16,16 @@ import org.joml.Vector3fc; import org.joml.Vector3ic; +import java.util.EnumMap; import java.util.List; +import static org.allaymc.api.block.type.BlockTypes.BAMBOO_BLOCK_TYPE; +import static org.allaymc.api.data.VanillaItemTags.*; +import static org.allaymc.api.data.VanillaItemTags.WOODEN_TIER; +import static org.allaymc.api.data.VanillaMaterialTypes.*; +import static org.allaymc.api.item.type.ItemTypes.BAMBOO_TYPE; +import static org.allaymc.api.item.type.ItemTypes.SHEARS_TYPE; + /** * Allay Project 2023/5/19 * @@ -116,4 +127,44 @@ default NbtMap saveNBT() { short getEnchantmentLevel(EnchantmentType enchantmentType); void addEnchantment(EnchantmentType enchantmentType, short level); + + /** + * 获取破坏方块的加速倍数
+ * @param blockState 要破坏的方块 + * @return 加速倍速 + */ + default double getBreakTimeBonus(BlockState blockState) { + var itemType = getItemType(); + var materialType = blockState.getBlockType().getMaterial().materialType(); + // 剑破坏蜘蛛网加速 + if (itemType.hasItemTag(IS_SWORD)) { + if (materialType == WEB) return 15.0; + if (blockState.getBlockType() == BAMBOO_BLOCK_TYPE) return Integer.MAX_VALUE; + } + if (itemType == SHEARS_TYPE) { + // 剪刀破坏羊毛和树叶加速 + if (materialType == CLOTH) { + return 5.0; + } else if (materialType == WEB || materialType == LEAVES) { + return 15.0; + } + return 1.0; + } + if (!itemType.hasItemTag(IS_TOOL)) return 1.0; + if (itemType.hasItemTag(GOLDEN_TIER)) return 12.0; + if (itemType.hasItemTag(NETHERITE_TIER)) return 9.0; + if (itemType.hasItemTag(DIAMOND_TIER)) return 8.0; + if (itemType.hasItemTag(IRON_TIER)) return 6.0; + if (itemType.hasItemTag(STONE_TIER)) return 4.0; + if (itemType.hasItemTag(WOODEN_TIER)) return 2.0; + return 1.0; + } + + /** + * 判断是否是指定方块的正确破坏工具
+ * 若不是正确破坏工具,使用此物品破坏方块时将获得速度惩罚,且不会产生掉落物
+ * @param blockState 要破坏的方块 + * @return 是否是正确工具 + */ + boolean isCorrectToolFor(BlockState blockState); } diff --git a/Allay-Data/resources/block_correct_tool_special.nbt b/Allay-Data/resources/block_correct_tool_special.nbt new file mode 100644 index 000000000..05f3a3553 Binary files /dev/null and b/Allay-Data/resources/block_correct_tool_special.nbt differ diff --git a/Allay-Server/src/main/java/org/allaymc/server/item/component/common/ItemBaseComponentImpl.java b/Allay-Server/src/main/java/org/allaymc/server/item/component/common/ItemBaseComponentImpl.java index be9baeec2..f6b12bcd7 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/item/component/common/ItemBaseComponentImpl.java +++ b/Allay-Server/src/main/java/org/allaymc/server/item/component/common/ItemBaseComponentImpl.java @@ -4,7 +4,13 @@ import org.allaymc.api.block.data.BlockFace; import org.allaymc.api.block.type.BlockState; import org.allaymc.api.block.type.BlockType; +import org.allaymc.api.block.type.BlockTypes; +import org.allaymc.api.data.VanillaBlockId; +import org.allaymc.api.data.VanillaItemId; +import org.allaymc.api.data.VanillaMaterialTypes; +import org.allaymc.api.item.ItemHelper; import org.allaymc.api.item.enchantment.SimpleEnchantmentInstance; +import org.allaymc.api.utils.AllayStringUtils; import org.allaymc.api.utils.Identifier; import org.allaymc.api.component.annotation.ComponentIdentifier; import org.allaymc.api.component.annotation.ComponentedObject; @@ -23,18 +29,17 @@ import org.allaymc.api.item.type.ItemType; import org.allaymc.api.item.type.ItemTypes; import org.allaymc.api.world.Dimension; -import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.nbt.NbtMapBuilder; -import org.cloudburstmc.nbt.NbtType; +import org.cloudburstmc.nbt.*; import org.cloudburstmc.protocol.bedrock.data.GameType; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.joml.Vector3fc; import org.joml.Vector3ic; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.util.*; + +import static org.allaymc.api.item.ItemHelper.*; /** * Allay Project 2023/5/19 @@ -342,4 +347,99 @@ public short getEnchantmentLevel(EnchantmentType enchantmentType) { public void addEnchantment(EnchantmentType enchantmentType, short level) { enchantments.put(enchantmentType, new SimpleEnchantmentInstance(enchantmentType, level)); } + + // 记录那些对工具品质有要求的方块的正确工具集合 + private static final EnumMap CORRECT_TOOL_SPECIAL_MAP = new EnumMap<>(VanillaBlockId.class); + + static { + try (var reader = NbtUtils.createGZIPReader( + new BufferedInputStream( + Objects.requireNonNull( + ItemBaseComponentImpl + .class + .getClassLoader() + .getResourceAsStream("block_correct_tool_special.nbt"), + "block_correct_tool_special.nbt is missing!" + ) + ) + )) { + var nbtMap = (NbtMap) reader.readTag(); + nbtMap.forEach((k, v) -> { + var blockId = VanillaBlockId.fromIdentifier(new Identifier(k)); + var list = (NbtList) v; + VanillaItemId[] tools = list.stream().map(itemId -> VanillaItemId.fromIdentifier(new Identifier(itemId))).toArray(VanillaItemId[]::new); + CORRECT_TOOL_SPECIAL_MAP.put(blockId, tools); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isCorrectToolFor(BlockState blockState) { + var it = getItemType(); + var bt = blockState.getBlockType(); + var vanillaItemId = VanillaItemId.fromIdentifier(it.getIdentifier()); + var vanillaBlockId = VanillaBlockId.fromIdentifier(bt.getIdentifier()); + if (vanillaItemId != null && vanillaBlockId != null) { + var specialCorrectTools = CORRECT_TOOL_SPECIAL_MAP.get(vanillaBlockId); + if (specialCorrectTools != null) { + for (var tool : specialCorrectTools) { + if (tool == vanillaItemId) { + return true; + } + } + return false; + } + } + var mt = blockState.getBlockType().getMaterial().materialType(); + if (it == ItemTypes.SHEARS_TYPE) { + if ( + bt == BlockTypes.VINE_TYPE || + bt == BlockTypes.GLOW_LICHEN_TYPE + ) return true; + return mt == VanillaMaterialTypes.CLOTH || + mt == VanillaMaterialTypes.LEAVES || + mt == VanillaMaterialTypes.PLANT || + mt == VanillaMaterialTypes.WEB; + } + if (isAxe(it)) { + return mt == VanillaMaterialTypes.WOOD; + } + if (isShovel(it)) { + return mt == VanillaMaterialTypes.DIRT || + mt == VanillaMaterialTypes.CLAY || + mt == VanillaMaterialTypes.SAND || + mt == VanillaMaterialTypes.SNOW || + mt == VanillaMaterialTypes.TOPSNOW; + } + if (isHoe(it)) { + if ( + bt == BlockTypes.DRIED_KELP_BLOCK_TYPE || + bt == BlockTypes.HAY_BLOCK_TYPE || + bt == BlockTypes.TARGET_TYPE || + bt == BlockTypes.SPONGE_TYPE || + bt == BlockTypes.MOSS_BLOCK_TYPE + ) return true; + return mt == VanillaMaterialTypes.LEAVES || + mt == VanillaMaterialTypes.NETHERWART || + mt == VanillaMaterialTypes.SCULK; + } + if (isSword(it)) { + if ( + bt == BlockTypes.BAMBOO_TYPE || + bt == BlockTypes.BAMBOO_SAPLING_TYPE || + bt == BlockTypes.COCOA_TYPE || + bt == BlockTypes.HAY_BLOCK_TYPE || + bt == BlockTypes.VINE_TYPE || + bt == BlockTypes.GLOW_LICHEN_TYPE + ) return true; + return mt == VanillaMaterialTypes.VEGETABLE || + mt == VanillaMaterialTypes.LEAVES || + mt == VanillaMaterialTypes.WEB; + } + return false; + } + + } diff --git a/Allay-Server/src/main/java/org/allaymc/server/network/processor/PlayerAuthInputPacketProcessor.java b/Allay-Server/src/main/java/org/allaymc/server/network/processor/PlayerAuthInputPacketProcessor.java index aaf403c3a..65dc72c78 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/network/processor/PlayerAuthInputPacketProcessor.java +++ b/Allay-Server/src/main/java/org/allaymc/server/network/processor/PlayerAuthInputPacketProcessor.java @@ -57,7 +57,7 @@ protected void handleBlockAction(EntityPlayer player, List { @@ -77,97 +77,111 @@ private static boolean isInvalidGameType(EntityPlayer player) { return player.getGameType() == GameType.CREATIVE || player.getGameType() == GameType.SPECTATOR; } - protected int beBreakingBlockX = Integer.MAX_VALUE; - protected int beBreakingBlockY = Integer.MAX_VALUE; - protected int beBreakingBlockZ = Integer.MAX_VALUE; - protected int beBreakingFaceId; - protected BlockState beBreakingBlock; - protected long startBreakingTime; - protected double neededBreakingTime; + protected int breakingBlockX = Integer.MAX_VALUE; + protected int breakingBlockY = Integer.MAX_VALUE; + protected int breakingBlockZ = Integer.MAX_VALUE; + protected int breakingFaceId; + protected BlockState breakingBlock; + protected long startBreakingTime; // Ticks + protected double neededBreakingTime; // Seconds + protected double stopBreakingTime; // Ticks protected boolean isBreakingBlock() { - return beBreakingBlock != null; + return breakingBlock != null; } protected void startBreak(EntityPlayer player, int x, int y, int z, int blockFaceId) { - if (beBreakingBlock != null) { + if (breakingBlock != null) { log.warn("Player {} tried to start breaking a block while already breaking one", player.getOriginName()); stopBreak(player); } - if (beBreakingBlockX == x && beBreakingBlockY == y && beBreakingBlockZ == z) { + if (breakingBlockX == x && breakingBlockY == y && breakingBlockZ == z) { log.warn("Player {} tried to start breaking the same block twice", player.getOriginName()); return; } - beBreakingBlockX = x; - beBreakingBlockY = y; - beBreakingBlockZ = z; - beBreakingFaceId = blockFaceId; - beBreakingBlock = player.getDimension().getBlockState(x, y, z); + breakingBlockX = x; + breakingBlockY = y; + breakingBlockZ = z; + breakingFaceId = blockFaceId; + breakingBlock = player.getDimension().getBlockState(x, y, z); startBreakingTime = player.getWorld().getTick(); - neededBreakingTime = beBreakingBlock.getBlockType().getBlockBehavior().calculateBreakTime(beBreakingBlock, player.getContainer(FullContainerType.PLAYER_INVENTORY).getItemInHand(), player); + neededBreakingTime = breakingBlock.getBlockType().getBlockBehavior().calculateBreakTime(breakingBlock, player.getContainer(FullContainerType.PLAYER_INVENTORY).getItemInHand(), player); + stopBreakingTime = startBreakingTime + neededBreakingTime * 20.0d; var pk = new LevelEventPacket(); pk.setType(BLOCK_START_BREAK); pk.setPosition(Vector3f.from(x, y, z)); pk.setData((int) (65535 / (neededBreakingTime * 20))); player.getCurrentChunk().addChunkPacket(pk); - broadcastBreakingPracticeAndTime(player); + sendBreakingPracticeAndTime(player); } protected void stopBreak(EntityPlayer player) { var pk = new LevelEventPacket(); pk.setType(BLOCK_STOP_BREAK); - pk.setPosition(Vector3f.from(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ)); + pk.setPosition(Vector3f.from(breakingBlockX, breakingBlockY, breakingBlockZ)); pk.setData(0); player.getCurrentChunk().addChunkPacket(pk); - beBreakingBlockX = Integer.MAX_VALUE; - beBreakingBlockY = Integer.MAX_VALUE; - beBreakingBlockZ = Integer.MAX_VALUE; - beBreakingFaceId = 0; - beBreakingBlock = null; + breakingBlockX = Integer.MAX_VALUE; + breakingBlockY = Integer.MAX_VALUE; + breakingBlockZ = Integer.MAX_VALUE; + breakingFaceId = 0; + breakingBlock = null; startBreakingTime = 0; neededBreakingTime = 0; + stopBreakingTime = 0; } - protected static final int BLOCK_BREAKING_TIME_FAULT_TOLERANCE = 5; + protected static final int BLOCK_BREAKING_TIME_FAULT_TOLERANCE = 3; protected void completeBreak(EntityPlayer player, int x, int y, int z) { - if (beBreakingBlockX != x || beBreakingBlockY != y || beBreakingBlockZ != z) { + if (breakingBlockX != x || breakingBlockY != y || breakingBlockZ != z) { log.warn("Player {} tried to complete breaking a different block", player.getOriginName()); return; } var currentTime = player.getWorld().getTick(); - var actualBlockBreakingTime = currentTime - startBreakingTime; - if (Math.abs(actualBlockBreakingTime - neededBreakingTime * 20L) <= BLOCK_BREAKING_TIME_FAULT_TOLERANCE) { + if (Math.abs(currentTime - stopBreakingTime) <= BLOCK_BREAKING_TIME_FAULT_TOLERANCE) { var world = player.getDimension(); - var oldState = world.getBlockState(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ); + var oldState = world.getBlockState(breakingBlockX, breakingBlockY, breakingBlockZ); var pk = new LevelEventPacket(); pk.setType(LevelEvent.PARTICLE_DESTROY_BLOCK); - pk.setPosition(Vector3f.from(beBreakingBlockX + 0.5f, beBreakingBlockY + 0.5f, beBreakingBlockZ + 0.5f)); + pk.setPosition(Vector3f.from(breakingBlockX + 0.5f, breakingBlockY + 0.5f, breakingBlockZ + 0.5f)); pk.setData(oldState.blockStateHash()); player.getCurrentChunk().addChunkPacket(pk); - world.setBlockState(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ, AIR_TYPE.getDefaultState()); + world.setBlockState(breakingBlockX, breakingBlockY, breakingBlockZ, AIR_TYPE.getDefaultState()); } else { - log.warn("Mismatch block breaking time! Expected: {}gt, actual: {}gt", neededBreakingTime * 20, actualBlockBreakingTime); + log.warn("Mismatch block breaking complete time! Expected: {}gt, actual: {}gt", stopBreakingTime, currentTime); } stopBreak(player); } - protected void broadcastBreakingPracticeAndTime(EntityPlayer player) { + protected void sendBreakingPracticeAndTime(EntityPlayer player) { + updateBreakingTime(player); + var pk1 = new LevelEventPacket(); pk1.setType(PARTICLE_CRACK_BLOCK); - var blockFaceOffset = Objects.requireNonNull(BlockFace.fromId(beBreakingFaceId)).getOffset(); - pk1.setPosition(Vector3f.from(beBreakingBlockX + 0.5f + blockFaceOffset.x(), beBreakingBlockY + 0.5f + blockFaceOffset.y(), beBreakingBlockZ + 0.5f + blockFaceOffset.z())); - pk1.setData(beBreakingBlock.blockStateHash()); + var blockFaceOffset = Objects.requireNonNull(BlockFace.fromId(breakingFaceId)).getOffset(); + pk1.setPosition(Vector3f.from(breakingBlockX + 0.5f + blockFaceOffset.x(), breakingBlockY + 0.5f + blockFaceOffset.y(), breakingBlockZ + 0.5f + blockFaceOffset.z())); + pk1.setData(breakingBlock.blockStateHash()); var pk2 = new LevelEventPacket(); pk2.setType(BLOCK_UPDATE_BREAK); - pk2.setPosition(Vector3f.from(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ)); + pk2.setPosition(Vector3f.from(breakingBlockX, breakingBlockY, breakingBlockZ)); pk2.setData((int) (65535 / (neededBreakingTime * 20))); player.getCurrentChunk().addChunkPacket(pk1); player.getCurrentChunk().addChunkPacket(pk2); } + protected void updateBreakingTime(EntityPlayer player) { + var newBreakingTime = breakingBlock.getBehavior().calculateBreakTime(breakingBlock, player.getContainer(FullContainerType.PLAYER_INVENTORY).getItemInHand(), player); + if (neededBreakingTime == newBreakingTime) return; + // 破坏时间有变化,进行修正 + var currentTime = player.getWorld().getTick(); + var timeLeft = stopBreakingTime - currentTime; + stopBreakingTime = currentTime + (timeLeft / neededBreakingTime) * newBreakingTime; + neededBreakingTime = newBreakingTime; + } + protected void handleInputData(EntityPlayer player, Set inputData) { for (var input : inputData) { switch (input) { @@ -188,7 +202,7 @@ protected void handleInputData(EntityPlayer player, Set inp @Override public void handleSync(EntityPlayer player, PlayerAuthInputPacket packet) { handleBlockAction(player, packet.getPlayerActions()); - if (isBreakingBlock()) broadcastBreakingPracticeAndTime(player); + if (isBreakingBlock()) sendBreakingPracticeAndTime(player); handleInputData(player, packet.getInputData()); } @@ -197,7 +211,6 @@ public PacketSignal handleAsync(EntityPlayer player, PlayerAuthInputPacket packe if (notReadyForInput(player)) return PacketSignal.HANDLED; // The pos which client sends to the server is higher than the actual coordinates (one base offset) handleMovement(player, packet.getPosition().sub(0, player.getBaseOffset(), 0), packet.getRotation()); - if (isBreakingBlock()) broadcastBreakingPracticeAndTime(player); return PacketSignal.UNHANDLED; }