Skip to content

Commit c08bde5

Browse files
committed
Start fixing our block breaking code
1 parent 1c1684c commit c08bde5

File tree

6 files changed

+150
-96
lines changed

6 files changed

+150
-96
lines changed

core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public ItemMapping getMapping(ItemData data) {
144144
return lightBlock;
145145
}
146146

147-
boolean isBlock = data.getBlockDefinition() != null;
147+
boolean isBlock = data.getBlockDefinition() != null && data.getBlockDefinition().getRuntimeId() != 0;
148148
boolean hasDamage = data.getDamage() != 0;
149149

150150
for (ItemMapping mapping : this.items) {

core/src/main/java/org/geysermc/geyser/session/GeyserSession.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,18 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
401401
@Setter
402402
private BedrockDimension bedrockDimension = this.bedrockOverworldDimension;
403403

404+
/**
405+
* Stores the blockstate of the block being currently broken.
406+
*/
404407
@Setter
405408
private int breakingBlock;
406409

410+
/**
411+
* Stores the block break position of the currently broken block.
412+
*/
413+
@Setter
414+
private Vector3i blockBreakPosition;
415+
407416
@Setter
408417
private Vector3i lastBlockPlacePosition;
409418

@@ -1594,7 +1603,7 @@ private void startGame() {
15941603

15951604
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.SERVER);
15961605
startGamePacket.setRewindHistorySize(0);
1597-
startGamePacket.setServerAuthoritativeBlockBreaking(false);
1606+
startGamePacket.setServerAuthoritativeBlockBreaking(true);
15981607

15991608
startGamePacket.setServerId("");
16001609
startGamePacket.setWorldId("");

core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,9 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
534534
case CRAFT_RESULTS_DEPRECATED: // Tends to be called for UI inventories
535535
case CRAFT_RECIPE_OPTIONAL: // Anvils and cartography tables will handle this
536536
case CRAFT_LOOM: // Looms 1.17.40+
537-
case CRAFT_REPAIR_AND_DISENCHANT: { // Grindstones 1.17.40+
537+
case CRAFT_REPAIR_AND_DISENCHANT: // Grindstones 1.17.40+
538+
case MINE_BLOCK: // Server auth block breaking, confirms durability change
538539
break;
539-
}
540540
default:
541541
return rejectRequest(request);
542542
}

core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet)
175175
break;
176176
case ITEM_USE:
177177
switch (packet.getActionType()) {
178+
// Block placing
178179
case 0 -> {
179180
final Vector3i packetBlockPosition = packet.getBlockPosition();
180181
Vector3i blockPos = BlockUtils.getBlockPosition(packetBlockPosition, packet.getBlockFace());

core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java

Lines changed: 129 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
3333
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
3434
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
35+
import org.geysermc.geyser.GeyserImpl;
3536
import org.geysermc.geyser.api.block.custom.CustomBlockState;
37+
import org.geysermc.geyser.entity.EntityDefinitions;
3638
import org.geysermc.geyser.entity.type.Entity;
3739
import org.geysermc.geyser.entity.type.ItemFrameEntity;
3840
import org.geysermc.geyser.inventory.GeyserItemStack;
@@ -44,6 +46,7 @@
4446
import org.geysermc.geyser.session.GeyserSession;
4547
import org.geysermc.geyser.session.cache.SkullCache;
4648
import org.geysermc.geyser.translator.item.CustomItemTranslator;
49+
import org.geysermc.geyser.translator.protocol.bedrock.BedrockInventoryTransactionTranslator;
4750
import org.geysermc.geyser.util.BlockUtils;
4851
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
4952
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
@@ -62,6 +65,7 @@ static void translate(GeyserSession session, List<PlayerBlockActionData> playerA
6265
session.getBookEditCache().checkForSend();
6366

6467
for (PlayerBlockActionData blockActionData : playerActions) {
68+
GeyserImpl.getInstance().getLogger().error(blockActionData.toString());
6569
handle(session, blockActionData);
6670
}
6771
}
@@ -70,14 +74,15 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
7074
PlayerActionType action = blockActionData.getAction();
7175
Vector3i vector = blockActionData.getBlockPosition();
7276
int blockFace = blockActionData.getFace();
77+
7378
switch (action) {
7479
case DROP_ITEM -> {
7580
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
7681
vector, Direction.VALUES[blockFace], 0);
7782
session.sendDownstreamGamePacket(dropItemPacket);
7883
}
79-
case START_BREAK -> {
80-
// Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021
84+
case START_BREAK -> startBlockBreak(session, vector, blockFace);
85+
case BLOCK_CONTINUE_DESTROY -> {
8186
if (session.getGameMode() == GameMode.CREATIVE) {
8287
break;
8388
}
@@ -86,62 +91,26 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
8691
return;
8792
}
8893

89-
// Start the block breaking animation
90-
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector);
91-
LevelEventPacket startBreak = new LevelEventPacket();
92-
startBreak.setType(LevelEvent.BLOCK_START_BREAK);
93-
startBreak.setPosition(vector.toFloat());
94-
double breakTime = BlockUtils.getSessionBreakTimeTicks(session, BlockState.of(blockState).block());
95-
96-
// If the block is custom or the breaking item is custom, we must keep track of break time ourselves
97-
GeyserItemStack item = session.getPlayerInventory().getItemInHand();
98-
ItemMapping mapping = item.getMapping(session);
99-
ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(item.getComponents(), mapping) : null;
100-
CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(blockState);
101-
SkullCache.Skull skull = session.getSkullCache().getSkulls().get(vector);
102-
103-
session.setBlockBreakStartTime(0);
104-
if (blockStateOverride != null || customItem != null || (skull != null && skull.getBlockDefinition() != null)) {
105-
session.setBlockBreakStartTime(System.currentTimeMillis());
106-
}
107-
startBreak.setData((int) (65535 / breakTime));
108-
session.setBreakingBlock(blockState);
109-
session.sendUpstreamPacket(startBreak);
110-
111-
// Account for fire - the client likes to hit the block behind.
112-
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, blockFace);
113-
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
11494
Direction direction = Direction.VALUES[blockFace];
115-
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
116-
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
117-
direction, session.getWorldCache().nextPredictionSequence());
118-
session.sendDownstreamGamePacket(startBreakingPacket);
119-
}
12095

121-
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING,
122-
vector, direction, session.getWorldCache().nextPredictionSequence());
123-
session.sendDownstreamGamePacket(startBreakingPacket);
96+
// The Bedrock client won't send a new start_break packet, but just continue breaking blocks
97+
if (!vector.equals(session.getBlockBreakPosition())) {
98+
GeyserImpl.getInstance().getLogger().error("Invalid block break position! Expected " + session.getBlockBreakPosition() + ", got " + vector);
12499

125-
spawnBlockBreakParticles(session, direction, vector, BlockState.of(blockState));
126-
}
127-
case CONTINUE_BREAK -> {
128-
if (session.getGameMode() == GameMode.CREATIVE) {
100+
// Start breaking new block
101+
startBlockBreak(session, vector, blockFace);
129102
break;
130103
}
131104

132-
if (!canMine(session, vector)) {
133-
return;
134-
}
135-
136105
int breakingBlock = session.getBreakingBlock();
137106
if (breakingBlock == -1) {
107+
// TODO ??????
138108
breakingBlock = Block.JAVA_AIR_ID;
139109
}
140110

141111
Vector3f vectorFloat = vector.toFloat();
142112

143113
BlockState breakingBlockState = BlockState.of(breakingBlock);
144-
Direction direction = Direction.VALUES[blockFace];
145114
spawnBlockBreakParticles(session, direction, vector, breakingBlockState);
146115

147116
double breakTime = BlockUtils.getSessionBreakTimeTicks(session, breakingBlockState.block());
@@ -186,6 +155,13 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
186155
}
187156
}
188157

158+
// Bedrock "confirms" that it stopped breaking blocks by sending an abort packet after breaking the block
159+
if (session.getBlockBreakPosition() == null) {
160+
break;
161+
}
162+
163+
session.setBlockBreakPosition(null);
164+
189165
ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, vector, Direction.DOWN, 0);
190166
session.sendDownstreamGamePacket(abortBreakingPacket);
191167

@@ -198,11 +174,119 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
198174
session.sendUpstreamPacket(stopBreak);
199175
}
200176
// Handled in BedrockInventoryTransactionTranslator
201-
case STOP_BREAK -> {
177+
case BLOCK_PREDICT_DESTROY -> {
178+
breakBlock(session, vector, blockFace);
202179
}
203180
}
204181
}
205182

183+
private static void startBlockBreak(GeyserSession session, Vector3i vector, int blockFace) {
184+
session.setBlockBreakPosition(vector);
185+
186+
// Only send block breaking in the BLOCK_PREDICT_DESTROY case
187+
if (session.getGameMode() == GameMode.CREATIVE) {
188+
return;
189+
}
190+
191+
if (!canMine(session, vector)) {
192+
return;
193+
}
194+
195+
// Start the block breaking animation
196+
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector);
197+
LevelEventPacket startBreak = new LevelEventPacket();
198+
startBreak.setType(LevelEvent.BLOCK_START_BREAK);
199+
startBreak.setPosition(vector.toFloat());
200+
double breakTime = BlockUtils.getSessionBreakTimeTicks(session, BlockState.of(blockState).block());
201+
202+
// If the block is custom or the breaking item is custom, we must keep track of break time ourselves
203+
GeyserItemStack item = session.getPlayerInventory().getItemInHand();
204+
ItemMapping mapping = item.getMapping(session);
205+
ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(item.getComponents(), mapping) : null;
206+
CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(blockState);
207+
SkullCache.Skull skull = session.getSkullCache().getSkulls().get(vector);
208+
209+
session.setBlockBreakPosition(vector); // TODO account for fire workaround
210+
session.setBlockBreakStartTime(0);
211+
if (blockStateOverride != null || customItem != null || (skull != null && skull.getBlockDefinition() != null)) {
212+
session.setBlockBreakStartTime(System.currentTimeMillis());
213+
}
214+
startBreak.setData((int) (65535 / breakTime));
215+
session.setBreakingBlock(blockState);
216+
session.sendUpstreamPacket(startBreak);
217+
218+
// Account for fire - the client likes to hit the block behind.
219+
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, blockFace);
220+
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
221+
Direction direction = Direction.VALUES[blockFace];
222+
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
223+
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
224+
direction, session.getWorldCache().nextPredictionSequence());
225+
session.sendDownstreamGamePacket(startBreakingPacket);
226+
227+
// ServerboundPlayerActionPacket stopBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING, fireBlockPos,
228+
// direction, session.getWorldCache().nextPredictionSequence());
229+
// session.sendDownstreamGamePacket(stopBreakingPacket);
230+
}
231+
232+
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING,
233+
vector, direction, session.getWorldCache().nextPredictionSequence());
234+
session.sendDownstreamGamePacket(startBreakingPacket);
235+
236+
spawnBlockBreakParticles(session, direction, vector, BlockState.of(blockState));
237+
}
238+
239+
private static void breakBlock(GeyserSession session, Vector3i vector, int blockFace) {
240+
int blockState = session.getGameMode() == GameMode.CREATIVE ?
241+
session.getGeyser().getWorldManager().getBlockAt(session, vector) : session.getBreakingBlock();
242+
243+
session.setLastBlockPlaced(null);
244+
session.setLastBlockPlacePosition(null);
245+
246+
// Same deal with vanilla block placing as above.
247+
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
248+
BedrockInventoryTransactionTranslator.restoreCorrectBlock(session, vector);
249+
return;
250+
}
251+
252+
Vector3f playerPosition = session.getPlayerEntity().getPosition();
253+
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
254+
255+
// why is this here??? move to start break
256+
if (!BedrockInventoryTransactionTranslator.canInteractWithBlock(session, playerPosition, vector)) {
257+
BedrockInventoryTransactionTranslator.restoreCorrectBlock(session, vector);
258+
return;
259+
}
260+
261+
int sequence = session.getWorldCache().nextPredictionSequence();
262+
session.getWorldCache().markPositionInSequence(vector);
263+
// -1 means we don't know what block they're breaking
264+
if (blockState == -1) {
265+
blockState = Block.JAVA_AIR_ID;
266+
}
267+
268+
LevelEventPacket blockBreakPacket = new LevelEventPacket();
269+
blockBreakPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
270+
blockBreakPacket.setPosition(vector.toFloat());
271+
blockBreakPacket.setData(session.getBlockMappings().getBedrockBlockId(blockState));
272+
session.sendUpstreamPacket(blockBreakPacket);
273+
session.setBreakingBlock(-1);
274+
session.setBlockBreakPosition(null);
275+
276+
// TODO move
277+
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector);
278+
if (itemFrameEntity != null) {
279+
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
280+
InteractAction.ATTACK, session.isSneaking());
281+
session.sendDownstreamGamePacket(attackPacket);
282+
return;
283+
}
284+
285+
PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING;
286+
ServerboundPlayerActionPacket breakPacket = new ServerboundPlayerActionPacket(action, vector, Direction.VALUES[blockFace], sequence);
287+
session.sendDownstreamGamePacket(breakPacket);
288+
}
289+
206290
private static boolean canMine(GeyserSession session, Vector3i vector) {
207291
if (session.isHandsBusy()) {
208292
session.setBreakingBlock(-1);

0 commit comments

Comments
 (0)