Skip to content

Commit

Permalink
feat: server-auth block placing
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcmd committed Jun 13, 2024
1 parent 62684f8 commit a495c2d
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
@FunctionalInterface
public interface OnInteract {
/**
* @param player The player who interacted with the block
* @param player The player who interacted with the block, can be null
* @param itemStack The item in the player's hand
* @param dimension The dimension of the block & player
* @param blockPos The pos of the block that the player clicked on
* @param placeBlockPos Assuming the player is holding a block item in their hand, this parameter indicates where the block will be placed (if it can be placed)
* @param clickPos The precise pos where the player clicked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.allaymc.api.entity.interfaces.EntityPlayer;
import org.allaymc.api.entity.metadata.Metadata;
import org.allaymc.api.entity.type.EntityType;
import org.allaymc.api.item.ItemStack;
import org.allaymc.api.math.location.Location3fc;
import org.allaymc.api.utils.MathUtils;
import org.allaymc.api.world.Dimension;
Expand Down Expand Up @@ -357,6 +358,15 @@ default boolean isInWater() {
return false;
}

/**
* @param player The player who interacted with the entity, can be null
* @param itemStack The item used to interact with the entity
* @return true if the interaction is successful
*/
default boolean onInteract(EntityPlayer player, ItemStack itemStack) {
return false;
}

private static boolean isWaterType(BlockType<?> blockType) {
return blockType == BlockTypes.FLOWING_WATER_TYPE || blockType == BlockTypes.WATER_TYPE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.allaymc.api.client.skin.Skin;
import org.allaymc.api.client.storage.PlayerData;
import org.allaymc.api.entity.component.common.EntityBaseComponent;
import org.allaymc.api.entity.interfaces.EntityPlayer;
import org.allaymc.api.form.type.CustomForm;
import org.allaymc.api.form.type.Form;
import org.allaymc.api.math.location.Location3ic;
Expand Down Expand Up @@ -41,6 +40,10 @@ public interface EntityPlayerBaseComponent extends EntityBaseComponent, ChunkLoa

void setCrawling(boolean crawling);

void setInteractingBlock(boolean interactingBlock);

boolean isInteractingBlock();

int getHandSlot();

void setHandSlot(int handSlot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,12 @@ default void dropItemInPlayerPos(ItemStack itemStack) {
entityItem.setPickupDelay(40);
dimension.getEntityService().addEntity(entityItem);
}

default void sendItemInHandUpdate() {
var inv = getContainer(FullContainerType.PLAYER_INVENTORY);
var itemStack = inv.getItemInHand();
if (itemStack.getCount() != 0) inv.onSlotChange(inv.getHandSlot());
else inv.setItemInHand(Container.EMPTY_SLOT_PLACE_HOLDER);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,33 @@ default void clearStackNetworkId() {

void loadExtraTag(NbtMap extraTag);

boolean useItemOn(
/**
* 尝试在一个方块上使用该物品 <br>
* 方法内应该在使用成功时执行可能的减少物品数量,减少耐久等操作。不需要单独向玩家发送物品更新,调用方会发送 <br>
* 注意:放置方块不会调用此方法 <br>
* @return true如果成功使用
*/
default boolean useItemOn(
EntityPlayer player, Dimension dimension,
Vector3ic targetBlockPos, Vector3ic placeBlockPos,
Vector3fc clickPos, BlockFace blockFace);
Vector3fc clickPos, BlockFace blockFace) {
return false;
}

boolean useItemInAir(EntityPlayer player);
/**
* 尝试用此物品执行放方块操作,不管此物品是否是方块物品 <br>
* @return true如果成功放置方块,false如果放置失败(原因:此物品不是方块物品、放置检查失败、事件被撤回)
*/
default boolean placeBlock(
EntityPlayer player, Dimension dimension,
Vector3ic targetBlockPos, Vector3ic placeBlockPos,
Vector3fc clickPos, BlockFace blockFace) {
return false;
}

default boolean useItemInAir(EntityPlayer player) {
return false;
}

default boolean canMerge(ItemStack itemStack) {
return canMerge(itemStack, false);
Expand Down
4 changes: 4 additions & 0 deletions Allay-API/src/main/java/org/allaymc/api/world/Dimension.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ default void setBlockState(int x, int y, int z, BlockState blockState, int layer
}
}

default void sendBlockUpdateTo(BlockState blockState, Vector3ic pos, int layer, EntityPlayer player) {
sendBlockUpdateTo(blockState, pos.x(), pos.y(), pos.z(), layer, player);
}

default void sendBlockUpdateTo(BlockState blockState, int x, int y, int z, int layer, EntityPlayer player) {
player.sendPacket(createBlockUpdatePacket(blockState, x, y, z, layer));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.allaymc.api.block.function.Place;
import org.allaymc.api.block.type.BlockState;
import org.allaymc.api.block.type.BlockType;
import org.allaymc.api.entity.Entity;
import org.allaymc.api.utils.Identifier;
import org.allaymc.api.component.annotation.ComponentIdentifier;
import org.allaymc.api.component.annotation.Manager;
Expand Down Expand Up @@ -62,6 +61,7 @@ public void onScheduledUpdate(BlockStateWithPos blockState) {
@Override
public boolean place(EntityPlayer player, Dimension dimension, BlockState blockState, Vector3ic targetBlockPos, Vector3ic placeBlockPos, Vector3fc clickPos, BlockFace blockFace) {
Place.checkParam(player, dimension, blockState, targetBlockPos, placeBlockPos, clickPos, blockFace);
// TODO: check whether the old block can be replaced
dimension.setBlockState(placeBlockPos.x(), placeBlockPos.y(), placeBlockPos.z(), blockState);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public class EntityPlayerBaseComponentImpl extends EntityBaseComponentImpl<Entit
@Setter
protected Location3ic spawnPoint;
protected boolean awaitingDimensionChangeACK;
protected boolean interactingBlock;
protected AtomicInteger formIdCounter = new AtomicInteger(0);
protected Map<Integer, Form> forms = new Int2ObjectOpenHashMap<>();
protected Map<Integer, CustomForm> serverSettingForms = new Int2ObjectOpenHashMap<>();
Expand Down Expand Up @@ -323,6 +324,16 @@ public void setCrawling(boolean crawling) {
setAndSendEntityFlag(EntityFlag.CRAWLING, crawling);
}

@Override
public void setInteractingBlock(boolean interactingBlock) {
this.interactingBlock = interactingBlock;
}

@Override
public boolean isInteractingBlock() {
return interactingBlock;
}

@Override
public int getHandSlot() {
return containerHolderComponent.getContainer(FullContainerType.PLAYER_INVENTORY).getHandSlot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public ItemCauldronBaseComponentImpl(ItemStackInitInfo<ItemCauldronStack> initIn
}

@Override
public boolean useItemOn(EntityPlayer player, Dimension dimension, Vector3ic targetBlockPos, Vector3ic placeBlockPos, Vector3fc clickPos, BlockFace blockFace) {
public boolean placeBlock(EntityPlayer player, Dimension dimension, Vector3ic targetBlockPos, Vector3ic placeBlockPos, Vector3fc clickPos, BlockFace blockFace) {
return tryPlaceBlockState(player, dimension, targetBlockPos, placeBlockPos, clickPos, blockFace, CAULDRON_TYPE.getDefaultState());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,23 +270,18 @@ public void setCustomNBTContent(NbtMap customNBTContent) {
}

@Override
public boolean useItemOn(
EntityPlayer player,
Dimension dimension, Vector3ic targetBlockPos, Vector3ic placeBlockPos, Vector3fc clickPos,
BlockFace blockFace) {
public boolean placeBlock(EntityPlayer player, Dimension dimension, Vector3ic targetBlockPos, Vector3ic placeBlockPos, Vector3fc clickPos, BlockFace blockFace) {
if (thisItemStack.getItemType().getBlockType() == null)
return false;
var blockState = thisItemStack.toBlockState();
return tryPlaceBlockState(player, dimension, targetBlockPos, placeBlockPos, clickPos, blockFace, blockState);
}

@Override
public boolean useItemInAir(EntityPlayer player) {
return false;
}
// TODO: 由于服务端侧方块放置检查与客户端方块放置检查不能做到100%同步,会导致“吞方块”现象出现,这里先关闭检查
protected static final boolean DO_BLOCK_PLACING_CHECK = false;

protected boolean tryPlaceBlockState(EntityPlayer player, Dimension dimension, Vector3ic targetBlockPos, Vector3ic placeBlockPos, Vector3fc clickPos, BlockFace blockFace, BlockState blockState) {
if (player != null && hasEntityCollision(dimension, placeBlockPos, blockState))
if (player != null && DO_BLOCK_PLACING_CHECK && hasEntityCollision(dimension, placeBlockPos, blockState))
return false;
BlockType<?> blockType = blockState.getBlockType();
boolean result = blockType.getBlockBehavior().place(player, dimension, blockState, targetBlockPos, placeBlockPos, clickPos, blockFace);
Expand All @@ -300,13 +295,13 @@ protected void tryConsumeItem(EntityPlayer player) {
}

protected boolean hasEntityCollision(Dimension dimension, Vector3ic placePos, BlockState blockState) {
var block_aabb = blockState.getBehavior().getBlockAttributes(blockState)
var blockAABB = blockState.getBehavior().getBlockAttributes(blockState)
.computeOffsetVoxelShape(
placePos.x(),
placePos.y(),
placePos.z()
);
return !dimension.getEntityPhysicsService().computeCollidingEntities(block_aabb).isEmpty();
return !dimension.getEntityPhysicsService().computeCollidingEntities(blockAABB).isEmpty();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import com.google.common.base.Preconditions;
import org.allaymc.api.block.data.BlockFace;
import org.allaymc.api.container.Container;
import org.allaymc.api.container.FullContainerType;
import org.allaymc.api.entity.component.common.EntityDamageComponent;
import org.allaymc.api.entity.damage.DamageContainer;
import org.allaymc.api.entity.interfaces.EntityPlayer;
import org.allaymc.api.item.ItemStack;
import org.allaymc.api.network.processor.PacketProcessor;
import org.allaymc.api.utils.MathUtils;
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventorySource;
Expand All @@ -24,8 +22,6 @@
* @author Cool_Loong
*/
public class InventoryTransactionPacketProcessor extends PacketProcessor<InventoryTransactionPacket> {
protected long spamCheckTime;

public static final int ITEM_USE_CLICK_BLOCK = 0;
public static final int ITEM_USE_CLICK_AIR = 1;
public static final int ITEM_USE_BREAK_BLOCK = 2;
Expand All @@ -48,63 +44,41 @@ public void handleSync(EntityPlayer player, InventoryTransactionPacket packet) {
switch (packet.getActionType()) {
case ITEM_USE_CLICK_BLOCK -> {
var placePos = blockFace.offsetPos(blockPos);
if (!canInteract()) {
var blockState = world.getBlockState(placePos.x(), placePos.y(), placePos.z());
world.sendBlockUpdateTo(blockState, placePos.x(), placePos.y(), placePos.z(), 0, player);
return;
}
this.spamCheckTime = System.currentTimeMillis();

if (!interactBlockOrUseItem(player, itemStack, blockPos, placePos, clickPos, blockFace)) {
//Failed to interact, send back origin block state to client
var w = player.getLocation().dimension();
var blockStateClicked = w.getBlockState(blockPos.x(), blockPos.y(), blockPos.z());
w.sendBlockUpdateTo(blockStateClicked, blockPos.x(), blockPos.y(), blockPos.z(), 0, player);
var dimension = player.getDimension();
var interactedBlock = world.getBlockState(blockPos);
if (player.isInteractingBlock()) {
if (!interactedBlock.getBehavior().onInteract(player, itemStack, dimension, blockPos, placePos, clickPos, blockFace)) {
// 玩家与方块的互动不成功,可能是插件撤回了事件,需要覆盖客户端的方块变化
// 覆盖被点击方块变化
var blockStateClicked = dimension.getBlockState(blockPos);
dimension.sendBlockUpdateTo(blockStateClicked, blockPos, 0, player);

var blockStateReplaced = w.getBlockState(placePos.x(), placePos.y(), placePos.z());
w.sendBlockUpdateTo(blockStateReplaced, placePos.x(), placePos.y(), placePos.z(), 0, player);
} else {
//Used! Update item slot to client
if (itemStack.getCount() != 0) {
inv.onSlotChange(inv.getHandSlot());
} else {
inv.setItemInHand(Container.EMPTY_SLOT_PLACE_HOLDER);
// 玩家放置方块
if (itemStack.getItemType() != AIR_TYPE) {
if (!itemStack.placeBlock(player, dimension, blockPos, placePos, clickPos, blockFace)) {
var blockStateReplaced = dimension.getBlockState(placePos);
dimension.sendBlockUpdateTo(blockStateReplaced, placePos, 0, player);
}
}
}
}
} else itemStack.useItemOn(player, dimension, blockPos, placePos, clickPos, blockFace);
}
case ITEM_USE_CLICK_AIR -> {
if (itemStack.useItemInAir(player)) {
if (!player.hasAction()) {
player.setAction(true);
// TODO: check meaning of this return
// return;
}
player.setAction(false);
}
}
case ITEM_USE_BREAK_BLOCK -> {
// TODO
}
}
}
case NORMAL -> {
for (var action : packet.getActions()) {
if (action.getSource().getType().equals(InventorySource.Type.WORLD_INTERACTION)) {
if (action.getSource().getFlag().equals(InventorySource.Flag.DROP_ITEM)) {
//Do not ask me why mojang still use the old item transaction pk even the server-auth inv was enabled
var count = action.getToItem().getCount();
player.tryDropItemInHand(count);
}
}
}
player.sendItemInHandUpdate();
}
case ITEM_USE_ON_ENTITY -> {
var target = player.getDimension().getEntityByRuntimeId(packet.getRuntimeEntityId());
Preconditions.checkNotNull(target, "Player " + player.getOriginName() + " try to attack a entity which doesn't exist! Entity id: " + packet.getRuntimeEntityId());
switch (packet.getActionType()) {
case ITEM_USE_ON_ENTITY_INTERACT -> {
// TODO
}
case ITEM_USE_ON_ENTITY_INTERACT -> target.onInteract(player, player.getContainer(FullContainerType.PLAYER_INVENTORY).getItemInHand());
case ITEM_USE_ON_ENTITY_ATTACK -> {
EntityDamageComponent damageable;
if (target instanceof EntityDamageComponent cast) {
Expand All @@ -121,24 +95,22 @@ public void handleSync(EntityPlayer player, InventoryTransactionPacket packet) {
damageable.attack(damageContainer);
}
}
player.sendItemInHandUpdate();
}
case NORMAL -> {
for (var action : packet.getActions()) {
if (action.getSource().getType().equals(InventorySource.Type.WORLD_INTERACTION)) {
if (action.getSource().getFlag().equals(InventorySource.Flag.DROP_ITEM)) {
// Do not ask me why mojang still use the old item transaction pk even the server-auth inv was enabled
var count = action.getToItem().getCount();
player.tryDropItemInHand(count);
}
}
}
}
}
}

protected boolean canInteract() {
return System.currentTimeMillis() - this.spamCheckTime >= 100;
}

private boolean interactBlockOrUseItem(EntityPlayer player, ItemStack itemStack, Vector3ic blockPos, Vector3ic placePos, Vector3fc clickPos, BlockFace blockFace) {
var dimension = player.getLocation().dimension();
var blockStateClicked = dimension.getBlockState(blockPos.x(), blockPos.y(), blockPos.z());
if (!blockStateClicked.getBehavior().onInteract(player, itemStack, dimension, blockPos, placePos, clickPos, blockFace)) {
if (itemStack.getItemType() != AIR_TYPE) {
return itemStack.useItemOn(player, dimension, blockPos, placePos, clickPos, blockFace);
} else return false;
} else return true;
}

@Override
public BedrockPacketType getPacketType() {
return BedrockPacketType.INVENTORY_TRANSACTION;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ public PacketSignal handleAsync(EntityPlayer player, PlayerActionPacket packet)
world.setBlockState(pos.getX(), pos.getY(), pos.getZ(), AIR_TYPE.getDefaultState());
yield PacketSignal.HANDLED;
}
case START_ITEM_USE_ON -> {
if (player.isInteractingBlock()) {
log.warn("Player {} tried to start item use on without stopping", player.getOriginName());
yield PacketSignal.HANDLED;
}
player.setInteractingBlock(true);
yield PacketSignal.HANDLED;
}
case STOP_ITEM_USE_ON -> {
if (!player.isInteractingBlock()) {
log.warn("Player {} tried to stop item use on without starting", player.getOriginName());
yield PacketSignal.HANDLED;
}
player.setInteractingBlock(false);
yield PacketSignal.HANDLED;
}
default -> PacketSignal.UNHANDLED;
};
}
Expand Down
Loading

0 comments on commit a495c2d

Please sign in to comment.