spectra-physics 1.0.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/LICENSE +674 -0
- package/README.md +168 -0
- package/index.js +27 -0
- package/lib/engine/aabb.js +46 -0
- package/lib/engine/boat.js +137 -0
- package/lib/engine/collision.js +124 -0
- package/lib/engine/mth.js +47 -0
- package/lib/engine/physics.js +780 -0
- package/lib/engine/version.js +55 -0
- package/lib/engine/world.js +162 -0
- package/lib/plugin.js +574 -0
- package/package.json +51 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Version switches for client behaviour that differs between 1.16.5 and 26.x.
|
|
3
|
+
// Uses the protocol version number so "1.21.3" (768) == "1.21.2" etc.
|
|
4
|
+
|
|
5
|
+
const PV = {
|
|
6
|
+
'1.16.5': 754,
|
|
7
|
+
'1.17': 755,
|
|
8
|
+
'1.19.4': 762,
|
|
9
|
+
'1.20': 763,
|
|
10
|
+
'1.20.2': 764,
|
|
11
|
+
'1.20.3': 765,
|
|
12
|
+
'1.20.5': 766,
|
|
13
|
+
'1.21': 767,
|
|
14
|
+
'1.21.2': 768,
|
|
15
|
+
'1.21.4': 769,
|
|
16
|
+
'1.21.5': 770,
|
|
17
|
+
'1.21.6': 771,
|
|
18
|
+
'1.21.9': 773
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function versionFlags (registry) {
|
|
22
|
+
const pv = registry.version['>='] ? registry.version.version : registry.version
|
|
23
|
+
const at = (name) => pv >= PV[name]
|
|
24
|
+
return {
|
|
25
|
+
protocol: pv,
|
|
26
|
+
name: registry.version.minecraftVersion,
|
|
27
|
+
// login / configuration
|
|
28
|
+
configPhase: at('1.20.2'), // brand + client information are sent in the configuration phase
|
|
29
|
+
// movement packets
|
|
30
|
+
movementFlags: at('1.21.2'), // onGround + horizontalCollision bitflags in move packets
|
|
31
|
+
tickEnd: at('1.21.2'), // ServerboundClientTickEndPacket after every client tick
|
|
32
|
+
playerInputPacket: at('1.21.2'), // ServerboundPlayerInputPacket sent on key change (replaces steer_vehicle)
|
|
33
|
+
shiftInInput: at('1.21.6'), // no more PRESS/RELEASE_SHIFT_KEY player commands
|
|
34
|
+
clientLoaded: at('1.21.4'), // player does not tick until the level screen closes, then sends player_loaded
|
|
35
|
+
posRotBeforeConfirm: pv === PV['1.21.2'], // 1.21.2/1.21.3 answer teleports with pos-rot *before* accept-teleportation
|
|
36
|
+
relativeTeleportVelocity: at('1.21.2'), // teleport packet carries delta movement + delta flags
|
|
37
|
+
lpVelocity: at('1.21.9'), // entity_velocity uses lpVec3 (already blocks/tick)
|
|
38
|
+
// physics
|
|
39
|
+
posFromBoundingBox: !at('1.17'), // 1.16 recalculates the position from the bounding box centre
|
|
40
|
+
flyingSpeedLive: at('1.19.4'), // Player#getFlyingSpeed() instead of the lagging flyingSpeed field
|
|
41
|
+
minorCollision: at('1.19.4'), // sprinting survives "minor" horizontal collisions
|
|
42
|
+
supportingBlock: at('1.20'), // mainSupportingBlockPos drives the block below
|
|
43
|
+
attributes2005: at('1.20.5'), // gravity / step_height / jump_strength attributes
|
|
44
|
+
jumpKeepsHigherY: at('1.21'),
|
|
45
|
+
modernStepUp: at('1.21'), // collectCandidateStepUpHeights step algorithm
|
|
46
|
+
normalizedInput: at('1.21.2'), // KeyboardInput#moveVector is normalized
|
|
47
|
+
modernBackOff: at('1.21.2'), // canFallAtLeast based sneak edge back-off
|
|
48
|
+
squareInput: at('1.21.5'), // modifyInputSpeedForSquareMovement
|
|
49
|
+
fallDistanceDouble: at('1.21.5'),
|
|
50
|
+
// vehicles
|
|
51
|
+
moveVehicleOnGround: at('1.21.2')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { versionFlags, PV }
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Block / fluid access layer for the vanilla physics port.
|
|
3
|
+
// Reads mineflayer's world (prismarine-world) and caches per-stateId block traits.
|
|
4
|
+
|
|
5
|
+
const { f32 } = require('./mth')
|
|
6
|
+
|
|
7
|
+
const CLIMBABLE = new Set([
|
|
8
|
+
'ladder', 'vine', 'scaffolding', 'weeping_vines', 'weeping_vines_plant',
|
|
9
|
+
'twisting_vines', 'twisting_vines_plant', 'cave_vines', 'cave_vines_plant'
|
|
10
|
+
])
|
|
11
|
+
const WATER_SOURCES = new Set(['seagrass', 'tall_seagrass', 'kelp', 'kelp_plant', 'bubble_column'])
|
|
12
|
+
const FRICTION = { ice: 0.98, packed_ice: 0.98, frosted_ice: 0.98, blue_ice: 0.989, slime_block: 0.8, slime: 0.8 }
|
|
13
|
+
const SPEED_FACTOR = { soul_sand: 0.4, honey_block: 0.4 }
|
|
14
|
+
const JUMP_FACTOR = { honey_block: 0.5 }
|
|
15
|
+
|
|
16
|
+
const FULL = [0, 0, 0, 1, 1, 1]
|
|
17
|
+
|
|
18
|
+
class WorldView {
|
|
19
|
+
constructor (bot) {
|
|
20
|
+
this.bot = bot
|
|
21
|
+
this.registry = bot.registry
|
|
22
|
+
this.Block = require('prismarine-block')(bot.registry)
|
|
23
|
+
this.cache = new Map()
|
|
24
|
+
this.pos = { x: 0, y: 0, z: 0 }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get minY () { return this.bot.game?.minY ?? 0 }
|
|
28
|
+
get maxY () { return this.minY + (this.bot.game?.height ?? 256) }
|
|
29
|
+
|
|
30
|
+
hasChunk (bx, bz) {
|
|
31
|
+
return !!this.bot.world.getColumn(Math.floor(bx) >> 4, Math.floor(bz) >> 4)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// returns null when the column is not loaded
|
|
35
|
+
stateAt (x, y, z) {
|
|
36
|
+
const col = this.bot.world.getColumn(x >> 4, z >> 4)
|
|
37
|
+
if (!col) return null
|
|
38
|
+
if (y < this.minY || y >= this.maxY) return 0
|
|
39
|
+
this.pos.x = x & 15; this.pos.y = y; this.pos.z = z & 15
|
|
40
|
+
return col.getBlockStateId(this.pos)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
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)
|
|
48
|
+
return i
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
blockAt (x, y, z) {
|
|
52
|
+
const s = this.stateAt(x, y, z)
|
|
53
|
+
return this.info(s ?? 0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_build (stateId) {
|
|
57
|
+
let b
|
|
58
|
+
try { b = this.Block.fromStateId(stateId, 0) } catch (e) { b = null }
|
|
59
|
+
const name = b?.name ?? 'air'
|
|
60
|
+
const props = (b && b.getProperties) ? b.getProperties() : {}
|
|
61
|
+
const shapes = (b && Array.isArray(b.shapes)) ? b.shapes.filter(s => s && s.length === 6) : []
|
|
62
|
+
const info = {
|
|
63
|
+
stateId,
|
|
64
|
+
name,
|
|
65
|
+
props,
|
|
66
|
+
shapes,
|
|
67
|
+
isAir: name === 'air' || name === 'cave_air' || name === 'void_air',
|
|
68
|
+
friction: f32(FRICTION[name] ?? 0.6),
|
|
69
|
+
speedFactor: f32(SPEED_FACTOR[name] ?? 1.0),
|
|
70
|
+
jumpFactor: f32(JUMP_FACTOR[name] ?? 1.0),
|
|
71
|
+
climbable: CLIMBABLE.has(name),
|
|
72
|
+
isTrapdoor: name.endsWith('_trapdoor'),
|
|
73
|
+
isLadder: name === 'ladder',
|
|
74
|
+
isSlime: name === 'slime_block' || name === 'slime',
|
|
75
|
+
isBed: name.endsWith('_bed') || name === 'bed',
|
|
76
|
+
isHoney: name === 'honey_block',
|
|
77
|
+
isCobweb: name === 'cobweb' || name === 'web',
|
|
78
|
+
isBerryBush: name === 'sweet_berry_bush',
|
|
79
|
+
isPowderSnow: name === 'powder_snow',
|
|
80
|
+
isScaffolding: name === 'scaffolding',
|
|
81
|
+
isFenceLike: name.endsWith('_fence') || name.endsWith('_wall') || name.endsWith('_fence_gate'),
|
|
82
|
+
fullCube: shapes.length === 1 && shapes[0].every((v, k) => v === FULL[k]),
|
|
83
|
+
fluid: null
|
|
84
|
+
}
|
|
85
|
+
// fluids
|
|
86
|
+
if (name === 'water' || name === 'lava' || name === 'flowing_water' || name === 'flowing_lava') {
|
|
87
|
+
const level = Number(props.level ?? 0)
|
|
88
|
+
const falling = level >= 8
|
|
89
|
+
const amount = falling ? 8 : (level === 0 ? 8 : 8 - level)
|
|
90
|
+
info.fluid = { type: name.includes('water') ? 'water' : 'lava', amount, falling, source: level === 0 }
|
|
91
|
+
} else if (props.waterlogged === true || props.waterlogged === 'true' || WATER_SOURCES.has(name)) {
|
|
92
|
+
info.fluid = { type: 'water', amount: 8, falling: false, source: true }
|
|
93
|
+
}
|
|
94
|
+
return info
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fluidAt (x, y, z) {
|
|
98
|
+
const s = this.stateAt(x, y, z)
|
|
99
|
+
if (s == null) return null
|
|
100
|
+
return this.info(s).fluid
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// FluidState.getOwnHeight()
|
|
104
|
+
static ownHeight (fluid) {
|
|
105
|
+
return fluid ? f32(fluid.amount / 9.0) : 0
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// FluidState.getHeight(level, pos)
|
|
109
|
+
fluidHeight (x, y, z, fluid) {
|
|
110
|
+
if (!fluid) return 0
|
|
111
|
+
const above = this.fluidAt(x, y + 1, z)
|
|
112
|
+
if (above && above.type === fluid.type) return 1.0
|
|
113
|
+
return WorldView.ownHeight(fluid)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Collect every collision box that could touch `bb` (BlockCollisions semantics:
|
|
117
|
+
// unloaded chunks collide with nothing, outside build height is air).
|
|
118
|
+
collisionBoxes (bb, out = []) {
|
|
119
|
+
const x0 = Math.floor(bb.minX - 1.0e-7) - 1; const x1 = Math.floor(bb.maxX + 1.0e-7) + 1
|
|
120
|
+
const y0 = Math.floor(bb.minY - 1.0e-7) - 1; const y1 = Math.floor(bb.maxY + 1.0e-7) + 1
|
|
121
|
+
const z0 = Math.floor(bb.minZ - 1.0e-7) - 1; const z1 = Math.floor(bb.maxZ + 1.0e-7) + 1
|
|
122
|
+
for (let x = x0; x <= x1; x++) {
|
|
123
|
+
for (let z = z0; z <= z1; z++) {
|
|
124
|
+
const col = this.bot.world.getColumn(x >> 4, z >> 4)
|
|
125
|
+
if (!col) continue
|
|
126
|
+
for (let y = y0; y <= y1; y++) {
|
|
127
|
+
if (y < this.minY || y >= this.maxY) continue
|
|
128
|
+
this.pos.x = x & 15; this.pos.y = y; this.pos.z = z & 15
|
|
129
|
+
const info = this.info(col.getBlockStateId(this.pos))
|
|
130
|
+
const shapes = info.shapes
|
|
131
|
+
for (let k = 0; k < shapes.length; k++) {
|
|
132
|
+
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])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
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
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
containsAnyLiquid (bb) {
|
|
148
|
+
const x0 = Math.floor(bb.minX); const x1 = Math.ceil(bb.maxX)
|
|
149
|
+
const y0 = Math.floor(bb.minY); const y1 = Math.ceil(bb.maxY)
|
|
150
|
+
const z0 = Math.floor(bb.minZ); const z1 = Math.ceil(bb.maxZ)
|
|
151
|
+
for (let x = x0; x < x1; x++) {
|
|
152
|
+
for (let y = y0; y < y1; y++) {
|
|
153
|
+
for (let z = z0; z < z1; z++) {
|
|
154
|
+
if (this.fluidAt(x, y, z)) return true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = WorldView
|