spectra-physics 1.0.0 → 1.2.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,203 @@
1
+ 'use strict'
2
+
3
+ const { f32, sin, cos, degToRadF, clamp } = require('../mth')
4
+ const { moveRelative } = require('./input')
5
+ const { move } = require('./move')
6
+ const { fluidFallingAdjusted } = require('./fluids')
7
+ const blocks = require('./blocks')
8
+
9
+ const F_098 = f32(0.98)
10
+ const F_091 = f32(0.91)
11
+ const F_08 = f32(0.8)
12
+ const F_09 = f32(0.9)
13
+ const F_096 = f32(0.96)
14
+ const F_002 = f32(0.02)
15
+ const F_0026 = f32(0.025999999)
16
+ const F_0216 = f32(0.21600002)
17
+ const F_0546 = f32(0.54600006)
18
+ const F_015 = f32(0.15)
19
+ const F_042 = f32(0.42)
20
+ const F_01 = f32(0.1)
21
+ const F_02 = f32(0.2)
22
+ const F_004 = f32(0.04)
23
+ const F_06 = 0.6000000238418579
24
+ const F_03 = 0.30000001192092896
25
+
26
+ function flyingSpeed (player) {
27
+ if (!player.V.flyingSpeedLive) return player.legacyFlyingSpeed
28
+ if (player.abilities.flying) return player.sprinting ? f32(player.abilities.flyingSpeed * 2) : player.abilities.flyingSpeed
29
+ return player.sprinting ? F_0026 : F_002
30
+ }
31
+
32
+ function frictionInfluencedSpeed (player, friction) {
33
+ if (player.onGround) return f32(player.movementSpeed() * f32(F_0216 / f32(f32(friction * friction) * friction)))
34
+ return flyingSpeed(player)
35
+ }
36
+
37
+ function handleOnClimbable (player) {
38
+ if (!blocks.onClimbable(player)) return
39
+ player.fallDistance = 0
40
+ player.vel.x = clamp(player.vel.x, -F_015, F_015)
41
+ player.vel.z = clamp(player.vel.z, -F_015, F_015)
42
+ let vy = Math.max(player.vel.y, -F_015)
43
+ if (vy < 0 && !blocks.feetBlock(player).isScaffolding && player.input.shift) vy = 0
44
+ player.vel.y = vy
45
+ }
46
+
47
+ function isFree (player, dx, dy, dz) {
48
+ const box = player.bb().move(dx, dy, dz)
49
+ return player.world.noCollision(box) && !player.world.containsAnyLiquid(box)
50
+ }
51
+
52
+ // LivingEntity#jumpFromGround
53
+ function jumpFromGround (player) {
54
+ const V = player.V
55
+ const strength = V.attributes2005 ? f32(player.env.attribute('jump_strength', 0.42)) : F_042
56
+ let power = f32(strength * blocks.jumpFactor(player))
57
+ const boost = player.env.effect('jump_boost')
58
+ if (boost != null) power = f32(power + f32(F_01 * (boost + 1)))
59
+ if (V.attributes2005 && power <= 1.0e-5) return
60
+ player.vel.y = V.jumpKeepsHigherY ? Math.max(power, player.vel.y) : power
61
+ if (!player.sprinting) return
62
+ const r = degToRadF(player.yRot)
63
+ if (V.jumpDoubleSprintBoost) {
64
+ player.vel.x += -sin(r) * 0.2
65
+ player.vel.z += cos(r) * 0.2
66
+ } else {
67
+ player.vel.x += f32(-sin(r) * F_02)
68
+ player.vel.z += f32(cos(r) * F_02)
69
+ }
70
+ }
71
+
72
+ // jump handling in LivingEntity#aiStep
73
+ function handleJump (player) {
74
+ if (!player.input.jumping || player.abilities.flying) {
75
+ player.noJumpDelay = 0
76
+ return
77
+ }
78
+ const inLava = player.isInLava()
79
+ const height = inLava ? player.fluidHeight.lava : player.fluidHeight.water
80
+ const inWater = player.isInWater() && height > 0
81
+ const threshold = player.eyeHeight() < 0.4 ? 0 : 0.4
82
+ if (inWater && !(player.onGround && !(height > threshold))) {
83
+ player.vel.y += F_004
84
+ } else if (inLava && !(player.onGround && !(height > threshold))) {
85
+ player.vel.y += F_004
86
+ } else if ((player.onGround || (inWater && height <= threshold)) && player.noJumpDelay === 0) {
87
+ jumpFromGround(player)
88
+ player.noJumpDelay = 10
89
+ }
90
+ }
91
+
92
+ function travelInWater (player, ix, iy, iz, gravity, falling) {
93
+ const V = player.V
94
+ const startY = player.pos.y
95
+ let friction = player.sprinting ? F_09 : F_08
96
+ let speed = F_002
97
+ let strider = V.efficiencyAttributes
98
+ ? f32(player.env.attribute('water_movement_efficiency', 0))
99
+ : Math.min(3, player.env.bootsEnchant('depth_strider'))
100
+ if (!player.onGround) strider = f32(strider * 0.5)
101
+ if (strider > 0) {
102
+ const divisor = V.efficiencyAttributes ? 1 : 3
103
+ friction = f32(friction + f32(f32(f32(F_0546 - friction) * strider) / divisor))
104
+ speed = f32(speed + f32(f32(f32(player.movementSpeed() - speed) * strider) / divisor))
105
+ }
106
+ if (player.env.effect('dolphins_grace') != null) friction = F_096
107
+ moveRelative(player, speed, ix, iy, iz)
108
+ move(player, player.vel.x, player.vel.y, player.vel.z)
109
+ if (player.horizontalCollision && blocks.onClimbable(player)) player.vel.y = 0.2
110
+ player.vel.x *= friction
111
+ player.vel.y *= F_08
112
+ player.vel.z *= friction
113
+ fluidFallingAdjusted(player, gravity, falling)
114
+ if (player.horizontalCollision && isFree(player, player.vel.x, player.vel.y + F_06 - player.pos.y + startY, player.vel.z)) {
115
+ player.vel.y = F_03
116
+ }
117
+ }
118
+
119
+ function travelInLava (player, ix, iy, iz, gravity, falling) {
120
+ const startY = player.pos.y
121
+ moveRelative(player, F_002, ix, iy, iz)
122
+ move(player, player.vel.x, player.vel.y, player.vel.z)
123
+ if (player.fluidHeight.lava <= 0.4) {
124
+ player.vel.x *= 0.5
125
+ player.vel.y *= F_08
126
+ player.vel.z *= 0.5
127
+ fluidFallingAdjusted(player, gravity, falling)
128
+ } else {
129
+ player.vel.x *= 0.5
130
+ player.vel.y *= 0.5
131
+ player.vel.z *= 0.5
132
+ }
133
+ player.vel.y += -gravity / 4
134
+ if (player.horizontalCollision && isFree(player, player.vel.x, player.vel.y + F_06 - player.pos.y + startY, player.vel.z)) {
135
+ player.vel.y = F_03
136
+ }
137
+ }
138
+
139
+ function travelInAir (player, ix, iy, iz, gravity) {
140
+ const below = blocks.belowPos(player)
141
+ const friction = player.world.blockAt(below.x, below.y, below.z).friction
142
+ const drag = player.onGround ? f32(friction * F_091) : F_091
143
+ moveRelative(player, frictionInfluencedSpeed(player, friction), ix, iy, iz)
144
+ handleOnClimbable(player)
145
+ move(player, player.vel.x, player.vel.y, player.vel.z)
146
+ if ((player.horizontalCollision || player.input.jumping) && (blocks.onClimbable(player) || blocks.onPowderSnowWithBoots(player))) {
147
+ player.vel.y = 0.2
148
+ }
149
+ let vy = player.vel.y
150
+ const levitation = player.env.effect('levitation')
151
+ if (levitation != null) {
152
+ vy += (0.05 * (levitation + 1) - player.vel.y) * 0.2
153
+ player.fallDistance = 0
154
+ } else if (!player.world.hasChunk(below.x, below.z)) {
155
+ vy = player.pos.y > player.world.minY ? -0.1 : 0
156
+ } else {
157
+ vy -= gravity
158
+ }
159
+ player.vel.x *= drag
160
+ player.vel.y = vy * F_098
161
+ player.vel.z *= drag
162
+ }
163
+
164
+ // LivingEntity#travel
165
+ function livingTravel (player, ix, iy, iz) {
166
+ const falling = player.vel.y <= 0
167
+ let gravity = player.V.attributes2005 ? player.env.attribute('gravity', 0.08) : 0.08
168
+ if (falling && player.env.effect('slow_falling') != null) {
169
+ gravity = player.V.attributes2005 ? Math.min(gravity, 0.01) : 0.01
170
+ player.fallDistance = 0
171
+ }
172
+ const affected = !player.abilities.flying
173
+ if (player.isInWater() && affected) travelInWater(player, ix, iy, iz, gravity, falling)
174
+ else if (player.isInLava() && affected) travelInLava(player, ix, iy, iz, gravity, falling)
175
+ else travelInAir(player, ix, iy, iz, gravity)
176
+ }
177
+
178
+ function swimTowardsLook (player) {
179
+ const lookY = -sin(degToRadF(player.xRot))
180
+ const k = lookY < -0.2 ? 0.085 : 0.06
181
+ const above = player.world.fluidAt(Math.floor(player.pos.x), Math.floor(player.pos.y + 1 - 0.1), Math.floor(player.pos.z))
182
+ if (lookY <= 0 || player.input.jumping || above) player.vel.y += (lookY - player.vel.y) * k
183
+ }
184
+
185
+ // Player#travel
186
+ function travel (player) {
187
+ const ix = player.xxa
188
+ const iz = player.zza
189
+ if (player.swimming && !player.riding) swimTowardsLook(player)
190
+ if (!player.abilities.flying) {
191
+ livingTravel(player, ix, 0, iz)
192
+ return
193
+ }
194
+ const startVy = player.vel.y
195
+ const saved = player.legacyFlyingSpeed
196
+ if (!player.V.flyingSpeedLive) player.legacyFlyingSpeed = f32(player.abilities.flyingSpeed * (player.sprinting ? 2 : 1))
197
+ livingTravel(player, ix, 0, iz)
198
+ player.vel.y = startVy * 0.6
199
+ player.legacyFlyingSpeed = saved
200
+ player.fallDistance = 0
201
+ }
202
+
203
+ module.exports = { travel, handleJump }
@@ -0,0 +1,290 @@
1
+ 'use strict'
2
+ // Which blocks Entity#applyEffectsFromBlocks visits after travel (1.21.2+), and in what order. The order
3
+ // matters: effects such as bubble columns depend on the velocity left by the blocks visited before them.
4
+ // Each client version walks the movement differently; the four variants below follow those versions.
5
+
6
+ const { f32 } = require('../mth')
7
+
8
+ const EPS = 1.0e-7
9
+ const DEFLATE = f32(1.0e-5)
10
+ const SHORT_MOVE_SQ = f32(f32(0.99999) * f32(0.99999))
11
+ const TINY_MOVE_SQ = DEFLATE * DEFLATE
12
+ const FAR_SQ = 0.9999900000002526 * 0.9999900000002526
13
+ const STEP_LIMIT = 16
14
+
15
+ // insertion-ordered set of block positions, small enough that a linear scan beats hashing
16
+ class BlockList {
17
+ constructor () { this.coords = []; this.length = 0 }
18
+
19
+ clear () { this.length = 0; return this }
20
+
21
+ has (x, y, z) {
22
+ const c = this.coords
23
+ for (let i = 0; i < this.length * 3; i += 3) if (c[i] === x && c[i + 1] === y && c[i + 2] === z) return true
24
+ return false
25
+ }
26
+
27
+ // true when the position was not in the list yet
28
+ add (x, y, z) {
29
+ if (this.has(x, y, z)) return false
30
+ const i = this.length * 3
31
+ this.coords[i] = x; this.coords[i + 1] = y; this.coords[i + 2] = z
32
+ this.length++
33
+ return true
34
+ }
35
+
36
+ forEach (visit) {
37
+ const c = this.coords
38
+ for (let i = 0; i < this.length * 3; i += 3) visit(c[i], c[i + 1], c[i + 2])
39
+ }
40
+ }
41
+
42
+ const seenAlong = new BlockList()
43
+ const seenBetween = new BlockList()
44
+ const ordered = new BlockList()
45
+
46
+ const frac = (v) => v - Math.floor(v)
47
+ const sign = (v) => v === 0 ? 0 : (v > 0 ? 1 : -1)
48
+
49
+ // BlockPos.betweenClosed: x fastest, then y, then z
50
+ function forEachBetweenClosed (box, visit) {
51
+ const x0 = Math.floor(box.minX); const y0 = Math.floor(box.minY); const z0 = Math.floor(box.minZ)
52
+ const x1 = Math.floor(box.maxX); const y1 = Math.floor(box.maxY); const z1 = Math.floor(box.maxZ)
53
+ for (let z = z0; z <= z1; z++) {
54
+ for (let y = y0; y <= y1; y++) {
55
+ for (let x = x0; x <= x1; x++) visit(x, y, z)
56
+ }
57
+ }
58
+ }
59
+
60
+ // Y first, then the smaller of x/z ... matching Direction.axisStepOrder
61
+ const axisStepOrder = (v) => Math.abs(v.x) < Math.abs(v.z) ? 'yzx' : 'yxz'
62
+
63
+ // BlockPos.betweenCornersInDirection: starts at the corner facing away from the direction,
64
+ // walks Y, then the smaller of x/z, then the other one (innermost)
65
+ function forEachBetweenCorners (ax, ay, az, bx, by, bz, dir, visit) {
66
+ const minX = Math.min(ax, bx); const minY = Math.min(ay, by); const minZ = Math.min(az, bz)
67
+ const sizeX = Math.max(ax, bx) - minX; const sizeY = Math.max(ay, by) - minY; const sizeZ = Math.max(az, bz) - minZ
68
+ const sx = dir.x >= 0 ? 1 : -1; const sy = dir.y >= 0 ? 1 : -1; const sz = dir.z >= 0 ? 1 : -1
69
+ const x0 = sx > 0 ? minX : minX + sizeX; const y0 = sy > 0 ? minY : minY + sizeY; const z0 = sz > 0 ? minZ : minZ + sizeZ
70
+ const zSecond = Math.abs(dir.x) < Math.abs(dir.z)
71
+ for (let i = 0; i <= sizeY; i++) {
72
+ const y = y0 + sy * i
73
+ if (zSecond) {
74
+ for (let j = 0; j <= sizeZ; j++) {
75
+ const z = z0 + sz * j
76
+ for (let k = 0; k <= sizeX; k++) if (visit(x0 + sx * k, y, z) === false) return false
77
+ }
78
+ } else {
79
+ for (let j = 0; j <= sizeX; j++) {
80
+ const x = x0 + sx * j
81
+ for (let k = 0; k <= sizeZ; k++) if (visit(x, y, z0 + sz * k) === false) return false
82
+ }
83
+ }
84
+ }
85
+ return true
86
+ }
87
+
88
+ function clipPoint (best, delta, side, startSide, minA, maxA, minB, maxB, startA, deltaA, startB, deltaB) {
89
+ const t = (side - startSide) / delta
90
+ const a = startA + t * deltaA
91
+ const b = startB + t * deltaB
92
+ if (t > 0 && t < best.t && minA - EPS < a && a < maxA + EPS && minB - EPS < b && b < maxB + EPS) {
93
+ best.t = t
94
+ best.hit = true
95
+ }
96
+ }
97
+
98
+ // AABB.clip of the unit cube at (x, y, z) against the segment start -> end
99
+ function clip (x, y, z, start, end) {
100
+ const dx = end.x - start.x; const dy = end.y - start.y; const dz = end.z - start.z
101
+ const best = { t: 1, hit: false }
102
+ if (dx > EPS) clipPoint(best, dx, x, start.x, y, y + 1, z, z + 1, start.y, dy, start.z, dz)
103
+ else if (dx < -EPS) clipPoint(best, dx, x + 1, start.x, y, y + 1, z, z + 1, start.y, dy, start.z, dz)
104
+ if (dy > EPS) clipPoint(best, dy, y, start.y, z, z + 1, x, x + 1, start.z, dz, start.x, dx)
105
+ else if (dy < -EPS) clipPoint(best, dy, y + 1, start.y, z, z + 1, x, x + 1, start.z, dz, start.x, dx)
106
+ if (dz > EPS) clipPoint(best, dz, z, start.z, x, x + 1, y, y + 1, start.x, dx, start.y, dy)
107
+ else if (dz < -EPS) clipPoint(best, dz, z + 1, start.z, x, x + 1, y, y + 1, start.x, dx, start.y, dy)
108
+ return best.hit ? { x: start.x + best.t * dx, y: start.y + best.t * dy, z: start.z + best.t * dz } : null
109
+ }
110
+
111
+ // voxel walk of one corner of the box from start to end; calls hit(cx, cy, cz, point, step) on every cell it enters
112
+ function walk (start, end, limited, hit) {
113
+ const d = { x: end.x - start.x, y: end.y - start.y, z: end.z - start.z }
114
+ let cx = Math.floor(start.x); let cy = Math.floor(start.y); let cz = Math.floor(start.z)
115
+ const sx = sign(d.x); const sy = sign(d.y); const sz = sign(d.z)
116
+ const tx = sx === 0 ? Number.MAX_VALUE : sx / d.x
117
+ const ty = sy === 0 ? Number.MAX_VALUE : sy / d.y
118
+ const tz = sz === 0 ? Number.MAX_VALUE : sz / d.z
119
+ let nx = tx * (sx > 0 ? 1 - frac(start.x) : frac(start.x))
120
+ let ny = ty * (sy > 0 ? 1 - frac(start.y) : frac(start.y))
121
+ let nz = tz * (sz > 0 ? 1 - frac(start.z) : frac(start.z))
122
+ let steps = 0
123
+ while (nx <= 1 || ny <= 1 || nz <= 1) {
124
+ if (nx < ny) {
125
+ if (nx < nz) { cx += sx; nx += tx } else { cz += sz; nz += tz }
126
+ } else if (ny < nz) { cy += sy; ny += ty } else { cz += sz; nz += tz }
127
+ if (limited && steps++ > STEP_LIMIT) break
128
+ const point = clip(cx, cy, cz, start, end)
129
+ if (!point) continue
130
+ if (!limited) steps++
131
+ if (hit(cx, cy, cz, point, steps) === false) return -1
132
+ }
133
+ return steps
134
+ }
135
+
136
+ const clamp = (v, lo, hi) => v < lo ? lo : (v > hi ? hi : v)
137
+ const clampInto = (point, cx, cy, cz) => ({
138
+ x: clamp(point.x, cx + DEFLATE, cx + 1 - DEFLATE),
139
+ y: clamp(point.y, cy + DEFLATE, cy + 1 - DEFLATE),
140
+ z: clamp(point.z, cz + DEFLATE, cz + 1 - DEFLATE)
141
+ })
142
+
143
+ // 1.21.2 - 1.21.8: the min corner of the box walks the movement; every cell hit adds a box-sized block range
144
+ function legacyAlongTravel (box, direction, nudge, visit) {
145
+ const len = Math.sqrt(direction.x ** 2 + direction.y ** 2 + direction.z ** 2)
146
+ const n = nudge ? { x: direction.x / len * EPS, y: direction.y / len * EPS, z: direction.z / len * EPS } : { x: 0, y: 0, z: 0 }
147
+ const end = { x: box.minX + n.x, y: box.minY + n.y, z: box.minZ + n.z }
148
+ const start = { x: box.minX - direction.x - n.x, y: box.minY - direction.y - n.y, z: box.minZ - direction.z - n.z }
149
+ const sizeX = box.maxX - box.minX; const sizeY = box.maxY - box.minY; const sizeZ = box.maxZ - box.minZ
150
+ return walk(start, end, true, (cx, cy, cz, point, step) => {
151
+ const c = clampInto(point, cx, cy, cz)
152
+ const ex = Math.floor(c.x + sizeX); const ey = Math.floor(c.y + sizeY); const ez = Math.floor(c.z + sizeZ)
153
+ for (let x = cx; x <= ex; x++) {
154
+ for (let y = cy; y <= ey; y++) {
155
+ for (let z = cz; z <= ez; z++) visit(x, y, z, step)
156
+ }
157
+ }
158
+ })
159
+ }
160
+
161
+ // BlockCollisions#getFurthestCorner (1.21.10+)
162
+ function furthestCorner (v) {
163
+ const ax = Math.abs(v.x); const ay = Math.abs(v.y); const az = Math.abs(v.z)
164
+ const xs = v.x >= 0 ? 1 : -1; const ys = v.y >= 0 ? 1 : -1; const zs = v.z >= 0 ? 1 : -1
165
+ if (ax <= ay && ax <= az) return { x: -xs, y: -zs, z: ys }
166
+ return ay <= az ? { x: zs, y: -ys, z: -xs } : { x: -ys, y: xs, z: -zs }
167
+ }
168
+
169
+ // 1.21.9+: BlockGetter#forEachBlockIntersectedBetween
170
+ function modernBetween (from, to, box, visit) {
171
+ const d = { x: to.x - from.x, y: to.y - from.y, z: to.z - from.z }
172
+ if (d.x * d.x + d.y * d.y + d.z * d.z < TINY_MOVE_SQ) {
173
+ forEachBetweenClosed(box, (x, y, z) => visit(x, y, z, 0))
174
+ return
175
+ }
176
+ const seen = seenBetween.clear()
177
+ const back = box.move(-d.x, -d.y, -d.z)
178
+ const ok = forEachBetweenCorners(Math.floor(back.minX), Math.floor(back.minY), Math.floor(back.minZ),
179
+ Math.floor(back.maxX), Math.floor(back.maxY), Math.floor(back.maxZ), d, (x, y, z) => {
180
+ if (visit(x, y, z, 0) === false) return false
181
+ seen.add(x, y, z)
182
+ })
183
+ if (!ok) return
184
+ const sizeX = box.maxX - box.minX; const sizeY = box.maxY - box.minY; const sizeZ = box.maxZ - box.minZ
185
+ const corner = furthestCorner(d)
186
+ const center = { x: box.minX + 0.5 * sizeX, y: box.minY + 0.5 * sizeY, z: box.minZ + 0.5 * sizeZ }
187
+ const end = { x: center.x + sizeX * 0.5 * corner.x, y: center.y + sizeY * 0.5 * corner.y, z: center.z + sizeZ * 0.5 * corner.z }
188
+ const start = { x: end.x - d.x, y: end.y - d.y, z: end.z - d.z }
189
+ const steps = walk(start, end, false, (cx, cy, cz, point, step) => {
190
+ const c = clampInto(point, cx, cy, cz)
191
+ const ex = Math.floor(c.x - sizeX * corner.x); const ey = Math.floor(c.y - sizeY * corner.y); const ez = Math.floor(c.z - sizeZ * corner.z)
192
+ return forEachBetweenCorners(cx, cy, cz, ex, ey, ez, d, (x, y, z) => {
193
+ if (seen.add(x, y, z)) return visit(x, y, z, step)
194
+ })
195
+ })
196
+ if (steps < 0) return
197
+ forEachBetweenCorners(Math.floor(box.minX), Math.floor(box.minY), Math.floor(box.minZ),
198
+ Math.floor(box.maxX), Math.floor(box.maxY), Math.floor(box.maxZ), d, (x, y, z) => {
199
+ if (seen.add(x, y, z)) return visit(x, y, z, steps + 1)
200
+ })
201
+ }
202
+
203
+ /**
204
+ * Visits the blocks touched by this tick's movements in game order.
205
+ * moves: [{ from, to, input }] where input is the movement asked of Entity#move (1.21.5+), or null.
206
+ * visit(x, y, z, applyVelocity)
207
+ */
208
+ function traverseMoves (player, moves, visit) {
209
+ const pv = player.V.protocol
210
+ const boxAt = (p) => player.bbAt(p.x, p.y, p.z).deflate(DEFLATE)
211
+
212
+ if (pv < 769) { // 1.21.2 - 1.21.3: the final box, nudged walk
213
+ const box = player.bb().deflate(DEFLATE)
214
+ for (const { from, to } of moves) {
215
+ const d = { x: to.x - from.x, y: to.y - from.y, z: to.z - from.z }
216
+ if (d.x * d.x + d.y * d.y + d.z * d.z < SHORT_MOVE_SQ) {
217
+ forEachBetweenClosed(box, (x, y, z) => visit(x, y, z, true))
218
+ continue
219
+ }
220
+ const list = ordered.clear()
221
+ const add = (x, y, z) => { list.add(x, y, z) }
222
+ legacyAlongTravel(box, d, true, add)
223
+ forEachBetweenClosed(box, add)
224
+ list.forEach((x, y, z) => visit(x, y, z, true))
225
+ }
226
+ return
227
+ }
228
+
229
+ if (pv < 770) { // 1.21.4: box at the end of each movement
230
+ for (const { from, to } of moves) {
231
+ const box = boxAt(to)
232
+ const d = { x: to.x - from.x, y: to.y - from.y, z: to.z - from.z }
233
+ if (d.x * d.x + d.y * d.y + d.z * d.z < SHORT_MOVE_SQ) {
234
+ forEachBetweenClosed(box, (x, y, z) => visit(x, y, z, true))
235
+ continue
236
+ }
237
+ const list = ordered.clear()
238
+ const add = (x, y, z) => { list.add(x, y, z) }
239
+ legacyAlongTravel(box, d, false, add)
240
+ forEachBetweenClosed(box, add)
241
+ list.forEach((x, y, z) => visit(x, y, z, true))
242
+ }
243
+ return
244
+ }
245
+
246
+ const modern = pv >= 773
247
+ for (const move of moves) {
248
+ const delta = { x: move.to.x - move.from.x, y: move.to.y - move.from.y, z: move.to.z - move.from.z }
249
+ let budget = STEP_LIMIT
250
+ const segment = (from, to, limit) => {
251
+ const box = boxAt(to)
252
+ if (!modern) {
253
+ const d = { x: to.x - from.x, y: to.y - from.y, z: to.z - from.z }
254
+ if (d.x * d.x + d.y * d.y + d.z * d.z < SHORT_MOVE_SQ) {
255
+ forEachBetweenClosed(box, (x, y, z) => visit(x, y, z, true))
256
+ return 0
257
+ }
258
+ const seen = seenAlong.clear()
259
+ const steps = legacyAlongTravel(box, d, false, (x, y, z) => {
260
+ if (seen.add(x, y, z)) visit(x, y, z, true)
261
+ })
262
+ forEachBetweenClosed(box, (x, y, z) => { if (!seen.has(x, y, z)) visit(x, y, z, true) })
263
+ return steps
264
+ }
265
+ const far = (to.x - from.x) ** 2 + (to.y - from.y) ** 2 + (to.z - from.z) ** 2 > FAR_SQ
266
+ let reached = 0
267
+ modernBetween(from, to, box, (x, y, z, step) => {
268
+ if (step >= limit) return false
269
+ reached = step
270
+ visit(x, y, z, far || box.intersects(x, y, z, x + 1, y + 1, z + 1))
271
+ })
272
+ return reached + 1
273
+ }
274
+ if (move.input && (delta.x || delta.y || delta.z)) {
275
+ let from = move.from
276
+ for (const axis of axisStepOrder(modern ? move.input : delta)) {
277
+ const value = delta[axis]
278
+ if (value === 0) continue
279
+ const to = { ...from, [axis]: from[axis] + value }
280
+ budget -= segment(from, to, budget)
281
+ from = to
282
+ }
283
+ } else {
284
+ budget -= segment(move.from, move.to, STEP_LIMIT)
285
+ }
286
+ if (modern && budget <= 0) segment(move.to, move.to, 1)
287
+ }
288
+ }
289
+
290
+ module.exports = { traverseMoves, BlockList, forEachBetweenCorners, furthestCorner, clip }
@@ -1,52 +1,77 @@
1
1
  'use strict'
2
- // Version switches for client behaviour that differs between 1.16.5 and 26.x.
3
- // Uses the protocol version number so "1.21.3" (768) == "1.21.2" etc.
2
+ // Client behaviour that changed between 1.16.5 and 26.x, keyed by protocol number
3
+ // ("1.21.3" is 768, the same as 1.21.2).
4
4
 
5
5
  const PV = {
6
6
  '1.16.5': 754,
7
7
  '1.17': 755,
8
+ '1.18.2': 758,
8
9
  '1.19.4': 762,
9
10
  '1.20': 763,
10
11
  '1.20.2': 764,
11
- '1.20.3': 765,
12
12
  '1.20.5': 766,
13
13
  '1.21': 767,
14
14
  '1.21.2': 768,
15
15
  '1.21.4': 769,
16
16
  '1.21.5': 770,
17
17
  '1.21.6': 771,
18
- '1.21.9': 773
18
+ '1.21.9': 773,
19
+ '26.1': 775
19
20
  }
20
21
 
21
22
  function versionFlags (registry) {
22
- const pv = registry.version['>='] ? registry.version.version : registry.version
23
+ const pv = registry.version.version
23
24
  const at = (name) => pv >= PV[name]
24
25
  return {
25
26
  protocol: pv,
26
27
  name: registry.version.minecraftVersion,
27
- // login / configuration
28
- configPhase: at('1.20.2'), // brand + client information are sent in the configuration phase
29
- // movement packets
30
- movementFlags: at('1.21.2'), // onGround + horizontalCollision bitflags in move packets
31
- tickEnd: at('1.21.2'), // ServerboundClientTickEndPacket after every client tick
32
- playerInputPacket: at('1.21.2'), // ServerboundPlayerInputPacket sent on key change (replaces steer_vehicle)
33
- shiftInInput: at('1.21.6'), // no more PRESS/RELEASE_SHIFT_KEY player commands
34
- clientLoaded: at('1.21.4'), // player does not tick until the level screen closes, then sends player_loaded
35
- posRotBeforeConfirm: pv === PV['1.21.2'], // 1.21.2/1.21.3 answer teleports with pos-rot *before* accept-teleportation
36
- relativeTeleportVelocity: at('1.21.2'), // teleport packet carries delta movement + delta flags
37
- lpVelocity: at('1.21.9'), // entity_velocity uses lpVec3 (already blocks/tick)
38
- // physics
39
- posFromBoundingBox: !at('1.17'), // 1.16 recalculates the position from the bounding box centre
40
- flyingSpeedLive: at('1.19.4'), // Player#getFlyingSpeed() instead of the lagging flyingSpeed field
41
- minorCollision: at('1.19.4'), // sprinting survives "minor" horizontal collisions
42
- supportingBlock: at('1.20'), // mainSupportingBlockPos drives the block below
43
- attributes2005: at('1.20.5'), // gravity / step_height / jump_strength attributes
44
- jumpKeepsHigherY: at('1.21'),
45
- modernStepUp: at('1.21'), // collectCandidateStepUpHeights step algorithm
46
- normalizedInput: at('1.21.2'), // KeyboardInput#moveVector is normalized
47
- modernBackOff: at('1.21.2'), // canFallAtLeast based sneak edge back-off
48
- squareInput: at('1.21.5'), // modifyInputSpeedForSquareMovement
28
+
29
+ // connection
30
+ configPhase: at('1.20.2'),
31
+ movementFlags: at('1.21.2'),
32
+ tickEnd: at('1.21.2'),
33
+ playerInputPacket: at('1.21.2'),
34
+ shiftInInput: at('1.21.6'),
35
+ clientLoaded: at('1.21.4'),
36
+ posRotBeforeConfirm: pv === PV['1.21.2'],
37
+ relativeTeleportVelocity: at('1.21.2'),
38
+ lpVelocity: at('1.21.9'),
39
+ pointThree: !at('1.18.2'), // position is only reported after moving 0.03 (later 0.0002)
40
+
41
+ // input
42
+ inputModel: at('1.21.5') ? 'modern' : 'legacy',
43
+ floatSqrt: !at('1.17'),
44
+ normalizeEpsilon: at('1.21.2') ? Math.fround(1.0e-5) : 1.0e-4,
45
+ horizontalThreshold: at('1.21.5'), // tiny horizontal speed is cut by length, not per axis
46
+
47
+ // movement
48
+ posFromBoundingBox: !at('1.17'),
49
+ epsilonCollisionFlags: at('1.18.2'),
50
+ keepsXOnCornerCollision: !at('1.18.2'), // 1.14 - 1.18.1 bug
51
+ modernMoveThreshold: at('1.21.2'),
52
+ modernStepUp: at('1.21'),
53
+ supportingBlock: at('1.20'),
54
+ minorCollision: at('1.19.4'),
49
55
  fallDistanceDouble: at('1.21.5'),
56
+
57
+ // speed
58
+ flyingSpeedLive: at('1.19.4'),
59
+ airSprintFloatSum: at('1.18.2'),
60
+ attributes2005: at('1.20.5'),
61
+ efficiencyAttributes: at('1.21'), // movement_efficiency / water_movement_efficiency
62
+ soulSpeedBoots: pv >= 751 && !at('1.21'),
63
+ jumpDoubleSprintBoost: at('1.20.5'),
64
+ jumpKeepsHigherY: at('1.21.2'),
65
+
66
+ // world interaction
67
+ blockEffectsAfterTravel: at('1.21.2'),
68
+ blockEffectsPerAxis: at('1.21.5'),
69
+ insideBlockDeflate: at('1.19.4') ? 1.0e-5 : 0.001,
70
+ fluidRecheckInMove: pv !== PV['1.21.4'],
71
+ fluidInteraction: at('26.1'), // one pass over water and lava, also used by the recheck in move
72
+ swimmingNeedsFeetInWater: at('1.17'),
73
+ powderSnow: at('1.17'),
74
+
50
75
  // vehicles
51
76
  moveVehicleOnGround: at('1.21.2')
52
77
  }