start implementing new block breaking

This commit is contained in:
onebeastchris 2024-12-03 17:25:48 +08:00
parent 77ffb6098e
commit 289a74975d
5 changed files with 150 additions and 118 deletions

View file

@ -51,6 +51,9 @@ public enum GeyserAttributeType {
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f),
BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f), 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 // Bedrock Attributes
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f), ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),

View file

@ -286,6 +286,15 @@ public class SessionPlayerEntity extends PlayerEntity {
return attributeData; 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) { public void setLastDeathPosition(@Nullable GlobalPos pos) {
if (pos != null) { if (pos != null) {
dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition()); dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition());

View file

@ -523,6 +523,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter @Setter
private long blockBreakStartTime; private long blockBreakStartTime;
/**
* // TODO
*/
private long destroyProgress;
/** /**
* Stores whether the player intended to place a bucket. * Stores whether the player intended to place a bucket.
*/ */

View file

@ -32,6 +32,7 @@ import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData; import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
@ -88,7 +89,7 @@ final class BedrockBlockActions {
LevelEventPacket startBreak = new LevelEventPacket(); LevelEventPacket startBreak = new LevelEventPacket();
startBreak.setType(LevelEvent.BLOCK_START_BREAK); startBreak.setType(LevelEvent.BLOCK_START_BREAK);
startBreak.setPosition(vector.toFloat()); 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 // If the block is custom or the breaking item is custom, we must keep track of break time ourselves
GeyserItemStack item = session.getPlayerInventory().getItemInHand(); GeyserItemStack item = session.getPlayerInventory().getItemInHand();
@ -169,6 +170,7 @@ final class BedrockBlockActions {
if (session.getGameMode() != GameMode.CREATIVE) { if (session.getGameMode() != GameMode.CREATIVE) {
// As of 1.16.210: item frame items are taken out here. // 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 // 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); Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector);
if (itemFrameEntity != null) { if (itemFrameEntity != null) {
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(), ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
@ -180,6 +182,7 @@ final class BedrockBlockActions {
ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, vector, Direction.DOWN, 0); ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, vector, Direction.DOWN, 0);
session.sendDownstreamGamePacket(abortBreakingPacket); session.sendDownstreamGamePacket(abortBreakingPacket);
LevelEventPacket stopBreak = new LevelEventPacket(); LevelEventPacket stopBreak = new LevelEventPacket();
stopBreak.setType(LevelEvent.BLOCK_STOP_BREAK); stopBreak.setType(LevelEvent.BLOCK_STOP_BREAK);
stopBreak.setPosition(vector.toFloat()); stopBreak.setPosition(vector.toFloat());

View file

@ -27,156 +27,168 @@ package org.geysermc.geyser.util;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3i; 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.GeyserItemStack;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.inventory.item.BedrockEnchantment;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block; 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.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; 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.BlockTag;
import org.geysermc.geyser.session.cache.tags.GeyserHolderSet;
import org.geysermc.geyser.translator.collision.BlockCollision; 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.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ToolData;
public final class BlockUtils { public final class BlockUtils {
private static boolean correctTool(GeyserSession session, Block block, String itemToolType) { public static float getBlockDestroyProgress(GeyserSession session, BlockState blockState, GeyserItemStack itemInHand) {
return switch (itemToolType) { float destroySpeed = blockState.block().destroyTime();
case "axe" -> session.getTagCache().is(BlockTag.MINEABLE_AXE, block); if (destroySpeed == -1) {
case "hoe" -> session.getTagCache().is(BlockTag.MINEABLE_HOE, block); return 0;
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); int speedMultiplier = hasCorrectTool(session, blockState.block(), itemInHand) ? 30 : 100;
case "sword" -> block == Blocks.COBWEB; return getPlayerDestroySpeed(session, blockState, itemInHand) / destroySpeed / speedMultiplier;
default -> { }
session.getGeyser().getLogger().warning("Unknown tool type: " + itemToolType);
yield false; 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<Block> 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) { private static float getItemDestroySpeed(GeyserSession session, Block block, GeyserItemStack stack) {
if (toolType.equals("shears")) return isShearsEffective ? 5.0 : 15.0; ToolData tool = stack.getComponent(DataComponentType.TOOL);
if (toolType.isEmpty()) return 1.0; if (tool == null) {
return switch (toolTier) { return 1f;
// https://minecraft.wiki/w/Breaking#Speed }
case "wooden" -> 2.0;
case "stone" -> 4.0; for (ToolData.Rule rule : tool.getRules()) {
case "iron" -> 6.0; if (rule.getSpeed() != null) {
case "diamond" -> 8.0; GeyserHolderSet<Block> set = GeyserHolderSet.convertHolderSet(JavaRegistries.BLOCK, rule.getBlocks());
case "netherite" -> 9.0; if (session.getTagCache().is(set, block)) {
case "golden" -> 12.0; return rule.getSpeed();
default -> 1.0; }
}; }
}
return tool.getDefaultMiningSpeed();
} }
private static boolean canToolTierBreakBlock(GeyserSession session, Block block, String toolTier) { private static float getPlayerDestroySpeed(GeyserSession session, BlockState blockState, GeyserItemStack itemInHand) {
if (toolTier.equals("netherite") || toolTier.equals("diamond")) { float destroySpeed = getItemDestroySpeed(session, blockState.block(), itemInHand);
// As of 1.17, these tiers can mine everything that is mineable EntityEffectCache effectCache = session.getEffectCache();
return true;
if (destroySpeed > 1.0F) {
destroySpeed += session.getPlayerEntity().attributeOrDefault(GeyserAttributeType.MINING_EFFICIENCY);
} }
switch (toolTier) { int miningSpeedMultiplier = getMiningSpeedAmplification(effectCache);
// Use intentional fall-throughs to check each tier with this block if (miningSpeedMultiplier > 0) {
default: destroySpeed *= miningSpeedMultiplier * 0.2F;
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;
}
} }
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 int getMiningSpeedAmplification(EntityEffectCache cache) {
private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, boolean canTierMineBlock, return Math.max(cache.getHaste(), cache.getConduitPower());
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;
if (correctTool) { public int getDestroyStage(GeyserSession session) {
speed *= toolBreakTimeBonus(toolType, toolTier, isShearsEffective); return session.getDestroyProgress() > 0F ? (int) session.getDestroyProgress() * 10 : -1;
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;
} }
// TODO 1.21.4 this changed probably; no more tiers // 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) { 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 isShearsEffective = session.getTagCache().is(BlockTag.LEAVES, block) || session.getTagCache().is(BlockTag.WOOL, block); //TODO called twice
boolean canHarvestWithHand = !block.requiresCorrectToolForDrops(); // boolean canHarvestWithHand = !block.requiresCorrectToolForDrops();
String toolType = ""; // String toolType = "";
String toolTier = ""; // String toolTier = "";
boolean correctTool = false; // boolean correctTool = false;
boolean toolCanBreak = false; // boolean toolCanBreak = false;
if (item.isTool()) { // if (item.isTool()) {
toolType = item.getToolType(); // toolType = item.getToolType();
toolTier = item.getToolTier(); // toolTier = item.getToolTier();
correctTool = correctTool(session, block, toolType); // correctTool = correctTool(session, block, toolType);
toolCanBreak = canToolTierBreakBlock(session, block, toolTier); // toolCanBreak = canToolTierBreakBlock(session, block, toolTier);
} // }
//
int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(session, components, BedrockEnchantment.EFFICIENCY); // int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(session, components, BedrockEnchantment.EFFICIENCY);
int hasteLevel = 0; // int hasteLevel = 0;
int miningFatigueLevel = 0; // int miningFatigueLevel = 0;
//
if (!isSessionPlayer) { // if (!isSessionPlayer) {
// Another entity is currently mining; we have all the information we know // // Another entity is currently mining; we have all the information we know
return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, // return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective,
toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, true); // toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, true);
} // }
//
hasteLevel = Math.max(session.getEffectCache().getHaste(), session.getEffectCache().getConduitPower()); // hasteLevel = Math.max(session.getEffectCache().getHaste(), session.getEffectCache().getConduitPower());
miningFatigueLevel = session.getEffectCache().getMiningFatigue(); // miningFatigueLevel = session.getEffectCache().getMiningFatigue();
//
boolean waterInEyes = session.getCollisionManager().isWaterInEyes(); // boolean waterInEyes = session.getCollisionManager().isWaterInEyes();
boolean insideOfWaterWithoutAquaAffinity = waterInEyes && // boolean insideOfWaterWithoutAquaAffinity = waterInEyes &&
ItemUtils.getEnchantmentLevel(session, session.getPlayerInventory().getItem(5).getAllComponents(), BedrockEnchantment.AQUA_AFFINITY) < 1; // ItemUtils.getEnchantmentLevel(session, session.getPlayerInventory().getItem(5).getAllComponents(), BedrockEnchantment.AQUA_AFFINITY) < 1;
//
return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, // return calculateBreakTime(block.destroyTime(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective,
toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, session.getPlayerEntity().isOnGround()); // toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, session.getPlayerEntity().isOnGround());
} }
public static double getSessionBreakTime(GeyserSession session, Block block) { public static double getSessionBreakTime(GeyserSession session, Block block) {
PlayerInventory inventory = session.getPlayerInventory(); // PlayerInventory inventory = session.getPlayerInventory();
GeyserItemStack item = inventory.getItemInHand(); // GeyserItemStack item = inventory.getItemInHand();
ItemMapping mapping = ItemMapping.AIR; // ItemMapping mapping = ItemMapping.AIR;
DataComponents components = null; // DataComponents components = null;
if (item != null) { // if (item != null) {
mapping = item.getMapping(session); // mapping = item.getMapping(session);
components = item.getAllComponents(); // components = item.getAllComponents();
} // }
return getBreakTime(session, block, mapping, components, true); // return getBreakTime(session, block, mapping, components, true);
} }
/** /**