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),
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),

View file

@ -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());

View file

@ -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.
*/

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.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());

View file

@ -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<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) {
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<Block> 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);
}
/**