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.
- package/README.md +55 -6
- package/index.js +1 -0
- 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 +24 -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/simulation.js +35 -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 +30 -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/lib/pathfinder/astar.js +167 -0
- package/lib/pathfinder/costs.js +21 -0
- package/lib/pathfinder/digger.js +132 -0
- package/lib/pathfinder/goals.js +236 -0
- package/lib/pathfinder/index.js +434 -0
- package/lib/pathfinder/moves.js +223 -0
- package/lib/pathfinder/terrain.js +169 -0
- package/package.json +5 -3
|
@@ -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
|
+
}
|