32
32
import org .cloudburstmc .protocol .bedrock .data .PlayerBlockActionData ;
33
33
import org .cloudburstmc .protocol .bedrock .data .definitions .ItemDefinition ;
34
34
import org .cloudburstmc .protocol .bedrock .packet .LevelEventPacket ;
35
+ import org .geysermc .geyser .GeyserImpl ;
35
36
import org .geysermc .geyser .api .block .custom .CustomBlockState ;
37
+ import org .geysermc .geyser .entity .EntityDefinitions ;
36
38
import org .geysermc .geyser .entity .type .Entity ;
37
39
import org .geysermc .geyser .entity .type .ItemFrameEntity ;
38
40
import org .geysermc .geyser .inventory .GeyserItemStack ;
44
46
import org .geysermc .geyser .session .GeyserSession ;
45
47
import org .geysermc .geyser .session .cache .SkullCache ;
46
48
import org .geysermc .geyser .translator .item .CustomItemTranslator ;
49
+ import org .geysermc .geyser .translator .protocol .bedrock .BedrockInventoryTransactionTranslator ;
47
50
import org .geysermc .geyser .util .BlockUtils ;
48
51
import org .geysermc .mcprotocollib .protocol .data .game .entity .object .Direction ;
49
52
import org .geysermc .mcprotocollib .protocol .data .game .entity .player .GameMode ;
@@ -62,6 +65,7 @@ static void translate(GeyserSession session, List<PlayerBlockActionData> playerA
62
65
session .getBookEditCache ().checkForSend ();
63
66
64
67
for (PlayerBlockActionData blockActionData : playerActions ) {
68
+ GeyserImpl .getInstance ().getLogger ().error (blockActionData .toString ());
65
69
handle (session , blockActionData );
66
70
}
67
71
}
@@ -70,14 +74,15 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
70
74
PlayerActionType action = blockActionData .getAction ();
71
75
Vector3i vector = blockActionData .getBlockPosition ();
72
76
int blockFace = blockActionData .getFace ();
77
+
73
78
switch (action ) {
74
79
case DROP_ITEM -> {
75
80
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket (PlayerAction .DROP_ITEM ,
76
81
vector , Direction .VALUES [blockFace ], 0 );
77
82
session .sendDownstreamGamePacket (dropItemPacket );
78
83
}
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 -> {
81
86
if (session .getGameMode () == GameMode .CREATIVE ) {
82
87
break ;
83
88
}
@@ -86,62 +91,26 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
86
91
return ;
87
92
}
88
93
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 ();
114
94
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
- }
120
95
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 );
124
99
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 );
129
102
break ;
130
103
}
131
104
132
- if (!canMine (session , vector )) {
133
- return ;
134
- }
135
-
136
105
int breakingBlock = session .getBreakingBlock ();
137
106
if (breakingBlock == -1 ) {
107
+ // TODO ??????
138
108
breakingBlock = Block .JAVA_AIR_ID ;
139
109
}
140
110
141
111
Vector3f vectorFloat = vector .toFloat ();
142
112
143
113
BlockState breakingBlockState = BlockState .of (breakingBlock );
144
- Direction direction = Direction .VALUES [blockFace ];
145
114
spawnBlockBreakParticles (session , direction , vector , breakingBlockState );
146
115
147
116
double breakTime = BlockUtils .getSessionBreakTimeTicks (session , breakingBlockState .block ());
@@ -186,6 +155,13 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
186
155
}
187
156
}
188
157
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
+
189
165
ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket (PlayerAction .CANCEL_DIGGING , vector , Direction .DOWN , 0 );
190
166
session .sendDownstreamGamePacket (abortBreakingPacket );
191
167
@@ -198,11 +174,119 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct
198
174
session .sendUpstreamPacket (stopBreak );
199
175
}
200
176
// Handled in BedrockInventoryTransactionTranslator
201
- case STOP_BREAK -> {
177
+ case BLOCK_PREDICT_DESTROY -> {
178
+ breakBlock (session , vector , blockFace );
202
179
}
203
180
}
204
181
}
205
182
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
+
206
290
private static boolean canMine (GeyserSession session , Vector3i vector ) {
207
291
if (session .isHandsBusy ()) {
208
292
session .setBreakingBlock (-1 );
0 commit comments