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.
@@ -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
- this.cache = new Map()
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.bot.world.getColumn(Math.floor(bx) >> 4, Math.floor(bz) >> 4)
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.bot.world.getColumn(x >> 4, z >> 4)
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.cache.get(stateId)
45
- if (i) return 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
 
@@ -79,9 +125,12 @@ class WorldView {
79
125
  isPowderSnow: name === 'powder_snow',
80
126
  isScaffolding: name === 'scaffolding',
81
127
  isFenceLike: name.endsWith('_fence') || name.endsWith('_wall') || name.endsWith('_fence_gate'),
128
+ isBubbleColumn: name === 'bubble_column',
129
+ bubbleDrag: props.drag === true || props.drag === 'true',
82
130
  fullCube: shapes.length === 1 && shapes[0].every((v, k) => v === FULL[k]),
83
131
  fluid: null
84
132
  }
133
+ info.suffocating = info.fullCube && !(b?.transparent ?? true)
85
134
  // fluids
86
135
  if (name === 'water' || name === 'lava' || name === 'flowing_water' || name === 'flowing_lava') {
87
136
  const level = Number(props.level ?? 0)
@@ -115,33 +164,49 @@ class WorldView {
115
164
 
116
165
  // Collect every collision box that could touch `bb` (BlockCollisions semantics:
117
166
  // unloaded chunks collide with nothing, outside build height is air).
118
- collisionBoxes (bb, out = []) {
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
119
182
  const x0 = Math.floor(bb.minX - 1.0e-7) - 1; const x1 = Math.floor(bb.maxX + 1.0e-7) + 1
120
183
  const y0 = Math.floor(bb.minY - 1.0e-7) - 1; const y1 = Math.floor(bb.maxY + 1.0e-7) + 1
121
184
  const z0 = Math.floor(bb.minZ - 1.0e-7) - 1; const z1 = Math.floor(bb.maxZ + 1.0e-7) + 1
122
185
  for (let x = x0; x <= x1; x++) {
123
186
  for (let z = z0; z <= z1; z++) {
124
- const col = this.bot.world.getColumn(x >> 4, z >> 4)
187
+ const col = this.column(x >> 4, z >> 4)
125
188
  if (!col) continue
189
+ const bottom = this.ticking ? this.bottom : this.minY
190
+ const top = this.ticking ? this.top : this.maxY
126
191
  for (let y = y0; y <= y1; y++) {
127
- if (y < this.minY || y >= this.maxY) continue
192
+ if (y < bottom || y >= top) continue
128
193
  this.pos.x = x & 15; this.pos.y = y; this.pos.z = z & 15
129
194
  const info = this.info(col.getBlockStateId(this.pos))
130
- const shapes = info.shapes
195
+ const shapes = info.isPowderSnow ? this.powderSnowShapes(y) : info.shapes
131
196
  for (let k = 0; k < shapes.length; k++) {
132
197
  const s = shapes[k]
133
- out.push([s[0] + x, s[1] + y, s[2] + z, s[3] + x, s[4] + y, s[5] + z])
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)
134
205
  }
135
206
  }
136
207
  }
137
208
  }
138
- return out
139
- }
140
-
141
- noCollision (bb) {
142
- const boxes = this.collisionBoxes(bb)
143
- for (const b of boxes) if (bb.intersectsBox(b)) return false
144
- return true
209
+ return false
145
210
  }
146
211
 
147
212
  containsAnyLiquid (bb) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectra-physics",
3
- "version": "1.0.0",
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"