mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 06:24:59 +01:00
start implementing new block breaking
This commit is contained in:
parent
77ffb6098e
commit
289a74975d
5 changed files with 150 additions and 118 deletions
|
@ -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),
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue