spectra-physics 1.2.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 +30 -0
- package/index.js +1 -0
- package/lib/client/index.js +4 -0
- package/lib/client/simulation.js +35 -0
- package/lib/engine/player/index.js +11 -0
- 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 +4 -3
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// How the pathfinder sees blocks: where a player can stand (with slabs, stairs and carpets), whether its
|
|
3
|
+
// body fits, water and climbables, what hurts, and what can be dug and how long that takes.
|
|
4
|
+
|
|
5
|
+
const { COST } = require('./costs')
|
|
6
|
+
|
|
7
|
+
const PLAYER_HEIGHT = 1.8
|
|
8
|
+
const STEP = 0.6
|
|
9
|
+
const MAX_FLOOR = 1 // never stand on fences and walls (1.5 high)
|
|
10
|
+
|
|
11
|
+
// entering these hurts, traps or slows the player for a long time
|
|
12
|
+
const DANGER = new Set([
|
|
13
|
+
'lava', 'fire', 'soul_fire', 'cactus', 'sweet_berry_bush', 'wither_rose', 'campfire', 'soul_campfire',
|
|
14
|
+
'magma_block', 'powder_snow', 'nether_portal', 'end_portal', 'end_gateway', 'pointed_dripstone'
|
|
15
|
+
])
|
|
16
|
+
const SLOW = new Set(['cobweb', 'soul_sand', 'honey_block', 'web'])
|
|
17
|
+
const NO_BREAK = new Set([
|
|
18
|
+
'chest', 'trapped_chest', 'ender_chest', 'barrel', 'spawner', 'trial_spawner', 'vault', 'beacon',
|
|
19
|
+
'furnace', 'blast_furnace', 'smoker', 'hopper', 'dispenser', 'dropper', 'brewing_stand', 'lectern'
|
|
20
|
+
])
|
|
21
|
+
const FALLING = /^(sand|red_sand|gravel|suspicious_sand|suspicious_gravel|.*_concrete_powder|anvil|chipped_anvil|damaged_anvil|pointed_dripstone|scaffolding)$/
|
|
22
|
+
|
|
23
|
+
class Terrain {
|
|
24
|
+
constructor (world, options = {}) {
|
|
25
|
+
this.world = world
|
|
26
|
+
this.registry = world.registry
|
|
27
|
+
this.Block = world.Block
|
|
28
|
+
this.options = options
|
|
29
|
+
this.traits = []
|
|
30
|
+
this.digTicks = new Map()
|
|
31
|
+
this.tools = [null]
|
|
32
|
+
this.effects = {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// the inventory and effects at planning time decide which blocks are worth digging
|
|
36
|
+
prepare (bot) {
|
|
37
|
+
this.digTicks.clear()
|
|
38
|
+
const seen = new Set()
|
|
39
|
+
this.tools = [null]
|
|
40
|
+
for (const item of bot.inventory?.items?.() ?? []) {
|
|
41
|
+
if (seen.has(item.type)) continue
|
|
42
|
+
seen.add(item.type)
|
|
43
|
+
this.tools.push(item)
|
|
44
|
+
}
|
|
45
|
+
this.effects = bot.entity?.effects ?? {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
trait (stateId) {
|
|
49
|
+
let t = this.traits[stateId]
|
|
50
|
+
if (t) return t
|
|
51
|
+
const info = this.world.info(stateId)
|
|
52
|
+
let top = 0
|
|
53
|
+
for (const s of info.shapes) top = Math.max(top, s[4])
|
|
54
|
+
const block = this.registry.blocksByStateId?.[stateId]
|
|
55
|
+
// ladders and vines are thin panels against the wall: the body fits beside them
|
|
56
|
+
t = {
|
|
57
|
+
name: info.name,
|
|
58
|
+
empty: info.shapes.length === 0 || info.climbable || info.isLadder,
|
|
59
|
+
top,
|
|
60
|
+
water: info.fluid?.type === 'water' && !info.shapes.length,
|
|
61
|
+
waterlogged: info.fluid?.type === 'water',
|
|
62
|
+
lava: info.fluid?.type === 'lava',
|
|
63
|
+
climbable: info.climbable || info.isLadder,
|
|
64
|
+
danger: DANGER.has(info.name) || info.fluid?.type === 'lava',
|
|
65
|
+
slow: SLOW.has(info.name),
|
|
66
|
+
falls: FALLING.test(info.name),
|
|
67
|
+
breakable: !!block && block.diggable !== false && (block.hardness ?? 0) >= 0 && !NO_BREAK.has(info.name) && !info.fluid,
|
|
68
|
+
stateId
|
|
69
|
+
}
|
|
70
|
+
this.traits[stateId] = t
|
|
71
|
+
return t
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// null when the chunk is not loaded
|
|
75
|
+
at (x, y, z) {
|
|
76
|
+
const s = this.world.stateAt(x, y, z)
|
|
77
|
+
if (s == null) return null
|
|
78
|
+
return this.trait(s)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ticks to break a block with the best tool in the inventory
|
|
82
|
+
digCost (t) {
|
|
83
|
+
if (!t.breakable) return Infinity
|
|
84
|
+
let ticks = this.digTicks.get(t.stateId)
|
|
85
|
+
if (ticks !== undefined) return ticks
|
|
86
|
+
ticks = Infinity
|
|
87
|
+
let block
|
|
88
|
+
try { block = this.Block.fromStateId(t.stateId, 0) } catch { block = null }
|
|
89
|
+
if (block) {
|
|
90
|
+
const effects = this.effects
|
|
91
|
+
for (const tool of this.tools) {
|
|
92
|
+
let enchants = []
|
|
93
|
+
try { enchants = tool?.enchants ?? [] } catch { enchants = [] }
|
|
94
|
+
const ms = block.digTime(tool ? tool.type : null, false, false, false, Array.isArray(enchants) ? enchants : [], effects)
|
|
95
|
+
if (ms < Infinity) ticks = Math.min(ticks, Math.ceil(ms / 50))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (this.options.maxDigTicks != null && ticks > this.options.maxDigTicks) ticks = Infinity
|
|
99
|
+
this.digTicks.set(t.stateId, ticks)
|
|
100
|
+
return ticks
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// feet height when standing with the feet in block y, or null
|
|
104
|
+
floorAt (x, y, z) {
|
|
105
|
+
const feet = this.at(x, y, z)
|
|
106
|
+
if (!feet || feet.danger) return null
|
|
107
|
+
if (!feet.empty) {
|
|
108
|
+
if (feet.top <= 0.5) return y + feet.top
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
const below = this.at(x, y - 1, z)
|
|
112
|
+
if (!below || below.danger || below.empty || below.top <= 0.5 || below.top > MAX_FLOOR) return null
|
|
113
|
+
return y - 1 + below.top
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// the floor the block under the feet would give once the feet and head blocks are dug out
|
|
117
|
+
floorUnder (x, y, z) {
|
|
118
|
+
const below = this.at(x, y - 1, z)
|
|
119
|
+
if (!below || below.danger || below.empty || below.top <= 0.5 || below.top > MAX_FLOOR) return null
|
|
120
|
+
return y - 1 + below.top
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// the body fits between `floor` and floor + 1.8 in column x/z; blocks in the way are added to `dig`
|
|
124
|
+
bodyFits (x, floor, z, dig) {
|
|
125
|
+
const y0 = Math.floor(floor)
|
|
126
|
+
const y1 = Math.floor(floor + PLAYER_HEIGHT - 1e-6)
|
|
127
|
+
for (let y = y0; y <= y1; y++) {
|
|
128
|
+
const t = this.at(x, y, z)
|
|
129
|
+
if (!t) return false
|
|
130
|
+
if (t.danger) return false
|
|
131
|
+
if (t.empty) continue
|
|
132
|
+
if (y === y0 && y + t.top <= floor + 1e-6) continue // what we stand on
|
|
133
|
+
if (!dig || !this.canDigAt(x, y, z, t)) return false
|
|
134
|
+
dig.push([x, y, z, t])
|
|
135
|
+
}
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// digging must not let liquid or a falling block into the hole
|
|
140
|
+
canDigAt (x, y, z, t) {
|
|
141
|
+
if (!this.options.canDig || !t.breakable) return false
|
|
142
|
+
if (this.digCost(t) === Infinity) return false
|
|
143
|
+
const above = this.at(x, y + 1, z)
|
|
144
|
+
if (!above || above.falls || above.lava || above.water) return false
|
|
145
|
+
for (const [dx, dz] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
146
|
+
const side = this.at(x + dx, y, z + dz)
|
|
147
|
+
if (!side || side.lava || side.water) return false
|
|
148
|
+
}
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
isWater (x, y, z) {
|
|
153
|
+
const t = this.at(x, y, z)
|
|
154
|
+
return !!t && t.water
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
isClimbable (x, y, z) {
|
|
158
|
+
const t = this.at(x, y, z)
|
|
159
|
+
return !!t && t.climbable
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// extra ticks for standing somewhere slow
|
|
163
|
+
floorPenalty (x, floor, z) {
|
|
164
|
+
const t = this.at(x, Math.floor(floor) - (floor % 1 === 0 ? 1 : 0), z)
|
|
165
|
+
return t && t.slow ? COST.avoid : 0
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { Terrain, PLAYER_HEIGHT, STEP }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectra-physics",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Vanilla-accurate client physics and
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Vanilla-accurate client physics, movement and pathfinding for mineflayer (1.16.5 - 26.1)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"index.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"physics",
|
|
19
19
|
"movement",
|
|
20
20
|
"bot",
|
|
21
|
-
"prismarine"
|
|
21
|
+
"prismarine",
|
|
22
|
+
"pathfinder"
|
|
22
23
|
],
|
|
23
24
|
"author": "trqxyz",
|
|
24
25
|
"license": "GPL-3.0-only",
|