From 289a74975d607312e5860f028a71b3f6022d81b8 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Tue, 3 Dec 2024 17:25:48 +0800 Subject: [PATCH] start implementing new block breaking --- .../entity/attribute/GeyserAttributeType.java | 3 + .../type/player/SessionPlayerEntity.java | 9 + .../geyser/session/GeyserSession.java | 5 + .../player/input/BedrockBlockActions.java | 5 +- .../org/geysermc/geyser/util/BlockUtils.java | 246 +++++++++--------- 5 files changed, 150 insertions(+), 118 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java index 1e050c840..833f2f46d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java @@ -51,6 +51,9 @@ public enum GeyserAttributeType { MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f), + MINING_EFFICIENCY("minecraft:mining_efficiency", null, 0f, 1024f, 0f), + BLOCK_BREAK_SPEED("minecraft:block_break_speed", null, 0f, 1024f, 1f), + SUBMERGED_MINING_SPEED("minecraft:submerged_mining_speed", null, 0f, 20f, 0.2f), // Bedrock Attributes ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f), diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 9d5bc011c..7543e05bc 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -286,6 +286,15 @@ public class SessionPlayerEntity extends PlayerEntity { return attributeData; } + public float attributeOrDefault(GeyserAttributeType type) { + var attribute = this.attributes.get(type); + if (attribute == null) { + return type.getDefaultValue(); + } + + return attribute.getValue(); + } + public void setLastDeathPosition(@Nullable GlobalPos pos) { if (pos != null) { dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition()); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index ef6261ead..9b49b3cfc 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -523,6 +523,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private long blockBreakStartTime; + /** + * // TODO + */ + private long destroyProgress; + /** * Stores whether the player intended to place a bucket. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java index 061a04b77..ea386ebcf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java @@ -32,6 +32,7 @@ import org.cloudburstmc.protocol.bedrock.data.PlayerActionType; import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -88,7 +89,7 @@ final class BedrockBlockActions { LevelEventPacket startBreak = new LevelEventPacket(); startBreak.setType(LevelEvent.BLOCK_START_BREAK); startBreak.setPosition(vector.toFloat()); - double breakTime = BlockUtils.getSessionBreakTime(session, BlockState.of(blockState).block()) * 20; + double breakTime = BlockUtils.getSessionBreakTime(session, BlockState.of(blockState).block()) * 20; // TODO afdaöwelfunöwoaenf // If the block is custom or the breaking item is custom, we must keep track of break time ourselves GeyserItemStack item = session.getPlayerInventory().getItemInHand(); @@ -169,6 +170,7 @@ final class BedrockBlockActions { if (session.getGameMode() != GameMode.CREATIVE) { // As of 1.16.210: item frame items are taken out here. // Survival also sends START_BREAK, but by attaching our process here adventure mode also works + GeyserImpl.getInstance().getLogger().warning("abort break, not creative - item frame???"); Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector); if (itemFrameEntity != null) { ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(), @@ -180,6 +182,7 @@ final class BedrockBlockActions { ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, vector, Direction.DOWN, 0); session.sendDownstreamGamePacket(abortBreakingPacket); + LevelEventPacket stopBreak = new LevelEventPacket(); stopBreak.setType(LevelEvent.BLOCK_STOP_BREAK); stopBreak.setPosition(vector.toFloat()); diff --git a/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java b/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java index 1d84c169e..52a4a6df9 100644 --- a/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/BlockUtils.java @@ -27,156 +27,168 @@ package org.geysermc.geyser.util; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3i; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.type.Block; +import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.EntityEffectCache; +import org.geysermc.geyser.session.cache.registry.JavaRegistries; import org.geysermc.geyser.session.cache.tags.BlockTag; +import org.geysermc.geyser.session.cache.tags.GeyserHolderSet; import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ToolData; public final class BlockUtils { - private static boolean correctTool(GeyserSession session, Block block, String itemToolType) { - return switch (itemToolType) { - case "axe" -> session.getTagCache().is(BlockTag.MINEABLE_AXE, block); - case "hoe" -> session.getTagCache().is(BlockTag.MINEABLE_HOE, block); - case "pickaxe" -> session.getTagCache().is(BlockTag.MINEABLE_PICKAXE, block); - case "shears" -> session.getTagCache().is(BlockTag.LEAVES, block) || session.getTagCache().is(BlockTag.WOOL, block); - case "shovel" -> session.getTagCache().is(BlockTag.MINEABLE_SHOVEL, block); - case "sword" -> block == Blocks.COBWEB; - default -> { - session.getGeyser().getLogger().warning("Unknown tool type: " + itemToolType); - yield false; + public static float getBlockDestroyProgress(GeyserSession session, BlockState blockState, GeyserItemStack itemInHand) { + float destroySpeed = blockState.block().destroyTime(); + if (destroySpeed == -1) { + return 0; + } + + int speedMultiplier = hasCorrectTool(session, blockState.block(), itemInHand) ? 30 : 100; + return getPlayerDestroySpeed(session, blockState, itemInHand) / destroySpeed / speedMultiplier; + } + + private static boolean hasCorrectTool(GeyserSession session, Block block, GeyserItemStack stack) { + return !block.requiresCorrectToolForDrops() || isCorrectItemForDrops(session, block, stack); + } + + private static boolean isCorrectItemForDrops(GeyserSession session, Block block, GeyserItemStack stack) { + ToolData tool = stack.getComponent(DataComponentType.TOOL); + if (tool == null) { + return false; + } + + for (ToolData.Rule rule : tool.getRules()) { + if (rule.getCorrectForDrops() != null) { + GeyserHolderSet set = GeyserHolderSet.convertHolderSet(JavaRegistries.BLOCK, rule.getBlocks()); + if (session.getTagCache().is(set, block)) { + return rule.getCorrectForDrops(); + } } - }; + } + + return false; } - private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isShearsEffective) { - if (toolType.equals("shears")) return isShearsEffective ? 5.0 : 15.0; - if (toolType.isEmpty()) return 1.0; - return switch (toolTier) { - // https://minecraft.wiki/w/Breaking#Speed - case "wooden" -> 2.0; - case "stone" -> 4.0; - case "iron" -> 6.0; - case "diamond" -> 8.0; - case "netherite" -> 9.0; - case "golden" -> 12.0; - default -> 1.0; - }; + private static float getItemDestroySpeed(GeyserSession session, Block block, GeyserItemStack stack) { + ToolData tool = stack.getComponent(DataComponentType.TOOL); + if (tool == null) { + return 1f; + } + + for (ToolData.Rule rule : tool.getRules()) { + if (rule.getSpeed() != null) { + GeyserHolderSet set = GeyserHolderSet.convertHolderSet(JavaRegistries.BLOCK, rule.getBlocks()); + if (session.getTagCache().is(set, block)) { + return rule.getSpeed(); + } + } + } + + return tool.getDefaultMiningSpeed(); } - private static boolean canToolTierBreakBlock(GeyserSession session, Block block, String toolTier) { - if (toolTier.equals("netherite") || toolTier.equals("diamond")) { - // As of 1.17, these tiers can mine everything that is mineable - return true; + private static float getPlayerDestroySpeed(GeyserSession session, BlockState blockState, GeyserItemStack itemInHand) { + float destroySpeed = getItemDestroySpeed(session, blockState.block(), itemInHand); + EntityEffectCache effectCache = session.getEffectCache(); + + if (destroySpeed > 1.0F) { + destroySpeed += session.getPlayerEntity().attributeOrDefault(GeyserAttributeType.MINING_EFFICIENCY); } - switch (toolTier) { - // Use intentional fall-throughs to check each tier with this block - default: - if (session.getTagCache().is(BlockTag.NEEDS_STONE_TOOL, block)) { - return false; - } - case "stone": - if (session.getTagCache().is(BlockTag.NEEDS_IRON_TOOL, block)) { - return false; - } - case "iron": - if (session.getTagCache().is(BlockTag.NEEDS_DIAMOND_TOOL, block)) { - return false; - } + int miningSpeedMultiplier = getMiningSpeedAmplification(effectCache); + if (miningSpeedMultiplier > 0) { + destroySpeed *= miningSpeedMultiplier * 0.2F; } - return true; + if (effectCache.getMiningFatigue() != 0) { + float slowdown = switch (effectCache.getMiningFatigue()) { + case 1 -> 0.3F; + case 2 -> 0.09F; + case 3 -> 0.0027F; + default -> 8.1E-4F; + }; + destroySpeed *= slowdown; + } + + destroySpeed *= session.getPlayerEntity().attributeOrDefault(GeyserAttributeType.BLOCK_BREAK_SPEED); + if (session.getCollisionManager().isWaterInEyes()) { + destroySpeed *= session.getPlayerEntity().attributeOrDefault(GeyserAttributeType.SUBMERGED_MINING_SPEED); + } + + if (!session.getPlayerEntity().isOnGround()) { + destroySpeed /= 5F; + } + + return destroySpeed; } - // https://minecraft.wiki/w/Breaking - private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, boolean canTierMineBlock, - String toolType, boolean isShearsEffective, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, - boolean insideOfWaterWithoutAquaAffinity, boolean onGround) { - double baseTime = (((correctTool && canTierMineBlock) || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; - double speed = 1.0 / baseTime; + private static int getMiningSpeedAmplification(EntityEffectCache cache) { + return Math.max(cache.getHaste(), cache.getConduitPower()); + } - if (correctTool) { - speed *= toolBreakTimeBonus(toolType, toolTier, isShearsEffective); - speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1; - } - speed *= 1.0 + (0.2 * hasteLevel); - - switch (miningFatigueLevel) { - case 0: - break; - case 1: - speed -= (speed * 0.7); - break; - case 2: - speed -= (speed * 0.91); - break; - case 3: - speed -= (speed * 0.9973); - break; - default: - speed -= (speed * 0.99919); - break; - } - - if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; - if (!onGround) speed *= 0.2; - return 1.0 / speed; + public int getDestroyStage(GeyserSession session) { + return session.getDestroyProgress() > 0F ? (int) session.getDestroyProgress() * 10 : -1; } // TODO 1.21.4 this changed probably; no more tiers public static double getBreakTime(GeyserSession session, Block block, ItemMapping item, @Nullable DataComponents components, boolean isSessionPlayer) { - boolean isShearsEffective = session.getTagCache().is(BlockTag.LEAVES, block) || session.getTagCache().is(BlockTag.WOOL, block); //TODO called twice - boolean canHarvestWithHand = !block.requiresCorrectToolForDrops(); - String toolType = ""; - String toolTier = ""; - boolean correctTool = false; - boolean toolCanBreak = false; - if (item.isTool()) { - toolType = item.getToolType(); - toolTier = item.getToolTier(); - correctTool = correctTool(session, block, toolType); - toolCanBreak = canToolTierBreakBlock(session, block, toolTier); - } - - int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(session, components, BedrockEnchantment.EFFICIENCY); - int hasteLevel = 0; - int miningFatigueLevel = 0; - - if (!isSessionPlayer) { - // Another entity is currently mining; we have all the information we know - return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, - toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, true); - } - - hasteLevel = Math.max(session.getEffectCache().getHaste(), session.getEffectCache().getConduitPower()); - miningFatigueLevel = session.getEffectCache().getMiningFatigue(); - - boolean waterInEyes = session.getCollisionManager().isWaterInEyes(); - boolean insideOfWaterWithoutAquaAffinity = waterInEyes && - ItemUtils.getEnchantmentLevel(session, session.getPlayerInventory().getItem(5).getAllComponents(), BedrockEnchantment.AQUA_AFFINITY) < 1; - - return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, - toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, session.getPlayerEntity().isOnGround()); +// boolean isShearsEffective = session.getTagCache().is(BlockTag.LEAVES, block) || session.getTagCache().is(BlockTag.WOOL, block); //TODO called twice +// boolean canHarvestWithHand = !block.requiresCorrectToolForDrops(); +// String toolType = ""; +// String toolTier = ""; +// boolean correctTool = false; +// boolean toolCanBreak = false; +// if (item.isTool()) { +// toolType = item.getToolType(); +// toolTier = item.getToolTier(); +// correctTool = correctTool(session, block, toolType); +// toolCanBreak = canToolTierBreakBlock(session, block, toolTier); +// } +// +// int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(session, components, BedrockEnchantment.EFFICIENCY); +// int hasteLevel = 0; +// int miningFatigueLevel = 0; +// +// if (!isSessionPlayer) { +// // Another entity is currently mining; we have all the information we know +// return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, +// toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, true); +// } +// +// hasteLevel = Math.max(session.getEffectCache().getHaste(), session.getEffectCache().getConduitPower()); +// miningFatigueLevel = session.getEffectCache().getMiningFatigue(); +// +// boolean waterInEyes = session.getCollisionManager().isWaterInEyes(); +// boolean insideOfWaterWithoutAquaAffinity = waterInEyes && +// ItemUtils.getEnchantmentLevel(session, session.getPlayerInventory().getItem(5).getAllComponents(), BedrockEnchantment.AQUA_AFFINITY) < 1; +// +// return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, +// toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, session.getPlayerEntity().isOnGround()); } public static double getSessionBreakTime(GeyserSession session, Block block) { - PlayerInventory inventory = session.getPlayerInventory(); - GeyserItemStack item = inventory.getItemInHand(); - ItemMapping mapping = ItemMapping.AIR; - DataComponents components = null; - if (item != null) { - mapping = item.getMapping(session); - components = item.getAllComponents(); - } - return getBreakTime(session, block, mapping, components, true); +// PlayerInventory inventory = session.getPlayerInventory(); +// GeyserItemStack item = inventory.getItemInHand(); +// ItemMapping mapping = ItemMapping.AIR; +// DataComponents components = null; +// if (item != null) { +// mapping = item.getMapping(session); +// components = item.getAllComponents(); +// } +// return getBreakTime(session, block, mapping, components, true); } /**