@miner-org/mineflayer-physics-reworked 0.0.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PhysicsEngine } from "./src/engine";
1
+ import { PhysicsEngine, PlayerState } from "./src/engine";
2
2
 
3
3
  interface ControlState {
4
4
  forward: boolean;
@@ -10,19 +10,13 @@ interface ControlState {
10
10
  }
11
11
 
12
12
  declare module "@miner-org/mineflayer-physics-reworked" {
13
- export default function inject(bot: Bot): void;
13
+ PhysicsEngine;
14
+ PlayerState;
14
15
  }
15
16
 
16
17
  declare module "mineflayer" {
17
18
  interface Bot {
18
- ashPhysics: PhysicsEngine;
19
- ashPhysicsEnabled: boolean;
20
- ashControlState: ControlState;
21
- ashGetControlState(): ControlState;
22
- ashClearControlStates(): void;
23
- ashSetControlState(
24
- control: "forward" | "back" | "left" | "right" | "jump" | "sneak",
25
- value: boolean,
26
- ): void;
19
+ physics: PhysicsEngine;
20
+ physicsEnabled: boolean;
27
21
  }
28
22
  }
package/index.js CHANGED
@@ -1,501 +1,3 @@
1
- const { Vec3 } = require("vec3");
2
- const assert = require("assert");
3
- const math = require("mineflayer/lib/math");
4
- const conv = require("mineflayer/lib/conversions");
5
- const { performance } = require("perf_hooks");
6
- const { createDoneTask, createTask } = require("mineflayer/lib/promise_utils");
1
+ const { PlayerState, PhysicsEngine } = require("./src/engine");
7
2
 
8
- const { PhysicsEngine, PlayerState } = require("./src/engine.js");
9
-
10
- module.exports = inject;
11
-
12
- const PI = Math.PI;
13
- const PI_2 = Math.PI * 2;
14
- const PHYSICS_INTERVAL_MS = 50;
15
- const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000; // 0.05
16
-
17
- /**
18
- * @param {import('mineflayer').Bot} bot
19
- */
20
- function inject(bot) {
21
- // Disable mineflayer's built-in physics
22
- bot.physicsEnabled = false;
23
- bot._client.on("position", () => {
24
- bot.physicsEnabled = false;
25
- });
26
-
27
- const world = {
28
- getBlock: (pos) => bot.blockAt(pos, false),
29
- };
30
-
31
- const physics = new PhysicsEngine(bot.registry, world);
32
-
33
- const positionUpdateSentEveryTick = bot.supportFeature(
34
- "positionUpdateSentEveryTick",
35
- );
36
-
37
- // Jump state
38
- bot.jumpQueued = false;
39
- bot.jumpTicks = 0;
40
-
41
- // Control state
42
- const controlState = {
43
- forward: false,
44
- back: false,
45
- left: false,
46
- right: false,
47
- jump: false,
48
- sprint: false,
49
- sneak: false,
50
- };
51
-
52
- // Look state
53
- let lastSentYaw = null;
54
- let lastSentPitch = null;
55
-
56
- // Physics timing
57
- let doPhysicsTimer = null;
58
-
59
- // Physics enabled flags
60
- let shouldUsePhysics = false;
61
- bot.ashPhysicsEnabled = true;
62
- let deadTicks = 21;
63
-
64
- // Last sent packet data
65
- const lastSent = {
66
- x: 0,
67
- y: 0,
68
- z: 0,
69
- yaw: 0,
70
- pitch: 0,
71
- onGround: false,
72
- time: 0,
73
- flags: { onGround: false, hasHorizontalCollision: false },
74
- };
75
-
76
- function doPhysics() {
77
- const now = performance.now();
78
- tickPhysics(now);
79
- }
80
-
81
- function tickPhysics(now) {
82
- // Skip if chunk is unloaded
83
- if (bot.blockAt(bot.entity.position) == null) return;
84
-
85
- if (bot.ashPhysicsEnabled && shouldUsePhysics) {
86
- const state = new PlayerState(bot, controlState);
87
-
88
- physics.simulatePlayer(state);
89
- state.apply(bot);
90
-
91
- bot.emit("physicsTick");
92
- bot.emit("physicTick"); // Deprecated
93
- }
94
-
95
- if (shouldUsePhysics) {
96
- updatePosition(now);
97
- }
98
- }
99
-
100
- /**
101
- * Update position and send packets to server
102
- */
103
- function updatePosition(now) {
104
- if (isEntityRemoved()) return;
105
-
106
- const dYaw = deltaYaw(bot.entity.yaw, lastSentYaw);
107
- const dPitch = bot.entity.pitch - (lastSentPitch || 0);
108
-
109
- const maxDeltaYaw = PHYSICS_TIMESTEP * physics.constants.yawSpeed;
110
- const maxDeltaPitch = PHYSICS_TIMESTEP * physics.constants.pitchSpeed;
111
- lastSentYaw += math.clamp(-maxDeltaYaw, dYaw, maxDeltaYaw);
112
- lastSentPitch += math.clamp(-maxDeltaPitch, dPitch, maxDeltaPitch);
113
-
114
- const yaw = Math.fround(conv.toNotchianYaw(lastSentYaw));
115
- const pitch = Math.fround(conv.toNotchianPitch(lastSentPitch));
116
- const position = bot.entity.position;
117
- const onGround = bot.entity.onGround;
118
-
119
- const positionUpdated =
120
- lastSent.x !== position.x ||
121
- lastSent.y !== position.y ||
122
- lastSent.z !== position.z ||
123
- Math.round((now - lastSent.time) / PHYSICS_INTERVAL_MS) *
124
- PHYSICS_INTERVAL_MS >=
125
- 1000;
126
-
127
- const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch;
128
-
129
- if (positionUpdated && lookUpdated) {
130
- sendPacketPositionAndLook(position, yaw, pitch, onGround);
131
- lastSent.time = now;
132
- } else if (positionUpdated) {
133
- sendPacketPosition(position, onGround);
134
- lastSent.time = now;
135
- } else if (lookUpdated) {
136
- sendPacketLook(yaw, pitch, onGround);
137
- } else if (positionUpdateSentEveryTick || onGround !== lastSent.onGround) {
138
- bot._client.write("flying", {
139
- onGround: bot.entity.onGround,
140
- flags: {
141
- onGround: bot.entity.onGround,
142
- hasHorizontalCollision: undefined,
143
- },
144
- });
145
- }
146
-
147
- lastSent.onGround = bot.entity.onGround;
148
- }
149
-
150
- function sendPacketPosition(position, onGround) {
151
- const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z);
152
- lastSent.x = position.x;
153
- lastSent.y = position.y;
154
- lastSent.z = position.z;
155
- lastSent.onGround = onGround;
156
- lastSent.flags = { onGround, hasHorizontalCollision: undefined };
157
- bot._client.write("position", lastSent);
158
- bot.emit("move", oldPos);
159
- }
160
-
161
- function sendPacketLook(yaw, pitch, onGround) {
162
- const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z);
163
- lastSent.yaw = yaw;
164
- lastSent.pitch = pitch;
165
- lastSent.onGround = onGround;
166
- lastSent.flags = { onGround, hasHorizontalCollision: undefined };
167
- bot._client.write("look", lastSent);
168
- bot.emit("move", oldPos);
169
- }
170
-
171
- function sendPacketPositionAndLook(position, yaw, pitch, onGround) {
172
- const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z);
173
- lastSent.x = position.x;
174
- lastSent.y = position.y;
175
- lastSent.z = position.z;
176
- lastSent.yaw = yaw;
177
- lastSent.pitch = pitch;
178
- lastSent.onGround = onGround;
179
- lastSent.flags = { onGround, hasHorizontalCollision: undefined };
180
- bot._client.write("position_look", lastSent);
181
- bot.emit("move", oldPos);
182
- }
183
-
184
- function deltaYaw(yaw1, yaw2) {
185
- let dYaw = (yaw1 - yaw2) % PI_2;
186
- if (dYaw < -PI) dYaw += PI_2;
187
- else if (dYaw > PI) dYaw -= PI_2;
188
- return dYaw;
189
- }
190
-
191
- function isEntityRemoved() {
192
- if (bot.isAlive === true) deadTicks = 0;
193
- if (bot.isAlive === false && deadTicks <= 20) deadTicks++;
194
- if (deadTicks >= 20) return true;
195
- return false;
196
- }
197
-
198
- function getEffectLevel(mcData, effectName, effects) {
199
- const effectDescriptor = mcData.effectsByName[effectName];
200
- if (!effectDescriptor) return 0;
201
- const effectInfo = effects[effectDescriptor.id];
202
- if (!effectInfo) return 0;
203
- return effectInfo.amplifier + 1;
204
- }
205
-
206
- function cleanup() {
207
- clearInterval(doPhysicsTimer);
208
- doPhysicsTimer = null;
209
- }
210
-
211
- // Expose physics engine to bot
212
- bot.ashPhysics = physics;
213
-
214
- bot.ashSetControlState = (control, state) => {
215
- assert.ok(control in controlState, `invalid control: ${control}`);
216
- assert.ok(typeof state === "boolean", `invalid state: ${state}`);
217
- if (controlState[control] === state) return;
218
-
219
- controlState[control] = state;
220
-
221
- if (control === "jump" && state) {
222
- bot.jumpQueued = true;
223
- } else if (control === "sprint") {
224
- bot._client.write("entity_action", {
225
- entityId: bot.entity.id,
226
- actionId: state ? 3 : 4,
227
- jumpBoost: 0,
228
- });
229
- } else if (control === "sneak") {
230
- bot._client.write("entity_action", {
231
- entityId: bot.entity.id,
232
- actionId: state ? 0 : 1,
233
- jumpBoost: 0,
234
- });
235
- }
236
- };
237
-
238
- bot.ashGetControlState = (control) => {
239
- assert.ok(control in controlState, `invalid control: ${control}`);
240
- return controlState[control];
241
- };
242
-
243
- bot.ashClearControlStates = () => {
244
- for (const control in controlState) {
245
- bot.ashSetControlState(control, false);
246
- }
247
- };
248
-
249
- bot.ashControlState = {};
250
- for (const control of Object.keys(controlState)) {
251
- Object.defineProperty(bot.ashControlState, control, {
252
- get() {
253
- return controlState[control];
254
- },
255
- set(state) {
256
- bot.ashSetControlState(control, state);
257
- return state;
258
- },
259
- });
260
- }
261
-
262
- let lookingTask = createDoneTask();
263
-
264
- bot.on("move", () => {
265
- if (
266
- !lookingTask.done &&
267
- Math.abs(deltaYaw(bot.entity.yaw, lastSentYaw)) < 0.001
268
- ) {
269
- lookingTask.finish();
270
- }
271
- });
272
-
273
- bot.look = async (yaw, pitch, force) => {
274
- if (!lookingTask.done) {
275
- lookingTask.finish();
276
- }
277
- lookingTask = createTask();
278
-
279
- const sensitivity = conv.fromNotchianPitch(0.15);
280
- const yawChange =
281
- Math.round((yaw - bot.entity.yaw) / sensitivity) * sensitivity;
282
- const pitchChange =
283
- Math.round((pitch - bot.entity.pitch) / sensitivity) * sensitivity;
284
-
285
- if (yawChange === 0 && pitchChange === 0) {
286
- return;
287
- }
288
-
289
- bot.entity.yaw += yawChange;
290
- bot.entity.pitch += pitchChange;
291
-
292
- if (force) {
293
- lastSentYaw = yaw;
294
- lastSentPitch = pitch;
295
- return;
296
- }
297
-
298
- await lookingTask.promise;
299
- };
300
-
301
- bot.lookAt = async (point, force) => {
302
- const delta = point.minus(
303
- bot.entity.position.offset(0, bot.entity.eyeHeight, 0),
304
- );
305
- const yaw = Math.atan2(-delta.x, -delta.z);
306
- const groundDistance = Math.sqrt(delta.x * delta.x + delta.z * delta.z);
307
- const pitch = Math.atan2(delta.y, groundDistance);
308
- await bot.look(yaw, pitch, force);
309
- };
310
-
311
- bot.elytraFly = async () => {
312
- if (bot.entity.elytraFlying) {
313
- throw new Error("Already elytra flying");
314
- } else if (bot.entity.onGround) {
315
- throw new Error("Unable to fly from ground");
316
- } else if (bot.entity.isInWater) {
317
- throw new Error("Unable to elytra fly while in water");
318
- }
319
-
320
- const mcData = require("minecraft-data")(bot.version);
321
- if (getEffectLevel(mcData, "Levitation", bot.entity.effects) > 0) {
322
- throw new Error("Unable to elytra fly with levitation effect");
323
- }
324
-
325
- const torsoSlot = bot.getEquipmentDestSlot("torso");
326
- const item = bot.inventory.slots[torsoSlot];
327
- if (item == null || item.name !== "elytra") {
328
- throw new Error("Elytra must be equipped to start flying");
329
- }
330
-
331
- bot._client.write("entity_action", {
332
- entityId: bot.entity.id,
333
- actionId: 8,
334
- jumpBoost: 0,
335
- });
336
- };
337
-
338
- bot.waitForTicks = async function (ticks) {
339
- if (ticks <= 0) return;
340
-
341
- await new Promise((resolve) => {
342
- const tickListener = () => {
343
- ticks--;
344
- if (ticks === 0) {
345
- bot.removeListener("physicsTick", tickListener);
346
- resolve();
347
- }
348
- };
349
- bot.on("physicsTick", tickListener);
350
- });
351
- };
352
-
353
- // Deprecated event warning
354
- bot.on("newListener", (name) => {
355
- if (name === "physicTick") {
356
- console.warn(
357
- "Mineflayer: You are using a deprecated event (physicTick)! Use physicsTick instead.",
358
- );
359
- }
360
- });
361
-
362
- // Track crawling/pose state from server-confirmed entity metadata
363
- bot._client.on("entity_metadata", (packet) => {
364
- if (!bot.entity || packet.entityId !== bot.entity.id) return;
365
-
366
- const entity = bot.entity;
367
-
368
- if (bot.supportFeature("mcDataHasEntityMetadata")) {
369
- const metadataKeys =
370
- bot.registry.entitiesByName[entity.name]?.metadataKeys;
371
- if (!metadataKeys) return;
372
-
373
- const metas = Object.fromEntries(
374
- packet.metadata.map((e) => [metadataKeys[e.key], e.value]),
375
- );
376
-
377
- if (metas.pose != null) {
378
- entity._pose = metas.pose;
379
- entity.isElytra = metas.pose === 1;
380
- entity.isSwimmingPose = metas.pose === 3;
381
- // Crawling is swimming pose but not in water
382
- entity.isCrawling = metas.pose === 3 && !entity.isInWater;
383
-
384
- bot.emit("botPoseUpdate", {
385
- pose: metas.pose,
386
- crawling: entity.isCrawling,
387
- swimming: entity.isSwimmingPose && entity.isInWater,
388
- elytra: entity.isElytra,
389
- });
390
- }
391
- }
392
- });
393
-
394
- // Entity velocity — handles being pushed by players, mobs, minecarts, etc.
395
- //this shit does not work for mineflayer bots :(
396
- bot._client.on("entity_velocity", (packet) => {
397
- if (packet.entityId !== bot.entity.id) return;
398
- if (!bot.ashPhysicsEnabled) return;
399
-
400
- bot.entity.velocity.x += packet.velocityX / 8000;
401
- bot.entity.velocity.y += packet.velocityY / 8000;
402
- bot.entity.velocity.z += packet.velocityZ / 8000;
403
- });
404
-
405
- // Explosion knockbakc
406
- bot._client.on("explosion", (explosion) => {
407
- if (bot.ashPhysicsEnabled && bot.game.gameMode !== "creative") {
408
- if (explosion.playerKnockback) {
409
- // 1.21.3+
410
- bot.entity.velocity.add(
411
- explosion.playerMotionX,
412
- explosion.playerMotionY,
413
- explosion.playerMotionZ,
414
- );
415
- } else if ("playerMotionX" in explosion) {
416
- // older versions
417
- bot.entity.velocity.x += explosion.playerMotionX;
418
- bot.entity.velocity.y += explosion.playerMotionY;
419
- bot.entity.velocity.z += explosion.playerMotionZ;
420
- }
421
- }
422
- });
423
-
424
- // Handle player rotation packet (1.21.3+)
425
- bot._client.on("player_rotation", (packet) => {
426
- bot.entity.yaw = conv.fromNotchianYaw(packet.yaw);
427
- bot.entity.pitch = conv.fromNotchianPitch(packet.pitch);
428
- });
429
-
430
- // Handle position updates from server
431
- bot._client.on("position", (packet) => {
432
- bot.entity.height = 1.8;
433
-
434
- const vel = bot.entity.velocity;
435
- const pos = bot.entity.position;
436
- let newYaw, newPitch;
437
-
438
- if (bot.registry.version[">="]("1.21.3")) {
439
- const flags = packet.flags;
440
- vel.set(flags.x ? vel.x : 0, flags.y ? vel.y : 0, flags.z ? vel.z : 0);
441
- pos.set(
442
- flags.x ? pos.x + packet.x : packet.x,
443
- flags.y ? pos.y + packet.y : packet.y,
444
- flags.z ? pos.z + packet.z : packet.z,
445
- );
446
- newYaw =
447
- (flags.yaw ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw;
448
- newPitch =
449
- (flags.pitch ? conv.toNotchianPitch(bot.entity.pitch) : 0) +
450
- packet.pitch;
451
- } else {
452
- vel.set(
453
- packet.flags & 1 ? vel.x : 0,
454
- packet.flags & 2 ? vel.y : 0,
455
- packet.flags & 4 ? vel.z : 0,
456
- );
457
- pos.set(
458
- packet.flags & 1 ? pos.x + packet.x : packet.x,
459
- packet.flags & 2 ? pos.y + packet.y : packet.y,
460
- packet.flags & 4 ? pos.z + packet.z : packet.z,
461
- );
462
- newYaw =
463
- (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) +
464
- packet.yaw;
465
- newPitch =
466
- (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) +
467
- packet.pitch;
468
- }
469
-
470
- bot.entity.yaw = conv.fromNotchianYaw(newYaw);
471
- bot.entity.pitch = conv.fromNotchianPitch(newPitch);
472
- bot.entity.onGround = false;
473
-
474
- sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround);
475
-
476
- shouldUsePhysics = true;
477
- bot.jumpTicks = 0;
478
- lastSentYaw = bot.entity.yaw;
479
- lastSentPitch = bot.entity.pitch;
480
-
481
- bot.emit("forcedMove");
482
- });
483
-
484
- bot.on("mount", () => {
485
- shouldUsePhysics = false;
486
- });
487
- bot.on("respawn", () => {
488
- shouldUsePhysics = false;
489
- });
490
-
491
- bot.on("login", () => {
492
- shouldUsePhysics = false;
493
- lastSentYaw = bot.entity.yaw;
494
- lastSentPitch = bot.entity.pitch;
495
- if (doPhysicsTimer === null) {
496
- doPhysicsTimer = setInterval(doPhysics, PHYSICS_INTERVAL_MS);
497
- }
498
- });
499
-
500
- bot.on("end", cleanup);
501
- }
3
+ module.exports = { PhysicsEngine, PlayerState };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miner-org/mineflayer-physics-reworked",
3
- "version": "0.0.3",
3
+ "version": "1.0.0",
4
4
  "scripts": {
5
5
  "npm-publish-patch": "npm version patch && npm publish --access public && git push origin main --tags",
6
6
  "npm-publish-minor": "npm version minor && npm publish --access public && git push origin main --tags",
@@ -12,7 +12,5 @@
12
12
  "author": "Ash",
13
13
  "license": "ISC",
14
14
  "description": "Mineflayer physics but with extra stuff like crawling support and scaffolding support",
15
- "dependencies": {
16
- "mineflayer": "^4.25.0"
17
- }
15
+ "dependencies": {}
18
16
  }
package/src/engine.js CHANGED
@@ -335,7 +335,8 @@ class PhysicsEngine {
335
335
  const vel = entity.vel;
336
336
  const pos = entity.pos;
337
337
  const isCrawling = entity.isCrawling;
338
- const isSneaking = entity.control.sneak && !isCrawling;
338
+ const isSneaking =
339
+ (entity.control.sneak && !isCrawling) || entity.trueSneaking;
339
340
 
340
341
  // Handle cobweb slowdown
341
342
  if (entity.isInWeb) {
@@ -874,7 +875,8 @@ class PhysicsEngine {
874
875
  const vel = entity.vel;
875
876
  const pos = entity.pos;
876
877
  const isCrawling = entity.isCrawling;
877
- const isSneaking = entity.control.sneak && !isCrawling;
878
+ const isSneaking =
879
+ (entity.control.sneak && !isCrawling) || entity.trueSneaking;
878
880
 
879
881
  const gravityMultiplier =
880
882
  vel.y <= 0 && entity.slowFalling > 0 ? this.constants.slowFalling : 1;
@@ -1156,7 +1158,8 @@ class PhysicsEngine {
1156
1158
  const vel = entity.vel;
1157
1159
  const pos = entity.pos;
1158
1160
  const isCrawling = entity.isCrawling;
1159
- const isSneaking = entity.control.sneak && !isCrawling;
1161
+ const isSneaking =
1162
+ (entity.control.sneak && !isCrawling) || entity.trueSneaking;
1160
1163
 
1161
1164
  // Swimming uses crawl hitbox (0.6 height horizontal)
1162
1165
  const isSwimming = entity.isSwimmingPose && entity.isInWater;
@@ -1290,6 +1293,7 @@ class PlayerState {
1290
1293
  // Crawling / pose state from server-confirmed metadata
1291
1294
  this.isCrawling = bot.entity.isCrawling ?? false;
1292
1295
  this.isSwimmingPose = bot.entity.isSwimmingPose ?? false;
1296
+ this.trueSneaking = bot.entity.serverSideSneaking ?? false;
1293
1297
 
1294
1298
  // Jump state
1295
1299
  this.jumpTicks = bot.jumpTicks;