Skip to content

Commit

Permalink
feat: server auth block breaking (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcmd committed Jun 10, 2024
1 parent 91bc2da commit de5741f
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import org.allaymc.api.block.property.type.BlockPropertyType;
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.container.FullContainerType;
import org.allaymc.api.container.impl.PlayerArmorContainer;
import org.allaymc.api.data.VanillaItemTags;
import org.allaymc.api.entity.Entity;
import org.allaymc.api.entity.component.common.EntityContainerHolderComponent;
Expand All @@ -17,7 +15,6 @@
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.world.Dimension;

/**
Expand Down Expand Up @@ -52,6 +49,7 @@ default double calculateBreakTime(BlockState blockState, ItemStack usedItem, Ent
double blockHardness = blockState.getBlockAttributes().hardness();
if (blockHardness == 0) return 0;
boolean isCorrectTool = isCorrectTool(blockState, usedItem);
boolean canHarvestWithHand = canHarvestWithHand();
boolean hasAquaAffinity = false;
boolean isInWater = false;
boolean isOnGround = true;
Expand Down Expand Up @@ -79,17 +77,13 @@ default double calculateBreakTime(BlockState blockState, ItemStack usedItem, Ent
}

// Calculate break time
double speed = 1.0d / blockHardness;
double baseTime = ((isCorrectTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness;
double speed = 1.0d / baseTime;
if (isCorrectTool) {
// 若是正确的工具,初始速度除以1.5
speed /= 1.5d;
// 工具等级(木制,石制,铁制,etc...)加成
speed *= toolBreakTimeBonus(usedItem, blockState);
// 工具效率附魔加成
speed += speedBonusByEfficiency(efficiencyLevel);
} else {
// 若不是正确的工具,初始速度除以5
speed /= 5.0d;
}
// 实体急迫药水效果加成
speed *= 1.0d + (0.2d * hasteEffectLevel);
Expand Down Expand Up @@ -124,6 +118,15 @@ default boolean isCorrectTool(BlockState blockState, ItemStack usedItem) {
return false;
}

/**
* 部分方块(例如石头,黑曜石)不可以被徒手挖去,强行挖取它们不会掉落任何物品,
* 且挖掘速度会受到惩罚(baseTime增大5倍,正常是1.5倍)
* @return 是否可以徒手挖取
*/
default boolean canHarvestWithHand() {
return true;
}

private void checkBlockType(BlockState blockState) {
if (blockState.getBlockType() != getBlockType()) throw new IllegalArgumentException("Block type is not match! Expected: " + getBlockType().getIdentifier() + ", actual: " + blockState.getBlockType().getIdentifier());
}
Expand Down
6 changes: 3 additions & 3 deletions Allay-API/src/main/java/org/allaymc/api/world/Dimension.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,11 @@ default BlockState[][][] getCollidingBlocks(AABBfc aabb, int layer, boolean igno
return notEmpty ? blockStates : null;
}

default void addLevelEvent(Vector3i pos, LevelEventType levelEventType, int data) {
var chunk = getChunkService().getChunk(pos.getX() >> 4, pos.getZ() >> 4);
default void addLevelEvent(float x, float y, float z, LevelEventType levelEventType, int data) {
var chunk = getChunkService().getChunk((int) x >> 4, (int) z >> 4);
if (chunk == null) return;
var levelEventPacket = new LevelEventPacket();
levelEventPacket.setPosition(pos.toFloat());
levelEventPacket.setPosition(Vector3f.from(x, y, z));
levelEventPacket.setType(levelEventType);
levelEventPacket.setData(data);
chunk.sendChunkPacket(levelEventPacket);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.allaymc.api.blockentity.component.common.BlockEntityBaseComponent;
import org.allaymc.api.blockentity.component.common.BlockEntityContainerHolderComponent;
import org.allaymc.api.blockentity.component.event.BlockEntityLoadNBTEvent;
import org.allaymc.api.container.FullContainerType;
import org.allaymc.api.utils.Identifier;
import org.allaymc.api.component.annotation.ComponentIdentifier;
import org.allaymc.api.component.annotation.Dependency;
Expand Down Expand Up @@ -92,6 +93,11 @@ private void onReplace(BlockOnReplaceEvent event) {
}
}

@Override
public boolean hasContainer(FullContainerType<?> type) {
return container.getContainerType() == type;
}

@Override
public <T extends Container> T getContainerBySlotType(ContainerSlotType slotType) {
// BlockEntityContainerHolder can only hold one container in its lifetime
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package org.allaymc.server.network.processor;

import lombok.extern.slf4j.Slf4j;
import org.allaymc.api.entity.interfaces.EntityPlayer;
import org.allaymc.api.math.location.Location3f;
import org.allaymc.api.network.processor.PacketProcessor;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.GameType;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketType;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
import org.cloudburstmc.protocol.common.PacketSignal;

import static org.allaymc.api.block.type.BlockTypes.AIR_TYPE;

@Slf4j
public class PlayerActionPacketProcessor extends PacketProcessor<PlayerActionPacket> {
@Override
public PacketSignal handleAsync(EntityPlayer player, PlayerActionPacket packet) {
Expand All @@ -30,6 +38,22 @@ public PacketSignal handleAsync(EntityPlayer player, PlayerActionPacket packet)
player.sendDimensionChangeSuccess();
yield PacketSignal.HANDLED;
}
case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> {
if (player.getGameType() != GameType.CREATIVE) {
log.warn("Player {} tried to creative destroy block in non-creative mode", player.getOriginName());
yield PacketSignal.HANDLED;
}
var pos = packet.getBlockPosition();
var world = player.getDimension();
var oldState = world.getBlockState(pos.getX(), pos.getY(), pos.getZ());
var pk = new LevelEventPacket();
pk.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
pk.setPosition(Vector3f.from(pos.getX() + 0.5f, pos.getY() + 0.5f, pos.getZ() + 0.5f));
pk.setData(oldState.blockStateHash());
player.getCurrentChunk().addChunkPacket(pk);
world.setBlockState(pos.getX(), pos.getY(), pos.getZ(), AIR_TYPE.getDefaultState());
yield PacketSignal.HANDLED;
}
default -> PacketSignal.UNHANDLED;
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
package org.allaymc.server.network.processor;

import lombok.extern.slf4j.Slf4j;
import org.allaymc.api.block.data.BlockFace;
import org.allaymc.api.block.type.BlockState;
import org.allaymc.api.container.FullContainerType;
import org.allaymc.api.entity.interfaces.EntityPlayer;
import org.allaymc.api.math.location.Location3f;
import org.allaymc.api.network.processor.PacketProcessor;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.GameType;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketType;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.cloudburstmc.protocol.common.PacketSignal;

import java.util.List;
import java.util.Objects;
import java.util.Set;

import static org.allaymc.api.block.type.BlockTypes.AIR_TYPE;
import static org.cloudburstmc.protocol.bedrock.data.LevelEvent.*;

/**
* Allay Project 11/22/2023
*
* @author Cool_Loong
*/
@Slf4j
public class PlayerAuthInputPacketProcessor extends PacketProcessor<PlayerAuthInputPacket> {

protected static void handleMovement(EntityPlayer player, Vector3f newPos, Vector3f newRot) {
protected void handleMovement(EntityPlayer player, Vector3f newPos, Vector3f newRot) {
var world = player.getLocation().dimension();
world.getEntityPhysicsService().offerScheduledMove(
player,
Expand All @@ -35,24 +44,131 @@ protected static void handleMovement(EntityPlayer player, Vector3f newPos, Vecto
);
}

protected static void handleBlockAction(EntityPlayer player, List<PlayerBlockActionData> blockActions) {
protected void handleBlockAction(EntityPlayer player, List<PlayerBlockActionData> blockActions) {
if (blockActions.isEmpty()) return;
var world = player.getLocation().dimension();
for (var action : blockActions) {
var pos = action.getBlockPosition();
switch (action.getAction()) {
case START_BREAK -> world.addLevelEvent(pos, LevelEvent.BLOCK_START_BREAK, 0);
case START_BREAK -> {
if (isInvalidGameType(player)) continue;
startBreak(player, pos.getX(), pos.getY(), pos.getZ(), action.getFace());
}
case BLOCK_CONTINUE_DESTROY -> {
// 当玩家破坏一个方块一半时转而破坏另一个方块
if (isInvalidGameType(player)) continue;
// HACK: 客户端不知道为什么会在BLOCK_PREDICT_DESTROY前发个无意义的BLOCK_CONTINUE_DESTROY,应该是bug,这里忽略掉
if (beBreakingBlockX == pos.getX() && beBreakingBlockY == pos.getY() && beBreakingBlockZ == pos.getZ()) continue;
startBreak(player, pos.getX(), pos.getY(), pos.getZ(), action.getFace());
}
case BLOCK_PREDICT_DESTROY -> {
var oldState = world.getBlockState(pos.getX(), pos.getY(), pos.getZ());
world.setBlockState(pos.getX(), pos.getY(), pos.getZ(), AIR_TYPE.getDefaultState());
world.addLevelEvent(pos, LevelEvent.BLOCK_STOP_BREAK, 0);
world.addLevelEvent(pos, LevelEvent.PARTICLE_DESTROY_BLOCK, oldState.blockStateHash());
if (isInvalidGameType(player)) continue;
completeBreak(player, pos.getX(), pos.getY(), pos.getZ());
}
case ABORT_BREAK -> {
// 挖掘中断
if (isInvalidGameType(player)) continue;
stopBreak(player);
}
}
}
}

protected static void handleInputData(EntityPlayer player, Set<PlayerAuthInputData> inputData) {
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 boolean isBreakingBlock() {
return beBreakingBlock != null;
}

protected void startBreak(EntityPlayer player, int x, int y, int z, int blockFaceId) {
if (beBreakingBlock != 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) {
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);
startBreakingTime = player.getWorld().getTick();
neededBreakingTime = beBreakingBlock.getBlockType().getBlockBehavior().calculateBreakTime(beBreakingBlock, player.getContainer(FullContainerType.PLAYER_INVENTORY).getItemInHand(), player);
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);
}

protected void stopBreak(EntityPlayer player) {
var pk = new LevelEventPacket();
pk.setType(BLOCK_STOP_BREAK);
pk.setPosition(Vector3f.from(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ));
pk.setData(0);
player.getCurrentChunk().addChunkPacket(pk);
beBreakingBlockX = Integer.MAX_VALUE;
beBreakingBlockY = Integer.MAX_VALUE;
beBreakingBlockZ = Integer.MAX_VALUE;
beBreakingFaceId = 0;
beBreakingBlock = null;
startBreakingTime = 0;
neededBreakingTime = 0;
}

protected static final int BLOCK_BREAKING_TIME_FAULT_TOLERANCE = 5;

protected void completeBreak(EntityPlayer player, int x, int y, int z) {
if (beBreakingBlockX != x || beBreakingBlockY != y || beBreakingBlockZ != 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) {
var world = player.getDimension();
var oldState = world.getBlockState(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ);
var pk = new LevelEventPacket();
pk.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
pk.setPosition(Vector3f.from(beBreakingBlockX + 0.5f, beBreakingBlockY + 0.5f, beBreakingBlockZ + 0.5f));
pk.setData(oldState.blockStateHash());
player.getCurrentChunk().addChunkPacket(pk);
world.setBlockState(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ, AIR_TYPE.getDefaultState());
} else {
log.warn("Mismatch block breaking time! Expected: {}gt, actual: {}gt", neededBreakingTime * 20, actualBlockBreakingTime);
}
stopBreak(player);
}

protected void broadcastBreakingPracticeAndTime(EntityPlayer 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 pk2 = new LevelEventPacket();
pk2.setType(BLOCK_UPDATE_BREAK);
pk2.setPosition(Vector3f.from(beBreakingBlockX, beBreakingBlockY, beBreakingBlockZ));
pk2.setData((int) (65535 / (neededBreakingTime * 20)));

player.getCurrentChunk().addChunkPacket(pk1);
player.getCurrentChunk().addChunkPacket(pk2);
}

protected void handleInputData(EntityPlayer player, Set<PlayerAuthInputData> inputData) {
for (var input : inputData) {
switch (input) {
case START_SPRINTING -> player.setSprinting(true);
Expand All @@ -72,6 +188,7 @@ protected static void handleInputData(EntityPlayer player, Set<PlayerAuthInputDa
@Override
public void handleSync(EntityPlayer player, PlayerAuthInputPacket packet) {
handleBlockAction(player, packet.getPlayerActions());
if (isBreakingBlock()) broadcastBreakingPracticeAndTime(player);
handleInputData(player, packet.getInputData());
}

Expand All @@ -80,6 +197,7 @@ 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;
}

Expand Down
Loading

0 comments on commit de5741f

Please sign in to comment.