@@ -12,9 +12,10 @@ module.exports = inject
12
12
const PI = Math . PI
13
13
const PI_2 = Math . PI * 2
14
14
const PHYSICS_INTERVAL_MS = 50
15
- const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000
15
+ const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000 // 0.05
16
16
17
- function inject ( bot , { physicsEnabled } ) {
17
+ function inject ( bot , { physicsEnabled, maxCatchupTicks } ) {
18
+ const PHYSICS_CATCHUP_TICKS = maxCatchupTicks ?? 4
18
19
const world = { getBlock : ( pos ) => { return bot . blockAt ( pos , false ) } }
19
20
const physics = Physics ( bot . registry , world )
20
21
@@ -38,6 +39,7 @@ function inject (bot, { physicsEnabled }) {
38
39
let lastPhysicsFrameTime = null
39
40
let shouldUsePhysics = false
40
41
bot . physicsEnabled = physicsEnabled ?? true
42
+ let deadTicks = 21
41
43
42
44
const lastSent = {
43
45
x : 0 ,
@@ -51,25 +53,44 @@ function inject (bot, { physicsEnabled }) {
51
53
52
54
// This function should be executed each tick (every 0.05 seconds)
53
55
// How it works: https://gafferongames.com/post/fix_your_timestep/
56
+
57
+ // WARNING: THIS IS NOT ACCURATE ON WINDOWS (15.6 Timer Resolution)
58
+ // use WSL or switch to Linux
59
+ // see: https://discord.com/channels/413438066984747026/519952494768685086/901948718255833158
54
60
let timeAccumulator = 0
61
+ let catchupTicks = 0
55
62
function doPhysics ( ) {
56
63
const now = performance . now ( )
57
64
const deltaSeconds = ( now - lastPhysicsFrameTime ) / 1000
58
65
lastPhysicsFrameTime = now
59
66
60
67
timeAccumulator += deltaSeconds
61
-
68
+ catchupTicks = 0
62
69
while ( timeAccumulator >= PHYSICS_TIMESTEP ) {
63
- if ( bot . physicsEnabled && shouldUsePhysics ) {
64
- physics . simulatePlayer ( new PlayerState ( bot , controlState ) , world ) . apply ( bot )
65
- bot . emit ( 'physicsTick' )
66
- bot . emit ( 'physicTick' ) // Deprecated, only exists to support old plugins. May be removed in the future
67
- }
68
- updatePosition ( PHYSICS_TIMESTEP )
70
+ tickPhysics ( now )
69
71
timeAccumulator -= PHYSICS_TIMESTEP
72
+ catchupTicks ++
73
+ if ( catchupTicks >= PHYSICS_CATCHUP_TICKS ) break
74
+ }
75
+ }
76
+
77
+ function tickPhysics ( now ) {
78
+ if ( bot . blockAt ( bot . entity . position ) == null ) return // check if chunk is unloaded
79
+ if ( bot . physicsEnabled && shouldUsePhysics ) {
80
+ physics . simulatePlayer ( new PlayerState ( bot , controlState ) , world ) . apply ( bot )
81
+ bot . emit ( 'physicsTick' )
82
+ bot . emit ( 'physicTick' ) // Deprecated, only exists to support old plugins. May be removed in the future
83
+ }
84
+ if ( shouldUsePhysics ) {
85
+ updatePosition ( now )
70
86
}
71
87
}
72
88
89
+ // remove this when 'physicTick' is removed
90
+ bot . on ( 'newListener' , ( name ) => {
91
+ if ( name === 'physicTick' ) console . warn ( 'Mineflayer detected that you are using a deprecated event (physicTick)! Please use this event (physicsTick) instead.' )
92
+ } )
93
+
73
94
function cleanup ( ) {
74
95
clearInterval ( doPhysicsTimer )
75
96
doPhysicsTimer = null
@@ -117,17 +138,25 @@ function inject (bot, { physicsEnabled }) {
117
138
return dYaw
118
139
}
119
140
120
- function updatePosition ( dt ) {
121
- // If you're dead, you're probably on the ground though ...
122
- if ( ! bot . isAlive ) bot . entity . onGround = true
141
+ // returns false if packet should be sent, true if not
142
+ function sendPositionPacketInDeath ( ) {
143
+ if ( bot . isAlive === true ) deadTicks = 0
144
+ if ( bot . isAlive === false && deadTicks <= 20 ) deadTicks ++
145
+ if ( deadTicks >= 20 ) return true
146
+ return false
147
+ }
148
+
149
+ function updatePosition ( now ) {
150
+ // Only send updates for 20 ticks after death
151
+ if ( sendPositionPacketInDeath ( ) ) return
123
152
124
153
// Increment the yaw in baby steps so that notchian clients (not the server) can keep up.
125
154
const dYaw = deltaYaw ( bot . entity . yaw , lastSentYaw )
126
155
const dPitch = bot . entity . pitch - ( lastSentPitch || 0 )
127
156
128
157
// Vanilla doesn't clamp yaw, so we don't want to do it either
129
- const maxDeltaYaw = dt * physics . yawSpeed
130
- const maxDeltaPitch = dt * physics . pitchSpeed
158
+ const maxDeltaYaw = PHYSICS_TIMESTEP * physics . yawSpeed
159
+ const maxDeltaPitch = PHYSICS_TIMESTEP * physics . pitchSpeed
131
160
lastSentYaw += math . clamp ( - maxDeltaYaw , dYaw , maxDeltaYaw )
132
161
lastSentPitch += math . clamp ( - maxDeltaPitch , dPitch , maxDeltaPitch )
133
162
@@ -137,24 +166,28 @@ function inject (bot, { physicsEnabled }) {
137
166
const onGround = bot . entity . onGround
138
167
139
168
// Only send a position update if necessary, select the appropriate packet
140
- const positionUpdated = lastSent . x !== position . x || lastSent . y !== position . y || lastSent . z !== position . z
169
+ const positionUpdated = lastSent . x !== position . x || lastSent . y !== position . y || lastSent . z !== position . z ||
170
+ // Send a position update every second, even if no other update was made
171
+ // This function rounds to the nearest 50ms (or PHYSICS_INTERVAL_MS) and checks if a second has passed.
172
+ ( Math . round ( ( now - lastSent . time ) / PHYSICS_INTERVAL_MS ) * PHYSICS_INTERVAL_MS ) >= 1000
141
173
const lookUpdated = lastSent . yaw !== yaw || lastSent . pitch !== pitch
142
174
143
- if ( positionUpdated && lookUpdated && bot . isAlive ) {
175
+ if ( positionUpdated && lookUpdated ) {
144
176
sendPacketPositionAndLook ( position , yaw , pitch , onGround )
145
- } else if ( positionUpdated && bot . isAlive ) {
177
+ lastSent . time = now // only reset if positionUpdated is true
178
+ } else if ( positionUpdated ) {
146
179
sendPacketPosition ( position , onGround )
147
- } else if ( lookUpdated && bot . isAlive ) {
180
+ lastSent . time = now // only reset if positionUpdated is true
181
+ } else if ( lookUpdated ) {
148
182
sendPacketLook ( yaw , pitch , onGround )
149
- } else if ( performance . now ( ) - lastSent . time >= 1000 ) {
150
- // Send a position packet every second, even if no update was made
151
- sendPacketPosition ( position , onGround )
152
- lastSent . time = performance . now ( )
153
- } else if ( positionUpdateSentEveryTick && bot . isAlive ) {
183
+ } else if ( positionUpdateSentEveryTick || onGround !== lastSent . onGround ) {
154
184
// For versions < 1.12, one player packet should be sent every tick
155
185
// for the server to update health correctly
186
+ // For versions >= 1.12, onGround !== lastSent.onGround should be used, but it doesn't ever trigger outside of login
156
187
bot . _client . write ( 'flying' , { onGround : bot . entity . onGround } )
157
188
}
189
+
190
+ lastSent . onGround = bot . entity . onGround // onGround is always set
158
191
}
159
192
160
193
bot . physics = physics
@@ -262,7 +295,14 @@ function inject (bot, { physicsEnabled }) {
262
295
// player position and look (clientbound)
263
296
bot . _client . on ( 'position' , ( packet ) => {
264
297
bot . entity . height = 1.62
265
- bot . entity . velocity . set ( 0 , 0 , 0 )
298
+
299
+ // Velocity is only set to 0 if the flag is not set, otherwise keep current velocity
300
+ const vel = bot . entity . velocity
301
+ vel . set (
302
+ packet . flags & 1 ? vel . x : 0 ,
303
+ packet . flags & 2 ? vel . y : 0 ,
304
+ packet . flags & 4 ? vel . z : 0
305
+ )
266
306
267
307
// If flag is set, then the corresponding value is relative, else it is absolute
268
308
const pos = bot . entity . position
@@ -280,19 +320,14 @@ function inject (bot, { physicsEnabled }) {
280
320
281
321
if ( bot . supportFeature ( 'teleportUsesOwnPacket' ) ) {
282
322
bot . _client . write ( 'teleport_confirm' , { teleportId : packet . teleportId } )
283
- // Force send an extra packet to be like vanilla client
284
- sendPacketPositionAndLook ( pos , newYaw , newPitch , bot . entity . onGround )
285
323
}
286
324
sendPacketPositionAndLook ( pos , newYaw , newPitch , bot . entity . onGround )
287
325
288
326
shouldUsePhysics = true
289
- bot . entity . timeSinceOnGround = 0
327
+ bot . jumpTicks = 0
290
328
lastSentYaw = bot . entity . yaw
329
+ lastSentPitch = bot . entity . pitch
291
330
292
- if ( doPhysicsTimer === null ) {
293
- lastPhysicsFrameTime = performance . now ( )
294
- doPhysicsTimer = setInterval ( doPhysics , PHYSICS_INTERVAL_MS )
295
- }
296
331
bot . emit ( 'forcedMove' )
297
332
} )
298
333
@@ -313,5 +348,12 @@ function inject (bot, { physicsEnabled }) {
313
348
314
349
bot . on ( 'mount' , ( ) => { shouldUsePhysics = false } )
315
350
bot . on ( 'respawn' , ( ) => { shouldUsePhysics = false } )
351
+ bot . on ( 'login' , ( ) => {
352
+ shouldUsePhysics = false
353
+ if ( doPhysicsTimer === null ) {
354
+ lastPhysicsFrameTime = performance . now ( )
355
+ doPhysicsTimer = setInterval ( doPhysics , PHYSICS_INTERVAL_MS )
356
+ }
357
+ } )
316
358
bot . on ( 'end' , cleanup )
317
359
}
0 commit comments