Minecraft Forge 1.19.4取消方块破坏后BlockEntity同步异常求助
问题
在Minecraft Forge 1.19.4中,通过BlockEvent.BreakEvent事件处理方块破坏逻辑,特定条件下取消破坏操作。但该事件仅在服务端触发,导致客户端出现方块先消失再重现的视觉bug,同时客户端的BlockEntity数据与服务端不一致。
尝试过sendBlockUpdate和自定义数据包同步,但因同步时机(方块销毁重建前)问题未生效,需要可行的解决方案。
EventHandlers类代码
@Mod.EventBusSubscriber(modid = DoorLockPadMod.MODID) public class EventHandlers { @SubscribeEvent public static void onBlockDestroyed(@NotNull BlockEvent.BreakEvent event) { Player playerEntity = event.getPlayer(); Level level = playerEntity.getLevel(); BlockPos blockPos = event.getPos(); BlockState blockState = level.getBlockState(blockPos); FunctionUtils.setLevel(level); if (blockState.getBlock() instanceof DoorBlock) { BlockPos padBlockPos = FunctionUtils.checkIfAssignedDoor(blockPos, null); if (padBlockPos != null) { PadBlockEntity padBlockEntity = (PadBlockEntity) level.getBlockEntity(padBlockPos); BlockState padBlockState = level.getBlockState(padBlockPos); if (padBlockEntity != null && (!padBlockEntity.isDoorLocked() || playerEntity.isCreative())) { padBlockEntity.setRelatedDoor(null); padBlockEntity.setPassword(null); level.sendBlockUpdated(padBlockPos, padBlockState, padBlockState, 2); FunctionUtils.removeDoorAssignedPadBlockByPadBlock(padBlockPos); } else event.setCanceled(true); } } else if (blockState.getBlock() instanceof PadBlock) { PadBlockEntity padBlockEntity = (PadBlockEntity) level.getBlockEntity(blockPos); if (padBlockEntity != null && (!padBlockEntity.isDoorLocked() || playerEntity.isCreative())) { FunctionUtils.removeDoorAssignedPadBlockByPadBlock(padBlockPos); } else if (padBlockEntity != null) event.setCanceled(true); } } }
PadBlockEntity类代码
public class PadBlockEntity extends BlockEntity { private BlockPos pos; private BlockPos supportBlockPos; private String password; private boolean isDoorLocked; private String relatedDoor; private int ticks; public PadBlockEntity(BlockPos pos, BlockState state) { super(DoorLockPadMod.PAD_BLOCK_ENTITY.get(), pos, state); this.pos = pos; this.isDoorLocked = true; this.ticks = 0; } public BlockPos getPos() { return pos; } public void setPos(BlockPos pos) { this.pos = pos; } public BlockPos getSupportBlockPos() { return supportBlockPos; } public void setSupportBlockPos(BlockPos supportBlockpos) { this.supportBlockPos = supportBlockpos; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isDoorLocked() { return isDoorLocked; } public void setDoorLocked(boolean doorLocked) { isDoorLocked = doorLocked; } public String getRelatedDoor() { return relatedDoor; } public void setRelatedDoor(String relatedDoor) { this.relatedDoor = relatedDoor; } @Override protected void saveAdditional(CompoundTag nbt) { super.saveAdditional(nbt); if (this.getPassword() != null) nbt.putString("password", this.getPassword()); nbt.putLong("pos", this.getPos().asLong()); nbt.putLong("supportBlockPos", this.getSupportBlockPos().asLong()); if (this.getRelatedDoor() != null) nbt.putString("relatedDoor", this.getRelatedDoor()); } @Override public void load(@NotNull CompoundTag nbt) { super.load(nbt); this.password = nbt.getString("password"); this.pos = BlockPos.of(nbt.getLong("pos")); this.supportBlockPos = BlockPos.of(nbt.getLong("supportBlockPos")); this.relatedDoor = nbt.getString("relatedDoor"); if (StringUtils.isEmpty(this.relatedDoor)) { this.isDoorLocked = true; this.ticks = 0; } } @NotNull @Override public CompoundTag getUpdateTag() { super.getUpdateTag(); return saveWithoutMetadata(); } @Override public void handleUpdateTag(CompoundTag nbt) { super.handleUpdateTag(nbt); this.load(nbt); } @Override public Packet<ClientGamePacketListener> getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } public static <T extends BlockEntity> void tick(Level level, BlockPos blockPos, BlockState state, T blockEntity) { if (level.isClientSide && blockEntity instanceof PadBlockEntity padBlockEntity) { if (!padBlockEntity.isDoorLocked()) { padBlockEntity.ticks++; } if (padBlockEntity.ticks == 200) { padBlockEntity.setDoorLocked(true); FunctionUtils.sendToServer(padBlockEntity); padBlockEntity.ticks = 0; } } } }
解决方案
1. 客户端提前拦截破坏请求(优先推荐)
客户端在发送破坏请求前就判断是否允许破坏,从根源避免服务端取消后出现视觉闪烁:
- 监听客户端的
InputEvent.InteractionKeyMappingTriggered事件,玩家触发破坏动作时,先向服务端发送自定义查询数据包,询问目标方块是否可破坏。 - 服务端收到查询后返回权限结果,客户端根据结果决定是否执行本地破坏动画。
- 核心代码示例:
@Mod.EventBusSubscriber(modid = DoorLockPadMod.MODID, value = Dist.CLIENT) public class ClientEventHandlers { @SubscribeEvent public static void onInteractionTriggered(InputEvent.InteractionKeyMappingTriggered event) { if (event.getKeyMapping() == KeyMapping.MOUSE_BUTTON_LEFT) { HitResult hit = Minecraft.getInstance().hitResult; if (hit.getType() == HitResult.Type.BLOCK) { BlockPos pos = ((BlockHitResult) hit).getBlockPos(); NetworkHooks.sendToServer(new QueryBlockBreakablePacket(pos)); event.setCanceled(true); // 暂时阻止本地动作,等待服务端响应 } } } }
2. 服务端取消事件后强制同步区块数据
当服务端取消破坏事件后,直接向客户端发送目标方块所在区块的完整同步包,覆盖客户端错误状态:
// 在event.setCanceled(true)后添加 if (!level.isClientSide) { ServerPlayer serverPlayer = (ServerPlayer) playerEntity; Chunk chunk = level.getChunkAt(blockPos); serverPlayer.connection.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, false)); }
此方式数据量较大,适合小范围场景使用。
3. 优化BlockEntity同步时机
修改PadBlockEntity数据后,手动标记变更并触发同步:
padBlockEntity.setRelatedDoor(null); padBlockEntity.setPassword(null); padBlockEntity.setChanged(); // 标记BlockEntity已修改 level.sendBlockUpdated(padBlockPos, padBlockState, padBlockState, BlockFlags.BLOCK_UPDATE | BlockFlags.SEND_TO_CLIENTS);
确保服务端数据变更后立即通知客户端更新。
4. 修复原代码逻辑错误
原EventHandlers类中处理PadBlock的分支存在变量未定义问题,将padBlockPos改为blockPos:
} else if (blockState.getBlock() instanceof PadBlock) { PadBlockEntity padBlockEntity = (PadBlockEntity) level.getBlockEntity(blockPos); if (padBlockEntity != null && (!padBlockEntity.isDoorLocked() || playerEntity.isCreative())) { FunctionUtils.removeDoorAssignedPadBlockByPadBlock(blockPos); // 修正变量名 } else if (padBlockEntity != null) event.setCanceled(true); }
内容的提问来源于stack exchange,提问作者Huntwer
相关产品推荐
相关产品推荐

