prismarine-physics 1.7.0 → 1.9.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.
@@ -0,0 +1,22 @@
1
+ name: Repo Commands
2
+
3
+ on:
4
+ issue_comment: # Handle comment commands
5
+ types: [created]
6
+ pull_request: # Handle renamed PRs
7
+ types: [edited]
8
+
9
+ jobs:
10
+ comment-trigger:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Check out repository
14
+ uses: actions/checkout@v3
15
+ - name: Run command handlers
16
+ uses: PrismarineJS/prismarine-repo-actions@master
17
+ with:
18
+ # NOTE: You must specify a Personal Access Token (PAT) with repo access here. While you can use the default GITHUB_TOKEN, actions taken with it will not trigger other actions, so if you have a CI workflow, commits created by this action will not trigger it.
19
+ token: ${{ secrets.PAT_PASSWORD }}
20
+ # See `Options` section below for more info on these options
21
+ install-command: npm install
22
+ /fixlint.fix-command: npm run fix
package/HISTORY.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## History
2
2
 
3
+ ### 1.9.0
4
+ * [add 1.21 to proportionalliquidgravity setting (#117)](https://github.com/PrismarineJS/prismarine-physics/commit/4986c8e395773e770d461db06bce38f9faed0757) (thanks @Madlykeanu)
5
+
6
+ ### 1.8.0
7
+ * [Add support for elytra and rockets (#106)](https://github.com/PrismarineJS/prismarine-physics/commit/04351b4c5fa73e9eb7ca79b88c63f5bef59b5645) (thanks @lkwilson)
8
+ * [Fix "flowing" liquids not being included (#105)](https://github.com/PrismarineJS/prismarine-physics/commit/1f95aaad67684b381aedeb0a990e93dbb03025d0) (thanks @Flonja)
9
+ * [Update index.js (#104)](https://github.com/PrismarineJS/prismarine-physics/commit/dd159da044af4959f9fd8d7d97c14921e7288c1e) (thanks @Vakore)
10
+ * [Fixed airborne movement factor for sprinting (#75)](https://github.com/PrismarineJS/prismarine-physics/commit/116bb6fe3ae5066d53cedb69683a5fdb497c470d) (thanks @olie304)
11
+ * [Add command gh workflow allowing to use release command in comments (#102)](https://github.com/PrismarineJS/prismarine-physics/commit/6797491346be0ef1572aac94c78b2a1377982d54) (thanks @rom1504)
12
+ * [Fix 1.17 & 1.18 effect names (#101)](https://github.com/PrismarineJS/prismarine-physics/commit/cc7ae73d6929689645e77c7ba26bf0dcc4bc1e01) (thanks @killbinvlog)
13
+
3
14
  ### 1.7.0
4
15
 
5
16
  * 1.20
package/README.md CHANGED
@@ -39,10 +39,13 @@ const player = {
39
39
  isInWeb: false,
40
40
  isCollidedHorizontally: false,
41
41
  isCollidedVertically: false,
42
- yaw: 0
42
+ elytraFlying: false,
43
+ yaw: 0,
44
+ pitch: 0
43
45
  },
44
46
  jumpTicks: 0,
45
- jumpQueued: false
47
+ jumpQueued: false,
48
+ fireworkRocketDuration: 0
46
49
  }
47
50
  const playerState = new PlayerState(player, controls)
48
51
 
@@ -76,8 +79,10 @@ Read / Write properties:
76
79
  - isInWeb : (boolean) is the player in a web ?
77
80
  - isCollidedHorizontally : (boolean) is the player collided horizontally with a solid block ?
78
81
  - isCollidedVertically : (boolean) is the player collided vertically with a solid block ?
82
+ - elytraFlying : (boolean) is the player elytra flying ?
79
83
  - jumpTicks : (integer) number of ticks before the player can auto-jump again
80
84
  - jumpQueued : (boolean) true if the jump control state was true between the last tick and the current one
85
+ - fireworkRocketDuration : (number) how many ticks of firework boost are remaining ?
81
86
 
82
87
  Read only properties:
83
88
  - yaw : (float) the yaw angle, in radians, of the player entity
package/examples/basic.js CHANGED
@@ -22,9 +22,11 @@ function fakePlayer (pos, baseVersion) {
22
22
  isInWater: false,
23
23
  isInLava: false,
24
24
  isInWeb: false,
25
+ elytraFlying: false,
25
26
  isCollidedHorizontally: false,
26
27
  isCollidedVertically: false,
27
28
  yaw: 0,
29
+ pitch: 0,
28
30
  effects: []
29
31
  },
30
32
  inventory: {
@@ -32,6 +34,7 @@ function fakePlayer (pos, baseVersion) {
32
34
  },
33
35
  jumpTicks: 0,
34
36
  jumpQueued: false,
37
+ fireworkRocketDuration: 0,
35
38
  version: baseVersion
36
39
  }
37
40
  }
package/index.js CHANGED
@@ -30,14 +30,15 @@ function Physics (mcData, world) {
30
30
  const soulsandId = blocksByName.soul_sand.id
31
31
  const honeyblockId = blocksByName.honey_block ? blocksByName.honey_block.id : -1 // 1.15+
32
32
  const webId = blocksByName.cobweb ? blocksByName.cobweb.id : blocksByName.web.id
33
- const waterId = blocksByName.water.id
34
- const lavaId = blocksByName.lava.id
33
+ const waterIds = [blocksByName.water.id, blocksByName.flowing_water ? blocksByName.flowing_water.id : -1]
34
+ const lavaIds = [blocksByName.lava.id, blocksByName.flowing_lava ? blocksByName.flowing_lava.id : -1]
35
35
  const ladderId = blocksByName.ladder.id
36
36
  const vineId = blocksByName.vine.id
37
37
  const waterLike = new Set()
38
38
  if (blocksByName.seagrass) waterLike.add(blocksByName.seagrass.id) // 1.13+
39
39
  if (blocksByName.tall_seagrass) waterLike.add(blocksByName.tall_seagrass.id) // 1.13+
40
40
  if (blocksByName.kelp) waterLike.add(blocksByName.kelp.id) // 1.13+
41
+ if (blocksByName.kelp_plant) waterLike.add(blocksByName.kelp_plant.id) // 1.13+
41
42
  const bubblecolumnId = blocksByName.bubble_column ? blocksByName.bubble_column.id : -1 // 1.13+
42
43
  if (blocksByName.bubble_column) waterLike.add(bubblecolumnId)
43
44
 
@@ -342,6 +343,64 @@ function Physics (mcData, world) {
342
343
  }
343
344
  }
344
345
 
346
+ function getLookingVector (entity) {
347
+ // given a yaw pitch, we need the looking vector
348
+
349
+ // yaw is right handed rotation about y (up) starting from -z (north)
350
+ // pitch is -90 looking down, 90 looking up, 0 looking at horizon
351
+ // lets get its coordinate system.
352
+ // let x' = -z (north)
353
+ // let y' = -x (west)
354
+ // let z' = y (up)
355
+
356
+ // the non normalized looking vector in x', y', z' space is
357
+ // x' is cos(yaw)
358
+ // y' is sin(yaw)
359
+ // z' is tan(pitch)
360
+
361
+ // substituting back in x, y, z, we get the looking vector in the normal x, y, z space
362
+ // -z = cos(yaw) => z = -cos(yaw)
363
+ // -x = sin(yaw) => x = -sin(yaw)
364
+ // y = tan(pitch)
365
+
366
+ // normalizing the vectors, we divide each by |sqrt(x*x + y*y + z*z)|
367
+ // x*x + z*z = sin^2 + cos^2 = 1
368
+ // so |sqrt(xx+yy+zz)| = |sqrt(1+tan^2(pitch))|
369
+ // = |sqrt(1+sin^2(pitch)/cos^2(pitch))|
370
+ // = |sqrt((cos^2+sin^2)/cos^2(pitch))|
371
+ // = |sqrt(1/cos^2(pitch))|
372
+ // = |+/- 1/cos(pitch)|
373
+ // = 1/cos(pitch) since pitch in [-90, 90]
374
+
375
+ // the looking vector is therefore
376
+ // x = -sin(yaw) * cos(pitch)
377
+ // y = tan(pitch) * cos(pitch) = sin(pitch)
378
+ // z = -cos(yaw) * cos(pitch)
379
+
380
+ const yaw = entity.yaw
381
+ const pitch = entity.pitch
382
+ const sinYaw = Math.sin(yaw)
383
+ const cosYaw = Math.cos(yaw)
384
+ const sinPitch = Math.sin(pitch)
385
+ const cosPitch = Math.cos(pitch)
386
+ const lookX = -sinYaw * cosPitch
387
+ const lookY = sinPitch
388
+ const lookZ = -cosYaw * cosPitch
389
+ const lookDir = new Vec3(lookX, lookY, lookZ)
390
+ return {
391
+ yaw,
392
+ pitch,
393
+ sinYaw,
394
+ cosYaw,
395
+ sinPitch,
396
+ cosPitch,
397
+ lookX,
398
+ lookY,
399
+ lookZ,
400
+ lookDir
401
+ }
402
+ }
403
+
345
404
  function applyHeading (entity, strafe, forward, multiplier) {
346
405
  let speed = Math.sqrt(strafe * strafe + forward * forward)
347
406
  if (speed < 0.01) return new Vec3(0, 0, 0)
@@ -376,10 +435,79 @@ function Physics (mcData, world) {
376
435
 
377
436
  const gravityMultiplier = (vel.y <= 0 && entity.slowFalling > 0) ? physics.slowFalling : 1
378
437
 
379
- if (!entity.isInWater && !entity.isInLava) {
438
+ if (entity.isInWater || entity.isInLava) {
439
+ // Water / Lava movement
440
+ const lastY = pos.y
441
+ let acceleration = physics.liquidAcceleration
442
+ const inertia = entity.isInWater ? physics.waterInertia : physics.lavaInertia
443
+ let horizontalInertia = inertia
444
+
445
+ if (entity.isInWater) {
446
+ let strider = Math.min(entity.depthStrider, 3)
447
+ if (!entity.onGround) {
448
+ strider *= 0.5
449
+ }
450
+ if (strider > 0) {
451
+ horizontalInertia += (0.546 - horizontalInertia) * strider / 3
452
+ acceleration += (0.7 - acceleration) * strider / 3
453
+ }
454
+
455
+ if (entity.dolphinsGrace > 0) horizontalInertia = 0.96
456
+ }
457
+
458
+ applyHeading(entity, strafe, forward, acceleration)
459
+ moveEntity(entity, world, vel.x, vel.y, vel.z)
460
+ vel.y *= inertia
461
+ vel.y -= (entity.isInWater ? physics.waterGravity : physics.lavaGravity) * gravityMultiplier
462
+ vel.x *= horizontalInertia
463
+ vel.z *= horizontalInertia
464
+
465
+ if (entity.isCollidedHorizontally && doesNotCollide(world, pos.offset(vel.x, vel.y + 0.6 - pos.y + lastY, vel.z))) {
466
+ vel.y = physics.outOfLiquidImpulse // jump out of liquid
467
+ }
468
+ } else if (entity.elytraFlying) {
469
+ const {
470
+ pitch,
471
+ sinPitch,
472
+ cosPitch,
473
+ lookDir
474
+ } = getLookingVector(entity)
475
+ const horizontalSpeed = Math.sqrt(vel.x * vel.x + vel.z * vel.z)
476
+ const cosPitchSquared = cosPitch * cosPitch
477
+ vel.y += physics.gravity * gravityMultiplier * (-1.0 + cosPitchSquared * 0.75)
478
+ // cosPitch is in [0, 1], so cosPitch > 0.0 is just to protect against
479
+ // divide by zero errors
480
+ if (vel.y < 0.0 && cosPitch > 0.0) {
481
+ const movingDownSpeedModifier = vel.y * (-0.1) * cosPitchSquared
482
+ vel.x += lookDir.x * movingDownSpeedModifier / cosPitch
483
+ vel.y += movingDownSpeedModifier
484
+ vel.z += lookDir.z * movingDownSpeedModifier / cosPitch
485
+ }
486
+
487
+ if (pitch < 0.0 && cosPitch > 0.0) {
488
+ const lookDownSpeedModifier = horizontalSpeed * (-sinPitch) * 0.04
489
+ vel.x += -lookDir.x * lookDownSpeedModifier / cosPitch
490
+ vel.y += lookDownSpeedModifier * 3.2
491
+ vel.z += -lookDir.z * lookDownSpeedModifier / cosPitch
492
+ }
493
+
494
+ if (cosPitch > 0.0) {
495
+ vel.x += (lookDir.x / cosPitch * horizontalSpeed - vel.x) * 0.1
496
+ vel.z += (lookDir.z / cosPitch * horizontalSpeed - vel.z) * 0.1
497
+ }
498
+
499
+ vel.x *= 0.99
500
+ vel.y *= 0.98
501
+ vel.z *= 0.99
502
+ moveEntity(entity, world, vel.x, vel.y, vel.z)
503
+
504
+ if (entity.onGround) {
505
+ entity.elytraFlying = false
506
+ }
507
+ } else {
380
508
  // Normal movement
381
- let acceleration = physics.airborneAcceleration
382
- let inertia = physics.airborneInertia
509
+ let acceleration = 0.0
510
+ let inertia = 0.0
383
511
  const blockUnder = world.getBlock(pos.offset(0, -1, 0))
384
512
  if (entity.onGround && blockUnder) {
385
513
  let playerSpeedAttribute
@@ -407,6 +535,14 @@ function Physics (mcData, world) {
407
535
  inertia = (blockSlipperiness[blockUnder.type] || physics.defaultSlipperiness) * 0.91
408
536
  acceleration = attributeSpeed * (0.1627714 / (inertia * inertia * inertia))
409
537
  if (acceleration < 0) acceleration = 0 // acceleration should not be negative
538
+ } else {
539
+ acceleration = physics.airborneAcceleration
540
+ inertia = physics.airborneInertia
541
+
542
+ if (entity.control.sprint) {
543
+ const airSprintFactor = physics.airborneAcceleration * 0.3
544
+ acceleration += airSprintFactor
545
+ }
410
546
  }
411
547
 
412
548
  applyHeading(entity, strafe, forward, acceleration)
@@ -433,46 +569,16 @@ function Physics (mcData, world) {
433
569
  vel.y *= physics.airdrag
434
570
  vel.x *= inertia
435
571
  vel.z *= inertia
436
- } else {
437
- // Water / Lava movement
438
- const lastY = pos.y
439
- let acceleration = physics.liquidAcceleration
440
- const inertia = entity.isInWater ? physics.waterInertia : physics.lavaInertia
441
- let horizontalInertia = inertia
442
-
443
- if (entity.isInWater) {
444
- let strider = Math.min(entity.depthStrider, 3)
445
- if (!entity.onGround) {
446
- strider *= 0.5
447
- }
448
- if (strider > 0) {
449
- horizontalInertia += (0.546 - horizontalInertia) * strider / 3
450
- acceleration += (0.7 - acceleration) * strider / 3
451
- }
452
-
453
- if (entity.dolphinsGrace > 0) horizontalInertia = 0.96
454
- }
455
-
456
- applyHeading(entity, strafe, forward, acceleration)
457
- moveEntity(entity, world, vel.x, vel.y, vel.z)
458
- vel.y *= inertia
459
- vel.y -= (entity.isInWater ? physics.waterGravity : physics.lavaGravity) * gravityMultiplier
460
- vel.x *= horizontalInertia
461
- vel.z *= horizontalInertia
462
-
463
- if (entity.isCollidedHorizontally && doesNotCollide(world, pos.offset(vel.x, vel.y + 0.6 - pos.y + lastY, vel.z))) {
464
- vel.y = physics.outOfLiquidImpulse // jump out of liquid
465
- }
466
572
  }
467
573
  }
468
574
 
469
- function isMaterialInBB (world, queryBB, type) {
575
+ function isMaterialInBB (world, queryBB, types) {
470
576
  const cursor = new Vec3(0, 0, 0)
471
577
  for (cursor.y = Math.floor(queryBB.minY); cursor.y <= Math.floor(queryBB.maxY); cursor.y++) {
472
578
  for (cursor.z = Math.floor(queryBB.minZ); cursor.z <= Math.floor(queryBB.maxZ); cursor.z++) {
473
579
  for (cursor.x = Math.floor(queryBB.minX); cursor.x <= Math.floor(queryBB.maxX); cursor.x++) {
474
580
  const block = world.getBlock(cursor)
475
- if (block && block.type === type) return true
581
+ if (block && types.includes(block.type)) return true
476
582
  }
477
583
  }
478
584
  }
@@ -487,7 +593,7 @@ function Physics (mcData, world) {
487
593
  if (!block) return -1
488
594
  if (waterLike.has(block.type)) return 0
489
595
  if (block.getProperties().waterlogged) return 0
490
- if (block.type !== waterId) return -1
596
+ if (!waterIds.includes(block.type)) return -1
491
597
  const meta = block.metadata
492
598
  return meta >= 8 ? 0 : meta
493
599
  }
@@ -534,7 +640,7 @@ function Physics (mcData, world) {
534
640
  for (cursor.z = Math.floor(bb.minZ); cursor.z <= Math.floor(bb.maxZ); cursor.z++) {
535
641
  for (cursor.x = Math.floor(bb.minX); cursor.x <= Math.floor(bb.maxX); cursor.x++) {
536
642
  const block = world.getBlock(cursor)
537
- if (block && (block.type === waterId || waterLike.has(block.type) || block.getProperties().waterlogged)) {
643
+ if (block && (waterIds.includes(block.type) || waterLike.has(block.type) || block.getProperties().waterlogged)) {
538
644
  const waterLevel = cursor.y + 1 - getLiquidHeightPcent(block)
539
645
  if (Math.ceil(bb.maxY) >= waterLevel) waterBlocks.push(block)
540
646
  }
@@ -570,7 +676,7 @@ function Physics (mcData, world) {
570
676
  const lavaBB = getPlayerBB(pos).contract(0.1, 0.4, 0.1)
571
677
 
572
678
  entity.isInWater = isInWaterApplyCurrent(world, waterBB, vel)
573
- entity.isInLava = isMaterialInBB(world, lavaBB, lavaId)
679
+ entity.isInLava = isMaterialInBB(world, lavaBB, lavaIds)
574
680
 
575
681
  // Reset velocity component if it falls under the threshold
576
682
  if (Math.abs(vel.x) < physics.negligeableVelocity) vel.x = 0
@@ -608,6 +714,20 @@ function Physics (mcData, world) {
608
714
  forward *= physics.sneakSpeed
609
715
  }
610
716
 
717
+ entity.elytraFlying = entity.elytraFlying && entity.elytraEquipped && !entity.onGround && !entity.levitation
718
+
719
+ if (entity.fireworkRocketDuration > 0) {
720
+ if (!entity.elytraFlying) {
721
+ entity.fireworkRocketDuration = 0
722
+ } else {
723
+ const { lookDir } = getLookingVector(entity)
724
+ vel.x += lookDir.x * 0.1 + (lookDir.x * 1.5 - vel.x) * 0.5
725
+ vel.y += lookDir.y * 0.1 + (lookDir.y * 1.5 - vel.y) * 0.5
726
+ vel.z += lookDir.z * 0.1 + (lookDir.z * 1.5 - vel.z) * 0.5
727
+ --entity.fireworkRocketDuration
728
+ }
729
+ }
730
+
611
731
  moveEntityWithHeading(entity, world, strafe, forward)
612
732
 
613
733
  return entity
@@ -646,33 +766,10 @@ function getEnchantmentLevel (mcData, enchantmentName, enchantments) {
646
766
  return 0
647
767
  }
648
768
 
649
- function getStatusEffectNamesForVersion (supportFeature) {
650
- if (supportFeature('effectNamesAreRegistryNames')) {
651
- return {
652
- jumpBoostEffectName: 'jump_boost',
653
- speedEffectName: 'speed',
654
- slownessEffectName: 'slowness',
655
- dolphinsGraceEffectName: 'dolphins_grace',
656
- slowFallingEffectName: 'slow_falling',
657
- levitationEffectName: 'levitation'
658
- }
659
- } else {
660
- return {
661
- jumpBoostEffectName: 'JumpBoost',
662
- speedEffectName: 'Speed',
663
- slownessEffectName: 'Slowness',
664
- dolphinsGraceEffectName: 'DolphinsGrace',
665
- slowFallingEffectName: 'SlowFalling',
666
- levitationEffectName: 'Levitation'
667
- }
668
- }
669
- }
670
-
671
769
  class PlayerState {
672
770
  constructor (bot, control) {
673
771
  const mcData = require('minecraft-data')(bot.version)
674
772
  const nbt = require('prismarine-nbt')
675
- const supportFeature = makeSupportFeature(mcData)
676
773
 
677
774
  // Input / Outputs
678
775
  this.pos = bot.entity.position.clone()
@@ -683,8 +780,10 @@ class PlayerState {
683
780
  this.isInWeb = bot.entity.isInWeb
684
781
  this.isCollidedHorizontally = bot.entity.isCollidedHorizontally
685
782
  this.isCollidedVertically = bot.entity.isCollidedVertically
783
+ this.elytraFlying = bot.entity.elytraFlying
686
784
  this.jumpTicks = bot.jumpTicks
687
785
  this.jumpQueued = bot.jumpQueued
786
+ this.fireworkRocketDuration = bot.fireworkRocketDuration
688
787
 
689
788
  // Input only (not modified)
690
789
  this.attributes = bot.entity.attributes
@@ -694,15 +793,14 @@ class PlayerState {
694
793
 
695
794
  // effects
696
795
  const effects = bot.entity.effects
697
- const statusEffectNames = getStatusEffectNamesForVersion(supportFeature)
698
796
 
699
- this.jumpBoost = getEffectLevel(mcData, statusEffectNames.jumpBoostEffectName, effects)
700
- this.speed = getEffectLevel(mcData, statusEffectNames.speedEffectName, effects)
701
- this.slowness = getEffectLevel(mcData, statusEffectNames.slownessEffectName, effects)
797
+ this.jumpBoost = getEffectLevel(mcData, 'JumpBoost', effects)
798
+ this.speed = getEffectLevel(mcData, 'Speed', effects)
799
+ this.slowness = getEffectLevel(mcData, 'Slowness', effects)
702
800
 
703
- this.dolphinsGrace = getEffectLevel(mcData, statusEffectNames.dolphinsGraceEffectName, effects)
704
- this.slowFalling = getEffectLevel(mcData, statusEffectNames.slowFallingEffectName, effects)
705
- this.levitation = getEffectLevel(mcData, statusEffectNames.levitationEffectName, effects)
801
+ this.dolphinsGrace = getEffectLevel(mcData, 'DolphinsGrace', effects)
802
+ this.slowFalling = getEffectLevel(mcData, 'SlowFalling', effects)
803
+ this.levitation = getEffectLevel(mcData, 'Levitation', effects)
706
804
 
707
805
  // armour enchantments
708
806
  const boots = bot.inventory.slots[8]
@@ -713,6 +811,10 @@ class PlayerState {
713
811
  } else {
714
812
  this.depthStrider = 0
715
813
  }
814
+
815
+ // extra elytra requirements
816
+ const item = bot.inventory.slots[6]
817
+ this.elytraEquipped = item != null && item.name === 'elytra'
716
818
  }
717
819
 
718
820
  apply (bot) {
@@ -724,8 +826,10 @@ class PlayerState {
724
826
  bot.entity.isInWeb = this.isInWeb
725
827
  bot.entity.isCollidedHorizontally = this.isCollidedHorizontally
726
828
  bot.entity.isCollidedVertically = this.isCollidedVertically
829
+ bot.entity.elytraFlying = this.elytraFlying
727
830
  bot.jumpTicks = this.jumpTicks
728
831
  bot.jumpQueued = this.jumpQueued
832
+ bot.fireworkRocketDuration = this.fireworkRocketDuration
729
833
  }
730
834
  }
731
835
 
package/lib/features.json CHANGED
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "name": "proportionalLiquidGravity",
9
9
  "description": "Liquid gravity is a proportion of normal gravity",
10
- "versions": ["1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20"]
10
+ "versions": ["1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20", "1.21"]
11
11
  },
12
12
  {
13
13
  "name": "velocityBlocksOnCollision",
@@ -23,10 +23,5 @@
23
23
  "name": "climbUsingJump",
24
24
  "description": "Entity can climb ladders and vines by pressing jump",
25
25
  "versions": ["1.14", "1.15", "1.17", "1.18"]
26
- },
27
- {
28
- "name": "effectNamesAreRegistryNames",
29
- "description": "Status effect names equal to their registry names",
30
- "versions": ["1.17", "1.18"]
31
26
  }
32
27
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prismarine-physics",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Provide the physics engine for minecraft entities",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -27,11 +27,14 @@ function fakePlayer (pos) {
27
27
  isInWeb: false,
28
28
  isCollidedHorizontally: false,
29
29
  isCollidedVertically: false,
30
+ elytraFlying: false,
30
31
  yaw: 0,
32
+ pitch: 0,
31
33
  effects: {}
32
34
  },
33
35
  jumpTicks: 0,
34
36
  jumpQueued: false,
37
+ fireworkRocketDuration: 0,
35
38
  version: '1.13.2',
36
39
  inventory: {
37
40
  slots: []
@@ -0,0 +1,692 @@
1
+ /* eslint-env mocha */
2
+
3
+ const { Physics, PlayerState } = require('prismarine-physics')
4
+ const { Vec3 } = require('vec3')
5
+ const expect = require('expect')
6
+
7
+ const mcData = require('minecraft-data')('1.13.2')
8
+ const Block = require('prismarine-block')('1.13.2')
9
+
10
+ const fakeWorld = {
11
+ getBlock: (pos) => {
12
+ const type = (pos.y < 60) ? mcData.blocksByName.stone.id : mcData.blocksByName.air.id
13
+ const b = new Block(type, 0, 0)
14
+ b.position = pos
15
+ return b
16
+ }
17
+ }
18
+
19
+ const fakeWallWorld = {
20
+ getBlock: (pos) => {
21
+ const type = (pos.y < 60 || pos.x > 50) ? mcData.blocksByName.stone.id : mcData.blocksByName.air.id
22
+ const b = new Block(type, 0, 0)
23
+ b.position = pos
24
+ return b
25
+ }
26
+ }
27
+
28
+ const fakeWebWorld = {
29
+ getBlock: (pos) => {
30
+ const type = (pos.y < 60) ? mcData.blocksByName.stone.id : mcData.blocksByName.cobweb.id
31
+ const b = new Block(type, 0, 0)
32
+ b.position = pos
33
+ return b
34
+ }
35
+ }
36
+
37
+ const waterWorld = {
38
+ getBlock: (pos) => {
39
+ const type = (pos.y < 60) ? mcData.blocksByName.water.id : mcData.blocksByName.air.id
40
+ const b = new Block(type, 0, 0)
41
+ b.position = pos
42
+ return b
43
+ }
44
+ }
45
+
46
+ const lavaWorld = {
47
+ getBlock: (pos) => {
48
+ const type = (pos.y < 60) ? mcData.blocksByName.lava.id : mcData.blocksByName.air.id
49
+ const b = new Block(type, 0, 0)
50
+ b.position = pos
51
+ return b
52
+ }
53
+ }
54
+
55
+ function fakePlayer (pos) {
56
+ return {
57
+ entity: {
58
+ position: pos,
59
+ velocity: new Vec3(0, 0, 0),
60
+ onGround: false,
61
+ isInWater: false,
62
+ isInLava: false,
63
+ isInWeb: false,
64
+ isCollidedHorizontally: false,
65
+ isCollidedVertically: false,
66
+ elytraFlying: false,
67
+ yaw: Math.PI * 3 / 2, // east (+x)
68
+ pitch: 20 * Math.PI / 180,
69
+ effects: {}
70
+ },
71
+ jumpTicks: 0,
72
+ jumpQueued: false,
73
+ fireworkRocketDuration: 0,
74
+ version: '1.13.2',
75
+ inventory: {
76
+ slots: []
77
+ }
78
+ }
79
+ }
80
+
81
+ function untilIdle (player, physics, world, controls) {
82
+ const playerState = new PlayerState(player, controls)
83
+ while (!playerState.onGround || playerState.vel.x !== 0 || playerState.vel.z !== 0) {
84
+ physics.simulatePlayer(playerState, world)
85
+ }
86
+ playerState.apply(player)
87
+ }
88
+
89
+ function passTicks (ticks, player, physics, world, controls) {
90
+ const playerState = new PlayerState(player, controls)
91
+ for (let i = 0; i < ticks; ++i) {
92
+ physics.simulatePlayer(playerState, world)
93
+ }
94
+ playerState.apply(player)
95
+ }
96
+
97
+ function mpsToTps (mps) {
98
+ // 20 ticks per second
99
+ return mps / 20
100
+ }
101
+
102
+ describe('Elytra tests', () => {
103
+ it('flies east and then back', () => {
104
+ const physics = Physics(mcData, fakeWorld)
105
+ const controls = {
106
+ forward: false,
107
+ back: false,
108
+ left: false,
109
+ right: false,
110
+ jump: false,
111
+ sprint: false,
112
+ sneak: false
113
+ }
114
+ const player = fakePlayer(new Vec3(0, 61, 0))
115
+ player.inventory.slots[6] = { name: 'elytra' }
116
+
117
+ // wait til on ground
118
+ untilIdle(player, physics, fakeWorld, controls)
119
+
120
+ expect(player.entity.position).toEqual(new Vec3(0, 60, 0))
121
+
122
+ // jump
123
+ player.jumpQueued = true
124
+ passTicks(10, player, physics, fakeWorld, controls)
125
+ expect(player.entity.onGround).toBeFalsy()
126
+
127
+ // fly
128
+ player.entity.elytraFlying = true
129
+ passTicks(1, player, physics, fakeWorld, controls)
130
+
131
+ // boost
132
+ player.fireworkRocketDuration = 20
133
+
134
+ // wait til on ground
135
+ untilIdle(player, physics, fakeWorld, controls)
136
+
137
+ expect(player.entity.elytraFlying).toBeFalsy()
138
+ expect(player.fireworkRocketDuration).toEqual(0)
139
+
140
+ expect(player.entity.position.x).toBeGreaterThan(140)
141
+ expect(player.entity.position.y).toEqual(60)
142
+ expect(player.entity.position.z).toBeCloseTo(0)
143
+
144
+ // turn around
145
+ player.entity.yaw = Math.PI / 2 // west (-x)
146
+
147
+ // jump
148
+ player.jumpQueued = true
149
+ passTicks(10, player, physics, fakeWorld, controls)
150
+
151
+ expect(player.entity.onGround).toBeFalsy()
152
+
153
+ // fly
154
+ player.entity.elytraFlying = true
155
+ passTicks(1, player, physics, fakeWorld, controls)
156
+ expect(player.entity.elytraFlying).toBeTruthy()
157
+
158
+ // boost
159
+ player.fireworkRocketDuration = 20
160
+
161
+ // wait til on ground
162
+ untilIdle(player, physics, fakeWorld, controls)
163
+
164
+ expect(player.entity.elytraFlying).toBeFalsy()
165
+ expect(player.fireworkRocketDuration).toEqual(0)
166
+
167
+ // should be back at start
168
+ expect(player.entity.position.x).toBeCloseTo(0)
169
+ expect(player.entity.position.y).toEqual(60)
170
+ expect(player.entity.position.z).toBeCloseTo(0)
171
+ })
172
+
173
+ it('flies south and then back', () => {
174
+ const physics = Physics(mcData, fakeWorld)
175
+ const controls = {
176
+ forward: false,
177
+ back: false,
178
+ left: false,
179
+ right: false,
180
+ jump: false,
181
+ sprint: false,
182
+ sneak: false
183
+ }
184
+ const player = fakePlayer(new Vec3(0, 61, 0))
185
+ player.inventory.slots[6] = { name: 'elytra' }
186
+ player.entity.yaw = 0 // north (-z)
187
+
188
+ // wait til on ground
189
+ untilIdle(player, physics, fakeWorld, controls)
190
+
191
+ expect(player.entity.position).toEqual(new Vec3(0, 60, 0))
192
+
193
+ // jump
194
+ player.jumpQueued = true
195
+ passTicks(10, player, physics, fakeWorld, controls)
196
+ expect(player.entity.onGround).toBeFalsy()
197
+
198
+ // fly
199
+ player.entity.elytraFlying = true
200
+ passTicks(1, player, physics, fakeWorld, controls)
201
+
202
+ // boost
203
+ player.fireworkRocketDuration = 20
204
+
205
+ // wait til on ground
206
+ untilIdle(player, physics, fakeWorld, controls)
207
+
208
+ expect(player.entity.elytraFlying).toBeFalsy()
209
+ expect(player.fireworkRocketDuration).toEqual(0)
210
+
211
+ expect(player.entity.position.x).toBeCloseTo(0)
212
+ expect(player.entity.position.y).toEqual(60)
213
+ expect(player.entity.position.z).toBeLessThan(-140)
214
+
215
+ // turn around
216
+ player.entity.yaw = Math.PI // south (z)
217
+
218
+ // jump
219
+ player.jumpQueued = true
220
+ passTicks(10, player, physics, fakeWorld, controls)
221
+
222
+ expect(player.entity.onGround).toBeFalsy()
223
+
224
+ // fly
225
+ player.entity.elytraFlying = true
226
+ passTicks(1, player, physics, fakeWorld, controls)
227
+ expect(player.entity.elytraFlying).toBeTruthy()
228
+
229
+ // boost
230
+ player.fireworkRocketDuration = 20
231
+
232
+ // wait til on ground
233
+ untilIdle(player, physics, fakeWorld, controls)
234
+
235
+ expect(player.entity.elytraFlying).toBeFalsy()
236
+ expect(player.fireworkRocketDuration).toEqual(0)
237
+
238
+ // should be back at start
239
+ expect(player.entity.position.x).toBeCloseTo(0)
240
+ expect(player.entity.position.y).toEqual(60)
241
+ expect(player.entity.position.z).toBeCloseTo(0)
242
+ })
243
+
244
+ it('flies north east and then back', () => {
245
+ const physics = Physics(mcData, fakeWorld)
246
+ const controls = {
247
+ forward: false,
248
+ back: false,
249
+ left: false,
250
+ right: false,
251
+ jump: false,
252
+ sprint: false,
253
+ sneak: false
254
+ }
255
+ const player = fakePlayer(new Vec3(0, 61, 0))
256
+ player.inventory.slots[6] = { name: 'elytra' }
257
+ // facing positive z, to the left is positive x
258
+ player.entity.yaw = -Math.PI / 4 + 2 * Math.PI // right 45 degrees of north (-z), towards east (+x)
259
+
260
+ // wait til on ground
261
+ untilIdle(player, physics, fakeWorld, controls)
262
+
263
+ expect(player.entity.position).toEqual(new Vec3(0, 60, 0))
264
+
265
+ // jump
266
+ player.jumpQueued = true
267
+ passTicks(10, player, physics, fakeWorld, controls)
268
+ expect(player.entity.onGround).toBeFalsy()
269
+
270
+ // fly
271
+ player.entity.elytraFlying = true
272
+ passTicks(1, player, physics, fakeWorld, controls)
273
+
274
+ // boost
275
+ player.fireworkRocketDuration = 20
276
+
277
+ // wait til on ground
278
+ untilIdle(player, physics, fakeWorld, controls)
279
+
280
+ expect(player.entity.elytraFlying).toBeFalsy()
281
+ expect(player.fireworkRocketDuration).toEqual(0)
282
+
283
+ expect(player.entity.position.x).toBeGreaterThan(140 * Math.sin(Math.PI / 4))
284
+ expect(player.entity.position.y).toEqual(60)
285
+ expect(player.entity.position.z).toBeLessThan(-140 * Math.sin(Math.PI / 4))
286
+
287
+ // turn around
288
+ player.entity.yaw = -Math.PI / 4 + Math.PI // south (z)
289
+
290
+ // jump
291
+ player.jumpQueued = true
292
+ passTicks(10, player, physics, fakeWorld, controls)
293
+
294
+ expect(player.entity.onGround).toBeFalsy()
295
+
296
+ // fly
297
+ player.entity.elytraFlying = true
298
+ passTicks(1, player, physics, fakeWorld, controls)
299
+ expect(player.entity.elytraFlying).toBeTruthy()
300
+
301
+ // boost
302
+ player.fireworkRocketDuration = 20
303
+
304
+ // wait til on ground
305
+ untilIdle(player, physics, fakeWorld, controls)
306
+
307
+ expect(player.entity.elytraFlying).toBeFalsy()
308
+ expect(player.fireworkRocketDuration).toEqual(0)
309
+
310
+ // should be back at start
311
+ expect(player.entity.position.x).toBeCloseTo(0)
312
+ expect(player.entity.position.y).toEqual(60)
313
+ expect(player.entity.position.z).toBeCloseTo(0)
314
+ })
315
+
316
+ it('stops flight without elytra', () => {
317
+ const physics = Physics(mcData, fakeWorld)
318
+ const controls = {
319
+ forward: false,
320
+ back: false,
321
+ left: false,
322
+ right: false,
323
+ jump: false,
324
+ sprint: false,
325
+ sneak: false
326
+ }
327
+ const player = fakePlayer(new Vec3(0, 80, 0))
328
+ player.inventory.slots[6] = { name: 'elytra' }
329
+
330
+ player.entity.elytraFlying = true
331
+ player.fireworkRocketDuration = 20
332
+ physics.simulatePlayer(new PlayerState(player, controls), fakeWorld).apply(player)
333
+ expect(player.entity.elytraFlying).toBeTruthy()
334
+ expect(player.fireworkRocketDuration).toEqual(19)
335
+ player.inventory.slots[6] = undefined
336
+ physics.simulatePlayer(new PlayerState(player, controls), fakeWorld).apply(player)
337
+ expect(player.entity.elytraFlying).toBeFalsy()
338
+ expect(player.fireworkRocketDuration).toEqual(0)
339
+ })
340
+
341
+ it('can fly out of water', () => {
342
+ const physics = Physics(mcData, waterWorld)
343
+ const controls = {
344
+ forward: false,
345
+ back: false,
346
+ left: false,
347
+ right: false,
348
+ jump: false,
349
+ sprint: false,
350
+ sneak: false
351
+ }
352
+ const player = fakePlayer(new Vec3(0, 40, 0))
353
+ player.inventory.slots[6] = { name: 'elytra' }
354
+ player.entity.pitch = 80 * Math.PI / 180
355
+
356
+ player.entity.elytraFlying = true
357
+ physics.simulatePlayer(new PlayerState(player, controls), waterWorld).apply(player)
358
+ expect(player.entity.isInWater).toBeTruthy()
359
+ expect(player.entity.elytraFlying).toBeTruthy()
360
+ physics.simulatePlayer(new PlayerState(player, controls), waterWorld).apply(player)
361
+ expect(player.entity.isInWater).toBeTruthy()
362
+ expect(player.entity.elytraFlying).toBeTruthy()
363
+
364
+ player.fireworkRocketDuration = 20
365
+ physics.simulatePlayer(new PlayerState(player, controls), waterWorld).apply(player)
366
+ expect(player.entity.elytraFlying).toBeTruthy()
367
+ expect(player.fireworkRocketDuration).toEqual(19)
368
+
369
+ const playerState = new PlayerState(player, controls)
370
+ while (playerState.isInWater) {
371
+ playerState.fireworkRocketDuration = 1
372
+ physics.simulatePlayer(playerState, waterWorld)
373
+ }
374
+ playerState.apply(player)
375
+ expect(player.fireworkRocketDuration).toEqual(0)
376
+ expect(player.entity.position.y).toBeGreaterThan(59)
377
+ })
378
+
379
+ it('can fly out of lava', () => {
380
+ const physics = Physics(mcData, lavaWorld)
381
+ const controls = {
382
+ forward: false,
383
+ back: false,
384
+ left: false,
385
+ right: false,
386
+ jump: false,
387
+ sprint: false,
388
+ sneak: false
389
+ }
390
+ const player = fakePlayer(new Vec3(0, 40, 0))
391
+ player.inventory.slots[6] = { name: 'elytra' }
392
+ player.entity.pitch = 80 * Math.PI / 180
393
+
394
+ player.entity.elytraFlying = true
395
+ physics.simulatePlayer(new PlayerState(player, controls), lavaWorld).apply(player)
396
+ expect(player.entity.isInLava).toBeTruthy()
397
+ expect(player.entity.elytraFlying).toBeTruthy()
398
+ physics.simulatePlayer(new PlayerState(player, controls), lavaWorld).apply(player)
399
+ expect(player.entity.isInLava).toBeTruthy()
400
+ expect(player.entity.elytraFlying).toBeTruthy()
401
+
402
+ player.fireworkRocketDuration = 20
403
+ physics.simulatePlayer(new PlayerState(player, controls), lavaWorld).apply(player)
404
+ expect(player.entity.elytraFlying).toBeTruthy()
405
+ expect(player.fireworkRocketDuration).toEqual(19)
406
+
407
+ const playerState = new PlayerState(player, controls)
408
+ while (playerState.isInLava) {
409
+ playerState.fireworkRocketDuration = 1
410
+ physics.simulatePlayer(playerState, lavaWorld)
411
+ }
412
+ playerState.apply(player)
413
+ expect(player.fireworkRocketDuration).toEqual(0)
414
+ expect(player.entity.position.y).toBeGreaterThan(59)
415
+ })
416
+
417
+ it('landing while jumping stops flight', () => {
418
+ const physics = Physics(mcData, fakeWorld)
419
+ const controls = {
420
+ forward: false,
421
+ back: false,
422
+ left: false,
423
+ right: false,
424
+ jump: false,
425
+ sprint: false,
426
+ sneak: false
427
+ }
428
+ const player = fakePlayer(new Vec3(0, 61, 0))
429
+ player.inventory.slots[6] = { name: 'elytra' }
430
+
431
+ // wait til on ground
432
+ untilIdle(player, physics, fakeWorld, controls)
433
+ expect(player.entity.position).toEqual(new Vec3(0, 60, 0))
434
+
435
+ // jump
436
+ player.jumpQueued = true
437
+ passTicks(10, player, physics, fakeWorld, controls)
438
+ expect(player.entity.onGround).toBeFalsy()
439
+
440
+ // fly
441
+ player.entity.elytraFlying = true
442
+ passTicks(1, player, physics, fakeWorld, controls)
443
+
444
+ // boost
445
+ player.fireworkRocketDuration = 20
446
+ passTicks(10, player, physics, fakeWorld, controls)
447
+ expect(player.entity.onGround).toBeFalsy()
448
+
449
+ controls.jump = true
450
+ const playerState = new PlayerState(player, controls)
451
+ while (!playerState.onGround) {
452
+ expect(playerState.elytraFlying).toBeTruthy()
453
+ physics.simulatePlayer(playerState, fakeWorld)
454
+ }
455
+ playerState.apply(player)
456
+ expect(player.entity.onGround).toBeTruthy()
457
+ expect(player.entity.elytraFlying).toBeFalsy()
458
+
459
+ // let jump controls cause a jump
460
+ passTicks(10, player, physics, fakeWorld, controls)
461
+ expect(player.entity.onGround).toBeFalsy()
462
+ expect(player.entity.elytraFlying).toBeFalsy()
463
+ })
464
+
465
+ it('cant fly in webs', () => {
466
+ const webPhysics = Physics(mcData, fakeWebWorld)
467
+ const airPhysics = Physics(mcData, fakeWorld)
468
+ const controls = {
469
+ forward: false,
470
+ back: false,
471
+ left: false,
472
+ right: false,
473
+ jump: false,
474
+ sprint: false,
475
+ sneak: false
476
+ }
477
+ const airPlayer = fakePlayer(new Vec3(0, 150, 0))
478
+ airPlayer.inventory.slots[6] = { name: 'elytra' }
479
+ airPlayer.entity.elytraFlying = true
480
+ passTicks(20 * 10, airPlayer, airPhysics, fakeWorld, controls)
481
+
482
+ const webPlayer = fakePlayer(new Vec3(0, 150, 0))
483
+ webPlayer.inventory.slots[6] = { name: 'elytra' }
484
+ webPlayer.entity.elytraFlying = true
485
+ passTicks(20 * 10, webPlayer, webPhysics, fakeWebWorld, controls)
486
+
487
+ // web player should have barely moved
488
+ expect(webPlayer.entity.position.x).toBeGreaterThan(0)
489
+ expect(webPlayer.entity.position.x).toBeLessThan(1)
490
+ expect(webPlayer.entity.position.y).toBeGreaterThan(149)
491
+
492
+ // elytra player should be far away and much lower
493
+ expect(airPlayer.entity.position.x).toBeGreaterThan(200)
494
+ expect(airPlayer.entity.position.y).toBeLessThan(110)
495
+ })
496
+
497
+ it('can hit a wall', () => {
498
+ const physics = Physics(mcData, fakeWallWorld)
499
+ const controls = {
500
+ forward: false,
501
+ back: false,
502
+ left: false,
503
+ right: false,
504
+ jump: false,
505
+ sprint: false,
506
+ sneak: false
507
+ }
508
+ const player = fakePlayer(new Vec3(0, 100, 0))
509
+ player.inventory.slots[6] = { name: 'elytra' }
510
+ player.entity.elytraFlying = true
511
+ player.fireworkRocketDuration = 100
512
+
513
+ // let bot accelerate from firework
514
+ passTicks(10, player, physics, fakeWallWorld, controls)
515
+
516
+ // should be moving 30 m/s (30 / 20 m/tick)
517
+ expect(player.entity.velocity.x).toBeGreaterThan(mpsToTps(30))
518
+
519
+ // until near wall
520
+ const playerState = new PlayerState(player, controls)
521
+ while (playerState.pos.x < 49) {
522
+ physics.simulatePlayer(playerState, fakeWallWorld)
523
+ }
524
+ playerState.apply(player)
525
+
526
+ // should still be moving fast
527
+ expect(player.entity.velocity.x).toBeGreaterThan(mpsToTps(30))
528
+
529
+ // 1 / (30/20) = 2/3, so 1 tick should be enough to hit the wall
530
+ passTicks(1, player, physics, fakeWallWorld, controls)
531
+
532
+ // it should have stopped in the x direction
533
+ expect(player.entity.velocity.x).toBeCloseTo(0)
534
+ })
535
+
536
+ it('can flight straight up', () => {
537
+ const physics = Physics(mcData, fakeWallWorld)
538
+ const controls = {
539
+ forward: false,
540
+ back: false,
541
+ left: false,
542
+ right: false,
543
+ jump: false,
544
+ sprint: false,
545
+ sneak: false
546
+ }
547
+ const player = fakePlayer(new Vec3(0, 100, 0))
548
+ player.inventory.slots[6] = { name: 'elytra' }
549
+ player.entity.elytraFlying = true
550
+ player.entity.pitch = Math.PI / 2
551
+ player.fireworkRocketDuration = 20
552
+
553
+ // let bot accelerate from firework
554
+ passTicks(20, player, physics, fakeWallWorld, controls)
555
+
556
+ // should be moving very fast
557
+ expect(player.entity.velocity.y).toBeGreaterThan(mpsToTps(25))
558
+ expect(player.entity.position.x).toBeCloseTo(0)
559
+ expect(player.entity.position.z).toBeCloseTo(0)
560
+
561
+ passTicks(20, player, physics, fakeWallWorld, controls)
562
+ expect(player.entity.velocity.y).toBeLessThan(mpsToTps(10))
563
+ expect(player.entity.position.x).toBeCloseTo(0)
564
+ expect(player.entity.position.z).toBeCloseTo(0)
565
+
566
+ passTicks(20, player, physics, fakeWallWorld, controls)
567
+ expect(player.entity.velocity.y).toBeLessThan(0)
568
+ expect(player.entity.position.x).toBeCloseTo(0)
569
+ expect(player.entity.position.z).toBeCloseTo(0)
570
+
571
+ passTicks(20, player, physics, fakeWallWorld, controls)
572
+ expect(player.entity.velocity.y).toBeLessThan(mpsToTps(20))
573
+ expect(player.entity.position.x).toBeCloseTo(0)
574
+ expect(player.entity.position.z).toBeCloseTo(0)
575
+ })
576
+
577
+ it('can flight straight down', () => {
578
+ const physics = Physics(mcData, fakeWallWorld)
579
+ const controls = {
580
+ forward: false,
581
+ back: false,
582
+ left: false,
583
+ right: false,
584
+ jump: false,
585
+ sprint: false,
586
+ sneak: false
587
+ }
588
+ const player = fakePlayer(new Vec3(0, 1000, 0))
589
+ player.inventory.slots[6] = { name: 'elytra' }
590
+ player.entity.elytraFlying = true
591
+ player.entity.pitch = -Math.PI / 2
592
+ player.fireworkRocketDuration = 20
593
+
594
+ // let bot accelerate from firework
595
+ passTicks(200, player, physics, fakeWallWorld, controls)
596
+
597
+ // should be moving very fast
598
+ expect(player.entity.position.y).toBeGreaterThan(100)
599
+ expect(player.entity.velocity.y).toBeLessThan(mpsToTps(-60))
600
+ expect(player.entity.position.x).toBeCloseTo(0)
601
+ expect(player.entity.position.z).toBeCloseTo(0)
602
+
603
+ // using firework actually slows the bot down
604
+ player.fireworkRocketDuration = 20
605
+ passTicks(10, player, physics, fakeWallWorld, controls)
606
+
607
+ // should be slower now
608
+ expect(player.entity.position.y).toBeGreaterThan(100)
609
+ expect(player.entity.velocity.y).toBeGreaterThan(mpsToTps(-40))
610
+ expect(player.entity.position.x).toBeCloseTo(0)
611
+ expect(player.entity.position.z).toBeCloseTo(0)
612
+ })
613
+
614
+ it('flies to point and then back', () => {
615
+ const physics = Physics(mcData, fakeWorld)
616
+ const controls = {
617
+ forward: false,
618
+ back: false,
619
+ left: false,
620
+ right: false,
621
+ jump: false,
622
+ sprint: false,
623
+ sneak: false
624
+ }
625
+ const player = fakePlayer(new Vec3(0, 61, 0))
626
+ player.inventory.slots[6] = { name: 'elytra' }
627
+
628
+ // wait til on ground
629
+ untilIdle(player, physics, fakeWorld, controls)
630
+
631
+ expect(player.entity.position).toEqual(new Vec3(0, 60, 0))
632
+
633
+ const unNormedFacing = new Vec3(300, 60, 400)
634
+ const facing = unNormedFacing.scale(1 / unNormedFacing.xzDistanceTo(new Vec3(0, 60, 0)))
635
+
636
+ // yaw is facing north (-z) and rotating to the left, west (-z), so
637
+ // atan2(-x, -z) is yaw
638
+ const yaw = Math.atan2(-facing.x, -facing.z)
639
+ player.entity.yaw = yaw
640
+
641
+ // jump
642
+ player.jumpQueued = true
643
+ passTicks(10, player, physics, fakeWorld, controls)
644
+ expect(player.entity.onGround).toBeFalsy()
645
+
646
+ // fly
647
+ player.entity.elytraFlying = true
648
+ passTicks(1, player, physics, fakeWorld, controls)
649
+
650
+ // boost
651
+ player.fireworkRocketDuration = 20
652
+
653
+ // wait til on ground
654
+ untilIdle(player, physics, fakeWorld, controls)
655
+
656
+ expect(player.entity.elytraFlying).toBeFalsy()
657
+ expect(player.fireworkRocketDuration).toEqual(0)
658
+
659
+ facing.scale(player.entity.position.xzDistanceTo(new Vec3(0, 60, 0)))
660
+ expect(player.entity.position.x).toBeCloseTo(facing.x)
661
+ expect(player.entity.position.y).toBeCloseTo(60)
662
+ expect(player.entity.position.z).toBeCloseTo(facing.z)
663
+
664
+ // turn around
665
+ player.entity.yaw += Math.PI
666
+
667
+ // jump
668
+ player.jumpQueued = true
669
+ passTicks(10, player, physics, fakeWorld, controls)
670
+
671
+ expect(player.entity.onGround).toBeFalsy()
672
+
673
+ // fly
674
+ player.entity.elytraFlying = true
675
+ passTicks(1, player, physics, fakeWorld, controls)
676
+ expect(player.entity.elytraFlying).toBeTruthy()
677
+
678
+ // boost
679
+ player.fireworkRocketDuration = 20
680
+
681
+ // wait til on ground
682
+ untilIdle(player, physics, fakeWorld, controls)
683
+
684
+ expect(player.entity.elytraFlying).toBeFalsy()
685
+ expect(player.fireworkRocketDuration).toEqual(0)
686
+
687
+ // should be back at start
688
+ expect(player.entity.position.x).toBeCloseTo(0)
689
+ expect(player.entity.position.y).toEqual(60)
690
+ expect(player.entity.position.z).toBeCloseTo(0)
691
+ })
692
+ })