spectra-physics 1.1.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.
- package/README.md +25 -6
- package/lib/client/clock.js +38 -0
- package/lib/client/connection.js +20 -0
- package/lib/client/environment.js +12 -43
- package/lib/client/index.js +20 -11
- package/lib/client/interact.js +79 -0
- package/lib/client/items.js +85 -0
- package/lib/client/protocol.js +42 -0
- package/lib/client/registries.js +86 -0
- package/lib/client/riding.js +7 -3
- package/lib/client/server.js +13 -0
- package/lib/client/ticker.js +8 -23
- package/lib/engine/aabb.js +5 -3
- package/lib/engine/boat.js +57 -7
- package/lib/engine/player/effects.js +12 -49
- package/lib/engine/player/fluids.js +15 -6
- package/lib/engine/player/index.js +19 -1
- package/lib/engine/player/input.js +5 -2
- package/lib/engine/player/move.js +13 -7
- package/lib/engine/player/pose.js +1 -1
- package/lib/engine/player/traversal.js +290 -0
- package/lib/engine/version.js +3 -1
- package/lib/engine/world.js +82 -20
- package/package.json +2 -1
|
@@ -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 }
|
package/lib/engine/version.js
CHANGED
|
@@ -15,7 +15,8 @@ const PV = {
|
|
|
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) {
|
|
@@ -67,6 +68,7 @@ function versionFlags (registry) {
|
|
|
67
68
|
blockEffectsPerAxis: at('1.21.5'),
|
|
68
69
|
insideBlockDeflate: at('1.19.4') ? 1.0e-5 : 0.001,
|
|
69
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
|
|
70
72
|
swimmingNeedsFeetInWater: at('1.17'),
|
|
71
73
|
powderSnow: at('1.17'),
|
|
72
74
|
|
package/lib/engine/world.js
CHANGED
|
@@ -14,37 +14,83 @@ const SPEED_FACTOR = { soul_sand: 0.4, honey_block: 0.4 }
|
|
|
14
14
|
const JUMP_FACTOR = { honey_block: 0.5 }
|
|
15
15
|
|
|
16
16
|
const FULL = [0, 0, 0, 1, 1, 1]
|
|
17
|
+
const NO_SHAPES = []
|
|
18
|
+
const SNOW_FULL = [FULL]
|
|
19
|
+
const SNOW_FALLING = [[0, 0, 0, 1, f32(0.9), 1]]
|
|
20
|
+
const ABOVE_EPSILON = f32(1.0e-5)
|
|
17
21
|
|
|
18
22
|
class WorldView {
|
|
19
23
|
constructor (bot) {
|
|
20
24
|
this.bot = bot
|
|
21
25
|
this.registry = bot.registry
|
|
22
26
|
this.Block = require('prismarine-block')(bot.registry)
|
|
23
|
-
|
|
27
|
+
let states = 1
|
|
28
|
+
for (const b of bot.registry.blocksArray ?? []) states = Math.max(states, (b.maxStateId ?? b.id) + 1)
|
|
29
|
+
this.infos = new Array(states).fill(null)
|
|
30
|
+
this.boxes = []
|
|
31
|
+
this.boxPool = []
|
|
24
32
|
this.pos = { x: 0, y: 0, z: 0 }
|
|
33
|
+
// collision context of the entity that is moving: { bottom, fallDistance, walksOnPowderSnow, descending }
|
|
34
|
+
this.entity = null
|
|
35
|
+
// while an entity ticks nothing else can change the world, so column lookups are memoised
|
|
36
|
+
this.ticking = false
|
|
37
|
+
this.columns = new Map()
|
|
38
|
+
this.bottom = 0
|
|
39
|
+
this.top = 256
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
beginTick (entity = null) {
|
|
43
|
+
this.entity = entity
|
|
44
|
+
this.ticking = true
|
|
45
|
+
this.columns.clear()
|
|
46
|
+
this.bottom = this.minY
|
|
47
|
+
this.top = this.maxY
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
endTick () {
|
|
51
|
+
this.entity = null
|
|
52
|
+
this.ticking = false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
column (cx, cz) {
|
|
56
|
+
if (!this.ticking) return this.bot.world.getColumn(cx, cz)
|
|
57
|
+
const key = cx * 4194304 + cz
|
|
58
|
+
let col = this.columns.get(key)
|
|
59
|
+
if (col === undefined) {
|
|
60
|
+
col = this.bot.world.getColumn(cx, cz) ?? null
|
|
61
|
+
this.columns.set(key, col)
|
|
62
|
+
}
|
|
63
|
+
return col
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// PowderSnowBlock#getCollisionShape
|
|
67
|
+
powderSnowShapes (y) {
|
|
68
|
+
const e = this.entity
|
|
69
|
+
if (!e) return NO_SHAPES
|
|
70
|
+
if (e.fallDistance > 2.5) return SNOW_FALLING
|
|
71
|
+
if (e.walksOnPowderSnow && e.bottom > y + 1 - ABOVE_EPSILON && !e.descending) return SNOW_FULL
|
|
72
|
+
return NO_SHAPES
|
|
25
73
|
}
|
|
26
74
|
|
|
27
75
|
get minY () { return this.bot.game?.minY ?? 0 }
|
|
28
76
|
get maxY () { return this.minY + (this.bot.game?.height ?? 256) }
|
|
29
77
|
|
|
30
78
|
hasChunk (bx, bz) {
|
|
31
|
-
return !!this.
|
|
79
|
+
return !!this.column(Math.floor(bx) >> 4, Math.floor(bz) >> 4)
|
|
32
80
|
}
|
|
33
81
|
|
|
34
82
|
// returns null when the column is not loaded
|
|
35
83
|
stateAt (x, y, z) {
|
|
36
|
-
const col = this.
|
|
84
|
+
const col = this.column(x >> 4, z >> 4)
|
|
37
85
|
if (!col) return null
|
|
38
|
-
if (y < this.minY || y >= this.maxY) return 0
|
|
86
|
+
if (this.ticking ? (y < this.bottom || y >= this.top) : (y < this.minY || y >= this.maxY)) return 0
|
|
39
87
|
this.pos.x = x & 15; this.pos.y = y; this.pos.z = z & 15
|
|
40
88
|
return col.getBlockStateId(this.pos)
|
|
41
89
|
}
|
|
42
90
|
|
|
43
91
|
info (stateId) {
|
|
44
|
-
let i = this.
|
|
45
|
-
if (i)
|
|
46
|
-
i = this._build(stateId)
|
|
47
|
-
this.cache.set(stateId, i)
|
|
92
|
+
let i = this.infos[stateId]
|
|
93
|
+
if (i == null) i = this.infos[stateId] = this._build(stateId)
|
|
48
94
|
return i
|
|
49
95
|
}
|
|
50
96
|
|
|
@@ -118,33 +164,49 @@ class WorldView {
|
|
|
118
164
|
|
|
119
165
|
// Collect every collision box that could touch `bb` (BlockCollisions semantics:
|
|
120
166
|
// unloaded chunks collide with nothing, outside build height is air).
|
|
121
|
-
|
|
167
|
+
// The returned list and its boxes are reused by the next call.
|
|
168
|
+
collisionBoxes (bb) {
|
|
169
|
+
this.boxes.length = 0
|
|
170
|
+
this.scan(bb, false)
|
|
171
|
+
return this.boxes
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
noCollision (bb) {
|
|
175
|
+
return !this.scan(bb, true)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// walks the blocks around bb; with test set, stops at the first box that overlaps bb
|
|
179
|
+
scan (bb, test) {
|
|
180
|
+
const pool = this.boxPool
|
|
181
|
+
const out = this.boxes
|
|
122
182
|
const x0 = Math.floor(bb.minX - 1.0e-7) - 1; const x1 = Math.floor(bb.maxX + 1.0e-7) + 1
|
|
123
183
|
const y0 = Math.floor(bb.minY - 1.0e-7) - 1; const y1 = Math.floor(bb.maxY + 1.0e-7) + 1
|
|
124
184
|
const z0 = Math.floor(bb.minZ - 1.0e-7) - 1; const z1 = Math.floor(bb.maxZ + 1.0e-7) + 1
|
|
125
185
|
for (let x = x0; x <= x1; x++) {
|
|
126
186
|
for (let z = z0; z <= z1; z++) {
|
|
127
|
-
const col = this.
|
|
187
|
+
const col = this.column(x >> 4, z >> 4)
|
|
128
188
|
if (!col) continue
|
|
189
|
+
const bottom = this.ticking ? this.bottom : this.minY
|
|
190
|
+
const top = this.ticking ? this.top : this.maxY
|
|
129
191
|
for (let y = y0; y <= y1; y++) {
|
|
130
|
-
if (y <
|
|
192
|
+
if (y < bottom || y >= top) continue
|
|
131
193
|
this.pos.x = x & 15; this.pos.y = y; this.pos.z = z & 15
|
|
132
194
|
const info = this.info(col.getBlockStateId(this.pos))
|
|
133
|
-
const shapes = info.shapes
|
|
195
|
+
const shapes = info.isPowderSnow ? this.powderSnowShapes(y) : info.shapes
|
|
134
196
|
for (let k = 0; k < shapes.length; k++) {
|
|
135
197
|
const s = shapes[k]
|
|
136
|
-
|
|
198
|
+
if (test) {
|
|
199
|
+
if (bb.intersects(s[0] + x, s[1] + y, s[2] + z, s[3] + x, s[4] + y, s[5] + z)) return true
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
const b = pool[out.length] ?? (pool[out.length] = new Array(6))
|
|
203
|
+
b[0] = s[0] + x; b[1] = s[1] + y; b[2] = s[2] + z; b[3] = s[3] + x; b[4] = s[4] + y; b[5] = s[5] + z
|
|
204
|
+
out.push(b)
|
|
137
205
|
}
|
|
138
206
|
}
|
|
139
207
|
}
|
|
140
208
|
}
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
noCollision (bb) {
|
|
145
|
-
const boxes = this.collisionBoxes(bb)
|
|
146
|
-
for (const b of boxes) if (bb.intersectsBox(b)) return false
|
|
147
|
-
return true
|
|
209
|
+
return false
|
|
148
210
|
}
|
|
149
211
|
|
|
150
212
|
containsAnyLiquid (bb) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectra-physics",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Vanilla-accurate client physics and movement for mineflayer (1.16.5 - 26.1)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"node": ">=22"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"minecraft-data": "^3.117.0",
|
|
40
41
|
"prismarine-block": "^1.23.0",
|
|
41
42
|
"prismarine-chunk": "^1.39.0",
|
|
42
43
|
"vec3": "^0.1.10"
|