spectra-physics 1.1.0 → 1.3.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,132 @@
1
+ 'use strict'
2
+ // Block breaking the way MultiPlayerGameMode does it: look at a visible face, START on one tick, add the
3
+ // destroy progress and swing every tick, STOP on the tick it reaches 1, then wait 5 ticks before the next
4
+ // block. mineflayer's dig() runs on a wall clock and swings every 350 ms instead.
5
+
6
+ const { Vec3 } = require('vec3')
7
+
8
+ const DESTROY_DELAY = 5
9
+ const FACES = [
10
+ { axis: 'y', sign: -1, face: 0 }, { axis: 'y', sign: 1, face: 1 },
11
+ { axis: 'z', sign: -1, face: 2 }, { axis: 'z', sign: 1, face: 3 },
12
+ { axis: 'x', sign: -1, face: 4 }, { axis: 'x', sign: 1, face: 5 }
13
+ ]
14
+
15
+ function installDigger (ctx) {
16
+ const { bot, client, V } = ctx
17
+ const hasSequence = V.protocol >= 759
18
+ let sequence = 0
19
+ let job = null
20
+ let delay = 0
21
+
22
+ const send = (status, block, face) => {
23
+ const packet = { status, location: block.position, face }
24
+ if (hasSequence) packet.sequence = status === 0 || status === 2 ? ++sequence : 0
25
+ client.write('block_dig', packet)
26
+ }
27
+
28
+ // faces the eyes can see, aimed at through the open side; closest first
29
+ function aimPoints (block) {
30
+ const eye = bot.entity.position.offset(0, bot.entity.eyeHeight ?? 1.62, 0)
31
+ const c = block.position.offset(0.5, 0.5, 0.5)
32
+ const out = []
33
+ for (const f of FACES) {
34
+ const toward = f.sign * (eye[f.axis] - c[f.axis])
35
+ if (toward <= 0.5) continue
36
+ const point = c.clone()
37
+ point[f.axis] += f.sign * 0.5
38
+ const dir = point.minus(eye).normalize()
39
+ const hit = bot.world.raycast(eye, dir, 6)
40
+ if (!hit || !hit.position.equals(block.position)) continue
41
+ out.push({ point: hit.intersect ?? point, face: hit.face ?? f.face, distance: eye.distanceTo(point) })
42
+ }
43
+ return out.sort((a, b) => a.distance - b.distance)
44
+ }
45
+
46
+ async function equipBest (block) {
47
+ const tool = bestTool(bot, block)
48
+ if (tool && bot.heldItem?.type !== tool.type) await bot.equip(tool, 'hand')
49
+ }
50
+
51
+ bot.on('physicsTick', () => {
52
+ if (delay > 0) delay--
53
+ if (!job) return
54
+ const block = bot.blockAt(job.block.position)
55
+ if (!block || block.type !== job.block.type) return finish(null)
56
+ if (!job.started) {
57
+ if (delay > 0) return
58
+ job.started = true
59
+ const ticks = Math.ceil(bot.digTime(block) / 50)
60
+ send(0, block, job.face)
61
+ bot.swingArm()
62
+ if (ticks <= 1) return finish(null, true)
63
+ return
64
+ }
65
+ if (job.waitingBreak) {
66
+ if (++job.waited > 20) finish(new Error('the server did not break the block'))
67
+ return
68
+ }
69
+ job.progress += 50 / Math.max(50, bot.digTime(block))
70
+ bot.swingArm()
71
+ if (job.progress >= 1 - 1e-9) {
72
+ send(2, block, job.face)
73
+ delay = DESTROY_DELAY
74
+ job.waitingBreak = true
75
+ job.waited = 0
76
+ }
77
+ })
78
+
79
+ bot.on('blockUpdate', (oldBlock, newBlock) => {
80
+ if (!job || !newBlock || !newBlock.position.equals(job.block.position)) return
81
+ if (newBlock.type !== job.block.type) finish(null)
82
+ })
83
+
84
+ function finish (err) {
85
+ const j = job
86
+ job = null
87
+ if (!j) return
88
+ if (err) j.reject(err)
89
+ else j.resolve()
90
+ }
91
+
92
+ ctx.dig = async (block) => {
93
+ if (job) throw new Error('already digging')
94
+ const aim = aimPoints(block)[0]
95
+ if (!aim) throw new Error(`${block.name} is not in view`)
96
+ await bot.lookAt(aim.point)
97
+ await equipBest(block)
98
+ if (bot.digTime(block) === Infinity) throw new Error(`cannot dig ${block.name}`)
99
+ return new Promise((resolve, reject) => {
100
+ job = { block, face: aim.face, progress: 0, started: false, resolve, reject }
101
+ setTimeout(() => { if (job && job.block === block) finish(new Error('dig timeout')) }, 60000)
102
+ })
103
+ }
104
+
105
+ ctx.stopDigging = () => {
106
+ if (!job) return
107
+ if (job.started && !job.waitingBreak) send(1, job.block, job.face)
108
+ finish(new Error('digging stopped'))
109
+ }
110
+
111
+ ctx.isDigging = () => !!job
112
+ }
113
+
114
+ // the inventory item that breaks a block fastest (null: the empty hand is as good)
115
+ function bestTool (bot, block) {
116
+ let best = null
117
+ let bestTime = bot.digTime ? digTimeWith(bot, block, null) : Infinity
118
+ for (const item of bot.inventory.items()) {
119
+ const t = digTimeWith(bot, block, item)
120
+ if (t < bestTime) { best = item; bestTime = t }
121
+ }
122
+ return best
123
+ }
124
+
125
+ function digTimeWith (bot, block, item) {
126
+ let enchants = []
127
+ try { enchants = item?.enchants ?? [] } catch { enchants = [] }
128
+ return block.digTime(item ? item.type : null, bot.game?.gameMode === 'creative', bot.entity.isInWater, !bot.entity.onGround,
129
+ Array.isArray(enchants) ? enchants : [], bot.entity.effects)
130
+ }
131
+
132
+ module.exports = { installDigger, bestTool, Vec3 }
@@ -0,0 +1,236 @@
1
+ 'use strict'
2
+ // Goals for the pathfinder, with the same shape as mineflayer-pathfinder's (heuristic, isEnd, hasChanged,
3
+ // isValid) so code written for it keeps working. Heuristics are in ticks, matching the move costs: the
4
+ // fastest way over flat ground is sprinting (about 3.56 ticks per block), so they never overestimate.
5
+
6
+ const { COST } = require('./costs')
7
+
8
+ function horizontal (dx, dz) {
9
+ const ax = Math.abs(dx)
10
+ const az = Math.abs(dz)
11
+ return Math.max(ax, az) + (Math.SQRT2 - 1) * Math.min(ax, az)
12
+ }
13
+
14
+ // lower bound on the ticks needed to cover the offset: going up is at least a walk per block,
15
+ // going down at least the fall time per block
16
+ function estimate (dx, dy, dz) {
17
+ return COST.walk * Math.max(horizontal(dx, dz), Math.max(0, dy)) + COST.fallTick * Math.max(0, -dy)
18
+ }
19
+
20
+ class Goal {
21
+ heuristic () { return 0 }
22
+ isEnd () { return true }
23
+ hasChanged () { return false }
24
+ isValid () { return true }
25
+ }
26
+
27
+ class GoalBlock extends Goal {
28
+ constructor (x, y, z) {
29
+ super()
30
+ this.x = Math.floor(x); this.y = Math.floor(y); this.z = Math.floor(z)
31
+ }
32
+
33
+ heuristic (node) { return estimate(this.x - node.x, this.y - node.y, this.z - node.z) }
34
+ isEnd (node) { return node.x === this.x && node.y === this.y && node.z === this.z }
35
+ }
36
+
37
+ class GoalNear extends Goal {
38
+ constructor (x, y, z, range) {
39
+ super()
40
+ this.x = Math.floor(x); this.y = Math.floor(y); this.z = Math.floor(z)
41
+ this.rangeSq = range * range
42
+ this.range = range
43
+ }
44
+
45
+ heuristic (node) {
46
+ const d = estimate(this.x - node.x, this.y - node.y, this.z - node.z)
47
+ return Math.max(0, d - this.range * COST.walk)
48
+ }
49
+
50
+ isEnd (node) {
51
+ const dx = this.x - node.x; const dy = this.y - node.y; const dz = this.z - node.z
52
+ return dx * dx + dy * dy + dz * dz <= this.rangeSq
53
+ }
54
+ }
55
+
56
+ class GoalXZ extends Goal {
57
+ constructor (x, z) {
58
+ super()
59
+ this.x = Math.floor(x); this.z = Math.floor(z)
60
+ }
61
+
62
+ heuristic (node) { return COST.walk * horizontal(this.x - node.x, this.z - node.z) }
63
+ isEnd (node) { return node.x === this.x && node.z === this.z }
64
+ }
65
+
66
+ class GoalNearXZ extends Goal {
67
+ constructor (x, z, range) {
68
+ super()
69
+ this.x = Math.floor(x); this.z = Math.floor(z)
70
+ this.range = range
71
+ this.rangeSq = range * range
72
+ }
73
+
74
+ heuristic (node) { return Math.max(0, COST.walk * (horizontal(this.x - node.x, this.z - node.z) - this.range)) }
75
+ isEnd (node) {
76
+ const dx = this.x - node.x; const dz = this.z - node.z
77
+ return dx * dx + dz * dz <= this.rangeSq
78
+ }
79
+ }
80
+
81
+ class GoalY extends Goal {
82
+ constructor (y) {
83
+ super()
84
+ this.y = Math.floor(y)
85
+ }
86
+
87
+ heuristic (node) { return estimate(0, this.y - node.y, 0) }
88
+ isEnd (node) { return node.y === this.y }
89
+ }
90
+
91
+ // stand next to a block (any of the 6 neighbours, or on top of it)
92
+ class GoalGetToBlock extends Goal {
93
+ constructor (x, y, z) {
94
+ super()
95
+ this.x = Math.floor(x); this.y = Math.floor(y); this.z = Math.floor(z)
96
+ }
97
+
98
+ heuristic (node) {
99
+ const dx = this.x - node.x; const dy = this.y - node.y; const dz = this.z - node.z
100
+ return Math.max(0, estimate(dx, dy, dz) - COST.walk)
101
+ }
102
+
103
+ isEnd (node) {
104
+ return Math.abs(this.x - node.x) + Math.abs(this.y - node.y) + Math.abs(this.z - node.z) === 1
105
+ }
106
+ }
107
+
108
+ // a face of the block can be seen and reached from the eyes (survival 4.5 blocks)
109
+ class GoalLookAtBlock extends Goal {
110
+ constructor (pos, world, options = {}) {
111
+ super()
112
+ this.x = Math.floor(pos.x); this.y = Math.floor(pos.y); this.z = Math.floor(pos.z)
113
+ this.world = world
114
+ this.reach = options.reach ?? 4.5
115
+ this.eyeHeight = options.eyeHeight ?? 1.62
116
+ this.reachSq = this.reach * this.reach
117
+ }
118
+
119
+ visible (node) {
120
+ if (!this.world?.raycast) return true
121
+ const { Vec3 } = require('vec3')
122
+ const eye = new Vec3(node.x + 0.5, (node.floor ?? node.y) + this.eyeHeight, node.z + 0.5)
123
+ const faces = [[0.5, 1.001, 0.5], [0.5, -0.001, 0.5], [-0.001, 0.5, 0.5], [1.001, 0.5, 0.5], [0.5, 0.5, -0.001], [0.5, 0.5, 1.001]]
124
+ for (const [fx, fy, fz] of faces) {
125
+ const target = new Vec3(this.x + fx, this.y + fy, this.z + fz)
126
+ const dir = target.minus(eye)
127
+ const length = dir.norm()
128
+ if (length > this.reach) continue
129
+ const hit = this.world.raycast(eye, dir.normalize(), length + 0.01)
130
+ if (hit && hit.position.x === this.x && hit.position.y === this.y && hit.position.z === this.z) return true
131
+ }
132
+ return false
133
+ }
134
+
135
+ heuristic (node) {
136
+ const d = estimate(this.x - node.x, this.y - node.y, this.z - node.z)
137
+ return Math.max(0, d - (this.reach - 1) * COST.walk)
138
+ }
139
+
140
+ isEnd (node) {
141
+ const dx = this.x + 0.5 - (node.x + 0.5)
142
+ const dy = this.y + 0.5 - (node.y + this.eyeHeight)
143
+ const dz = this.z + 0.5 - (node.z + 0.5)
144
+ if (node.x === this.x && node.z === this.z && (node.y === this.y || node.y + 1 === this.y)) return false
145
+ return dx * dx + dy * dy + dz * dz <= this.reachSq && this.visible(node)
146
+ }
147
+ }
148
+
149
+ class GoalCompositeAny extends Goal {
150
+ constructor (goals = []) {
151
+ super()
152
+ this.goals = goals
153
+ }
154
+
155
+ push (goal) { this.goals.push(goal) }
156
+ heuristic (node) { return Math.min(...this.goals.map(g => g.heuristic(node))) }
157
+ isEnd (node) { return this.goals.some(g => g.isEnd(node)) }
158
+ hasChanged () { return this.goals.some(g => g.hasChanged()) }
159
+ isValid () { return this.goals.every(g => g.isValid()) }
160
+ }
161
+
162
+ class GoalCompositeAll extends Goal {
163
+ constructor (goals = []) {
164
+ super()
165
+ this.goals = goals
166
+ }
167
+
168
+ push (goal) { this.goals.push(goal) }
169
+ heuristic (node) { return Math.max(...this.goals.map(g => g.heuristic(node))) }
170
+ isEnd (node) { return this.goals.every(g => g.isEnd(node)) }
171
+ hasChanged () { return this.goals.some(g => g.hasChanged()) }
172
+ isValid () { return this.goals.every(g => g.isValid()) }
173
+ }
174
+
175
+ class GoalInvert extends Goal {
176
+ constructor (goal) {
177
+ super()
178
+ this.goal = goal
179
+ }
180
+
181
+ heuristic (node) { return -this.goal.heuristic(node) }
182
+ isEnd (node) { return !this.goal.isEnd(node) }
183
+ hasChanged () { return this.goal.hasChanged() }
184
+ isValid () { return this.goal.isValid() }
185
+ }
186
+
187
+ // stay within range of an entity; the path is refreshed when it moves away from where it was planned to
188
+ class GoalFollow extends Goal {
189
+ constructor (entity, range) {
190
+ super()
191
+ this.entity = entity
192
+ this.range = range
193
+ this.rangeSq = range * range
194
+ this.update()
195
+ }
196
+
197
+ update () {
198
+ const p = this.entity.position
199
+ this.x = Math.floor(p.x); this.y = Math.floor(p.y); this.z = Math.floor(p.z)
200
+ }
201
+
202
+ heuristic (node) {
203
+ const d = estimate(this.x - node.x, this.y - node.y, this.z - node.z)
204
+ return Math.max(0, d - this.range * COST.walk)
205
+ }
206
+
207
+ isEnd (node) {
208
+ const dx = this.x - node.x; const dy = this.y - node.y; const dz = this.z - node.z
209
+ return dx * dx + dy * dy + dz * dz <= this.rangeSq
210
+ }
211
+
212
+ hasChanged () {
213
+ const p = this.entity.position
214
+ const x = Math.floor(p.x); const y = Math.floor(p.y); const z = Math.floor(p.z)
215
+ if (x === this.x && y === this.y && z === this.z) return false
216
+ this.x = x; this.y = y; this.z = z
217
+ return true
218
+ }
219
+
220
+ isValid () { return this.entity != null && this.entity.isValid !== false }
221
+ }
222
+
223
+ module.exports = {
224
+ Goal,
225
+ GoalBlock,
226
+ GoalNear,
227
+ GoalXZ,
228
+ GoalNearXZ,
229
+ GoalY,
230
+ GoalGetToBlock,
231
+ GoalLookAtBlock,
232
+ GoalCompositeAny,
233
+ GoalCompositeAll,
234
+ GoalInvert,
235
+ GoalFollow
236
+ }