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
package/lib/client/riding.js
CHANGED
|
@@ -17,20 +17,23 @@ function riderOffset (vehicle) {
|
|
|
17
17
|
function installRiding (ctx) {
|
|
18
18
|
const { bot, V, player, state, world } = ctx
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// the boat ticks before its rider, so it steers with the keys the rider set on the previous tick (Boat#setInput)
|
|
21
|
+
function tickBoat (vehicle) {
|
|
21
22
|
if (!state.boat || state.boat.entityId !== vehicle.id) {
|
|
22
23
|
state.boat = new BoatPhysics(world, V, { position: vehicle.position, yawDegrees: f32(conv.toNotchianYaw(vehicle.yaw)) })
|
|
23
24
|
state.boat.entityId = vehicle.id
|
|
25
|
+
state.boat.input = { forward: false, back: false, left: false, right: false }
|
|
24
26
|
}
|
|
25
|
-
state.boat.tick(
|
|
27
|
+
state.boat.tick(state.boat.input)
|
|
26
28
|
ctx.send('steer_boat', { leftPaddle: state.boat.paddle[0], rightPaddle: state.boat.paddle[1] })
|
|
27
29
|
vehicle.position.set(state.boat.pos.x, state.boat.pos.y, state.boat.pos.z)
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
ctx.ridingTick = (vehicle) => {
|
|
33
|
+
ctx.flushActions()
|
|
31
34
|
const keys = ctx.keys()
|
|
32
35
|
const controlling = vehicle.passengers?.[0] === bot.entity
|
|
33
|
-
if (isBoat(vehicle) && controlling) tickBoat(vehicle
|
|
36
|
+
if (isBoat(vehicle) && controlling) tickBoat(vehicle)
|
|
34
37
|
else state.boat = null
|
|
35
38
|
|
|
36
39
|
player.riding = true
|
|
@@ -44,6 +47,7 @@ function installRiding (ctx) {
|
|
|
44
47
|
player.yRot = f32(player.yRot + clamp(offset, -105, 105) - offset)
|
|
45
48
|
}
|
|
46
49
|
ctx.syncEntity()
|
|
50
|
+
if (state.boat) state.boat.input = { forward: keys.forward, back: keys.back, left: keys.left, right: keys.right }
|
|
47
51
|
|
|
48
52
|
if (!V.playerInputPacket) {
|
|
49
53
|
ctx.sendLook()
|
package/lib/client/server.js
CHANGED
|
@@ -85,6 +85,19 @@ function installServerHandlers (ctx) {
|
|
|
85
85
|
state.boat = null
|
|
86
86
|
bot.emit('dismount', vehicle)
|
|
87
87
|
})
|
|
88
|
+
|
|
89
|
+
// the passenger list replaces the old one; mineflayer only appends and only leaves on vehicle id -1,
|
|
90
|
+
// which the server never sends, so a bot stays "mounted" after every dismount
|
|
91
|
+
client.on('set_passengers', ({ entityId, passengers }) => {
|
|
92
|
+
const vehicle = bot.entities[entityId]
|
|
93
|
+
if (vehicle) vehicle.passengers = passengers.map(id => bot.entities[id]).filter(Boolean)
|
|
94
|
+
const current = bot.vehicle
|
|
95
|
+
if (!current || current.id !== entityId || passengers.includes(bot.entity?.id)) return
|
|
96
|
+
bot.vehicle = null
|
|
97
|
+
if (bot.entity) bot.entity.vehicle = null
|
|
98
|
+
state.boat = null
|
|
99
|
+
bot.emit('dismount', current)
|
|
100
|
+
})
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
module.exports = { installServerHandlers }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// bot.physics.simulatePlayer for plugins written against prismarine-physics (mineflayer-pathfinder plans
|
|
3
|
+
// its jumps with it): the prediction runs on this engine, so plans match how the bot really moves.
|
|
4
|
+
|
|
5
|
+
const conv = require('mineflayer/lib/conversions')
|
|
6
|
+
|
|
7
|
+
function installSimulation (ctx) {
|
|
8
|
+
const { bot, player } = ctx
|
|
9
|
+
const running = new WeakMap()
|
|
10
|
+
|
|
11
|
+
bot.physics.simulatePlayer = (state) => {
|
|
12
|
+
let sim = running.get(state)
|
|
13
|
+
if (!sim) {
|
|
14
|
+
sim = player.clone()
|
|
15
|
+
running.set(state, sim)
|
|
16
|
+
}
|
|
17
|
+
sim.pos = { x: state.pos.x, y: state.pos.y, z: state.pos.z }
|
|
18
|
+
sim.vel = { x: state.vel.x, y: state.vel.y, z: state.vel.z }
|
|
19
|
+
sim.onGround = !!state.onGround
|
|
20
|
+
sim.yRot = conv.toNotchianYaw(state.yaw)
|
|
21
|
+
sim.xRot = conv.toNotchianPitch(state.pitch)
|
|
22
|
+
const c = state.control ?? {}
|
|
23
|
+
sim.tick({ forward: !!c.forward, back: !!c.back, left: !!c.left, right: !!c.right, jump: !!c.jump, sprint: !!c.sprint, sneak: !!c.sneak })
|
|
24
|
+
state.pos.set(sim.pos.x, sim.pos.y, sim.pos.z)
|
|
25
|
+
state.vel.set(sim.vel.x, sim.vel.y, sim.vel.z)
|
|
26
|
+
state.onGround = sim.onGround
|
|
27
|
+
state.isInWater = sim.isInWater()
|
|
28
|
+
state.isInLava = sim.isInLava()
|
|
29
|
+
state.isCollidedHorizontally = sim.horizontalCollision
|
|
30
|
+
state.isCollidedVertically = sim.verticalCollision
|
|
31
|
+
return state
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { installSimulation }
|
package/lib/client/ticker.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
// Minecraft#tick for the local player plus the timer that drives it.
|
|
3
3
|
|
|
4
|
-
const {
|
|
4
|
+
const { startTicking, stopTicking } = require('./clock')
|
|
5
5
|
|
|
6
|
-
const TICK_MS = 50
|
|
7
|
-
const MAX_CATCH_UP = 10
|
|
8
6
|
const LOAD_TIMEOUT_TICKS = 60
|
|
9
7
|
const MAX_TURN_DELAY = 40
|
|
10
8
|
|
|
@@ -67,12 +65,14 @@ function installTicker (ctx) {
|
|
|
67
65
|
}
|
|
68
66
|
if (bot.isAlive === false) return
|
|
69
67
|
player.riding = false
|
|
68
|
+
ctx.updateItemUse()
|
|
70
69
|
// 1.17 - 1.18.1: a turn while creeping through the air can't be reported cleanly, the mouse waits a moment
|
|
71
70
|
if (state.creeping && state.turnDelay < MAX_TURN_DELAY) state.turnDelay++
|
|
72
71
|
else {
|
|
73
72
|
ctx.turn(state.turnDelay >= MAX_TURN_DELAY ? Infinity : undefined)
|
|
74
73
|
state.turnDelay = 0
|
|
75
74
|
}
|
|
75
|
+
ctx.flushActions()
|
|
76
76
|
if (bot.physicsEnabled) {
|
|
77
77
|
player.tick(ctx.keys())
|
|
78
78
|
if (player.events.abilitiesChanged) {
|
|
@@ -102,25 +102,13 @@ function installTicker (ctx) {
|
|
|
102
102
|
state.boat = null
|
|
103
103
|
playerTick()
|
|
104
104
|
}
|
|
105
|
+
ctx.flushActions()
|
|
105
106
|
if (V.tickEnd) ctx.send('tick_end', {})
|
|
106
107
|
bot.emit('physicsTick')
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
function start () {
|
|
112
|
-
if (timer) return
|
|
113
|
-
nextAt = performance.now() + TICK_MS
|
|
114
|
-
const loop = () => {
|
|
115
|
-
const now = performance.now()
|
|
116
|
-
for (let n = 0; now >= nextAt && n < MAX_CATCH_UP; n++) {
|
|
117
|
-
try { clientTick() } catch (err) { bot.emit('error', err) }
|
|
118
|
-
nextAt += TICK_MS
|
|
119
|
-
}
|
|
120
|
-
if (now - nextAt > 1000) nextAt = now + TICK_MS
|
|
121
|
-
timer = setTimeout(loop, Math.max(0, nextAt - performance.now()))
|
|
122
|
-
}
|
|
123
|
-
timer = setTimeout(loop, TICK_MS)
|
|
110
|
+
const tick = () => {
|
|
111
|
+
try { clientTick() } catch (err) { bot.emit('error', err) }
|
|
124
112
|
}
|
|
125
113
|
|
|
126
114
|
client.on('login', () => {
|
|
@@ -129,14 +117,11 @@ function installTicker (ctx) {
|
|
|
129
117
|
state.selectedSlot = 0
|
|
130
118
|
state.carriedSlot = 0
|
|
131
119
|
resetPlayer()
|
|
132
|
-
|
|
120
|
+
startTicking(tick)
|
|
133
121
|
})
|
|
134
122
|
client.on('respawn', resetPlayer)
|
|
135
123
|
client.on('start_configuration', () => { state.inLevel = false })
|
|
136
|
-
bot.on('end', () =>
|
|
137
|
-
clearTimeout(timer)
|
|
138
|
-
timer = null
|
|
139
|
-
})
|
|
124
|
+
bot.on('end', () => stopTicking(tick))
|
|
140
125
|
|
|
141
126
|
resetPlayer()
|
|
142
127
|
}
|
package/lib/engine/aabb.js
CHANGED
|
@@ -34,9 +34,11 @@ class AABB {
|
|
|
34
34
|
|
|
35
35
|
// strict positive-volume intersection (Shapes.joinIsNotEmpty(..., AND))
|
|
36
36
|
intersectsBox (b) {
|
|
37
|
-
return b[0]
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
return this.intersects(b[0], b[1], b[2], b[3], b[4], b[5])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
intersects (minX, minY, minZ, maxX, maxY, maxZ) {
|
|
41
|
+
return minX < this.maxX && maxX > this.minX && minY < this.maxY && maxY > this.minY && minZ < this.maxZ && maxZ > this.minZ
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
min (axis) { return axis === 0 ? this.minX : (axis === 1 ? this.minY : this.minZ) }
|
package/lib/engine/boat.js
CHANGED
|
@@ -18,12 +18,14 @@ class BoatPhysics {
|
|
|
18
18
|
this.yRot = entity.yawDegrees ?? 0
|
|
19
19
|
this.xRot = 0
|
|
20
20
|
this.deltaRotation = 0
|
|
21
|
-
this.status = 'IN_AIR'
|
|
22
|
-
this.oldStatus = 'IN_AIR'
|
|
23
21
|
this.landFriction = 0
|
|
24
22
|
this.waterLevel = 0
|
|
23
|
+
this.lastYd = 0
|
|
25
24
|
this.onGround = false
|
|
26
25
|
this.paddle = [false, false]
|
|
26
|
+
// the boat already ticked its status before this client took control
|
|
27
|
+
this.status = this.getStatus()
|
|
28
|
+
this.oldStatus = this.status
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
bb () {
|
|
@@ -57,6 +59,42 @@ class BoatPhysics {
|
|
|
57
59
|
return n > 0 ? f32(sum / n) : 0
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
// Boat#isUnderwater
|
|
63
|
+
underwater () {
|
|
64
|
+
const bb = this.bb()
|
|
65
|
+
const top = bb.maxY + 0.001
|
|
66
|
+
let under = false
|
|
67
|
+
for (let x = Math.floor(bb.minX); x < Math.ceil(bb.maxX); x++) {
|
|
68
|
+
for (let y = Math.floor(bb.maxY); y < Math.ceil(top); y++) {
|
|
69
|
+
for (let z = Math.floor(bb.minZ); z < Math.ceil(bb.maxZ); z++) {
|
|
70
|
+
const fl = this.world.fluidAt(x, y, z)
|
|
71
|
+
if (!fl || fl.type !== 'water' || !(top < y + this.world.fluidHeight(x, y, z, fl))) continue
|
|
72
|
+
if (!fl.source) return 'UNDER_FLOWING_WATER'
|
|
73
|
+
under = true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return under ? 'UNDER_WATER' : null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Boat#getWaterLevelAbove
|
|
81
|
+
waterLevelAbove () {
|
|
82
|
+
const bb = this.bb()
|
|
83
|
+
const top = Math.ceil(bb.maxY - this.lastYd)
|
|
84
|
+
for (let y = Math.floor(bb.maxY); y < top; y++) {
|
|
85
|
+
let level = 0
|
|
86
|
+
for (let x = Math.floor(bb.minX); x < Math.ceil(bb.maxX) && level < 1; x++) {
|
|
87
|
+
for (let z = Math.floor(bb.minZ); z < Math.ceil(bb.maxZ); z++) {
|
|
88
|
+
const fl = this.world.fluidAt(x, y, z)
|
|
89
|
+
if (fl && fl.type === 'water') level = Math.max(level, this.world.fluidHeight(x, y, z, fl))
|
|
90
|
+
if (level >= 1) break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (level < 1) return f32(y + level)
|
|
94
|
+
}
|
|
95
|
+
return top + 1
|
|
96
|
+
}
|
|
97
|
+
|
|
60
98
|
inWater () {
|
|
61
99
|
const bb = this.bb()
|
|
62
100
|
const x0 = Math.floor(bb.minX); const x1 = Math.ceil(bb.maxX)
|
|
@@ -69,7 +107,7 @@ class BoatPhysics {
|
|
|
69
107
|
for (let z = z0; z < z1; z++) {
|
|
70
108
|
const fl = this.world.fluidAt(x, y, z)
|
|
71
109
|
if (fl && fl.type === 'water') {
|
|
72
|
-
const top = y + this.world.fluidHeight(x, y, z, fl)
|
|
110
|
+
const top = f32(y + this.world.fluidHeight(x, y, z, fl))
|
|
73
111
|
this.waterLevel = Math.max(top, this.waterLevel)
|
|
74
112
|
found = found || bb.minY < top
|
|
75
113
|
}
|
|
@@ -80,6 +118,11 @@ class BoatPhysics {
|
|
|
80
118
|
}
|
|
81
119
|
|
|
82
120
|
getStatus () {
|
|
121
|
+
const under = this.underwater()
|
|
122
|
+
if (under) {
|
|
123
|
+
this.waterLevel = this.bb().maxY
|
|
124
|
+
return under
|
|
125
|
+
}
|
|
83
126
|
if (this.inWater()) return 'IN_WATER'
|
|
84
127
|
const f = this.groundFriction()
|
|
85
128
|
if (f > 0) { this.landFriction = f; return 'ON_LAND' }
|
|
@@ -89,16 +132,22 @@ class BoatPhysics {
|
|
|
89
132
|
floatBoat () {
|
|
90
133
|
let d2 = 0
|
|
91
134
|
let inv = f32(0.05)
|
|
135
|
+
let gravity = G
|
|
92
136
|
if (this.oldStatus === 'IN_AIR' && this.status !== 'IN_AIR' && this.status !== 'ON_LAND') {
|
|
93
|
-
this.
|
|
94
|
-
|
|
137
|
+
this.waterLevel = this.pos.y + BOAT_H
|
|
138
|
+
const y = this.waterLevelAbove() - BOAT_H + 0.101
|
|
139
|
+
if (this.world.noCollision(this.bb().move(0, y - this.pos.y, 0))) {
|
|
140
|
+
this.pos.y = y
|
|
141
|
+
this.vel.y = 0
|
|
142
|
+
this.lastYd = 0
|
|
143
|
+
}
|
|
95
144
|
this.status = 'IN_WATER'
|
|
96
145
|
return
|
|
97
146
|
}
|
|
98
|
-
if (this.status === 'IN_WATER') { d2 = (this.waterLevel - this.pos.y) / BOAT_H; inv = f32(0.9) } else if (this.status === 'IN_AIR') inv = f32(0.9)
|
|
147
|
+
if (this.status === 'IN_WATER') { d2 = (this.waterLevel - this.pos.y) / BOAT_H; inv = f32(0.9) } else if (this.status === 'UNDER_FLOWING_WATER') { gravity = -7.0e-4; inv = f32(0.9) } else if (this.status === 'UNDER_WATER') { d2 = f32(0.01); inv = f32(0.45) } else if (this.status === 'IN_AIR') inv = f32(0.9)
|
|
99
148
|
else if (this.status === 'ON_LAND') { inv = this.landFriction; this.landFriction = f32(this.landFriction / 2) }
|
|
100
149
|
this.vel.x *= inv
|
|
101
|
-
this.vel.y +=
|
|
150
|
+
this.vel.y += gravity
|
|
102
151
|
this.vel.z *= inv
|
|
103
152
|
this.deltaRotation = f32(this.deltaRotation * inv)
|
|
104
153
|
if (d2 > 0) this.vel.y = (this.vel.y + d2 * 0.06153846016296973) * 0.75
|
|
@@ -125,6 +174,7 @@ class BoatPhysics {
|
|
|
125
174
|
this.controlBoat(keys)
|
|
126
175
|
const collide = this.V.modernStepUp ? collideModern : collideLegacy
|
|
127
176
|
const r = collide(this.world, this.vel.x, this.vel.y, this.vel.z, this.bb(), this.onGround, 0)
|
|
177
|
+
this.lastYd = this.vel.y
|
|
128
178
|
if (r.x * r.x + r.y * r.y + r.z * r.z > 1.0e-7) { this.pos.x += r.x; this.pos.y += r.y; this.pos.z += r.z }
|
|
129
179
|
const vertical = this.vel.y !== r.y
|
|
130
180
|
this.onGround = vertical && this.vel.y < 0
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const AABB = require('../aabb')
|
|
4
3
|
const { f32 } = require('../mth')
|
|
4
|
+
const { traverseMoves, BlockList } = require('./traversal')
|
|
5
5
|
|
|
6
6
|
const F_098 = f32(0.98)
|
|
7
7
|
const F_08 = f32(0.8)
|
|
8
8
|
const F_09 = f32(0.9)
|
|
9
9
|
const F_005 = f32(0.05)
|
|
10
|
-
const F_1EM5 = f32(1.0e-5)
|
|
11
|
-
const LONG_MOVE = f32(0.99999) * f32(0.99999)
|
|
12
10
|
const HALF_WIDTH = f32(0.3)
|
|
13
11
|
|
|
14
12
|
const STUCK = {
|
|
@@ -69,6 +67,8 @@ function onInsideBlock (player, block, bx, by, bz, withVelocityEffects) {
|
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
|
|
70
|
+
const visited = new BlockList()
|
|
71
|
+
|
|
72
72
|
function forEachBlockIn (box, visit) {
|
|
73
73
|
const x0 = Math.floor(box.minX); const x1 = Math.floor(box.maxX)
|
|
74
74
|
const y0 = Math.floor(box.minY); const y1 = Math.floor(box.maxY)
|
|
@@ -94,57 +94,20 @@ function checkInsideBlocksLegacy (player) {
|
|
|
94
94
|
settleStuck(player)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
// voxel walk of the box corner between two points, used for moves longer than a block
|
|
98
|
-
function blocksAlongTravel (from, to, box, out) {
|
|
99
|
-
const dx = to.x - from.x; const dy = to.y - from.y; const dz = to.z - from.z
|
|
100
|
-
const start = { x: box.minX - dx, y: box.minY - dy, z: box.minZ - dz }
|
|
101
|
-
let cx = Math.floor(start.x); let cy = Math.floor(start.y); let cz = Math.floor(start.z)
|
|
102
|
-
const sx = Math.sign(dx); const sy = Math.sign(dy); const sz = Math.sign(dz)
|
|
103
|
-
const tx = sx === 0 ? Infinity : sx / dx
|
|
104
|
-
const ty = sy === 0 ? Infinity : sy / dy
|
|
105
|
-
const tz = sz === 0 ? Infinity : sz / dz
|
|
106
|
-
const frac = (v) => v - Math.floor(v)
|
|
107
|
-
let nx = tx * (sx > 0 ? 1 - frac(start.x) : frac(start.x))
|
|
108
|
-
let ny = ty * (sy > 0 ? 1 - frac(start.y) : frac(start.y))
|
|
109
|
-
let nz = tz * (sz > 0 ? 1 - frac(start.z) : frac(start.z))
|
|
110
|
-
const sizeX = box.maxX - box.minX; const sizeY = box.maxY - box.minY; const sizeZ = box.maxZ - box.minZ
|
|
111
|
-
for (let steps = 0; (nx <= 1 || ny <= 1 || nz <= 1) && steps <= 16; steps++) {
|
|
112
|
-
if (nx < ny) {
|
|
113
|
-
if (nx < nz) { cx += sx; nx += tx } else { cz += sz; nz += tz }
|
|
114
|
-
} else if (ny < nz) { cy += sy; ny += ty } else { cz += sz; nz += tz }
|
|
115
|
-
forEachBlockIn(new AABB(cx, cy, cz, cx + sizeX, cy + sizeY, cz + sizeZ), (x, y, z) => out.add(`${x},${y},${z}`))
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
97
|
// Entity#applyEffectsFromBlocks, 1.21.2+: after travel, along every movement made this tick
|
|
120
98
|
function applyEffectsAfterTravel (player) {
|
|
121
99
|
if (!player.V.blockEffectsAfterTravel) return
|
|
122
100
|
player.stuck = null
|
|
123
|
-
const end = {
|
|
124
|
-
const moves = player.V.blockEffectsPerAxis && player.movesThisTick.length ?
|
|
101
|
+
const end = { x: player.pos.x, y: player.pos.y, z: player.pos.z }
|
|
102
|
+
const moves = player.V.blockEffectsPerAxis && player.movesThisTick.length ? player.movesThisTick.slice() : [{ from: player.tickStart, to: end, input: null }]
|
|
125
103
|
const last = moves[moves.length - 1].to
|
|
126
|
-
if ((last.x - end.x) ** 2 + (last.y - end.y) ** 2 + (last.z - end.z) ** 2 > 9.9999994e-11) moves.push({ from: last, to: end })
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const far = lengthSq >= LONG_MOVE
|
|
134
|
-
if (player.V.blockEffectsPerAxis && lengthSq > 0) {
|
|
135
|
-
forEachBlockIn(player.bbAt(from.x, from.y, from.z).deflate(F_1EM5), (x, y, z) => keys.add(`${x},${y},${z}`))
|
|
136
|
-
}
|
|
137
|
-
if (far) blocksAlongTravel(from, to, endBox, keys)
|
|
138
|
-
for (const key of keys) {
|
|
139
|
-
if (visited.has(key)) continue
|
|
140
|
-
visited.add(key)
|
|
141
|
-
const [x, y, z] = key.split(',').map(Number)
|
|
142
|
-
const block = player.world.blockAt(x, y, z)
|
|
143
|
-
if (block.isAir) continue
|
|
144
|
-
const inEndBox = endBox.intersectsBox([x, y, z, x + 1, y + 1, z + 1])
|
|
145
|
-
onInsideBlock(player, block, x, y, z, far || inEndBox)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
104
|
+
if ((last.x - end.x) ** 2 + (last.y - end.y) ** 2 + (last.z - end.z) ** 2 > 9.9999994e-11) moves.push({ from: last, to: end, input: null })
|
|
105
|
+
visited.clear()
|
|
106
|
+
traverseMoves(player, moves, (x, y, z, applyVelocity) => {
|
|
107
|
+
const block = player.world.blockAt(x, y, z)
|
|
108
|
+
if (block.isAir || !visited.add(x, y, z)) return
|
|
109
|
+
onInsideBlock(player, block, x, y, z, applyVelocity)
|
|
110
|
+
})
|
|
148
111
|
player.movesThisTick.length = 0
|
|
149
112
|
settleStuck(player)
|
|
150
113
|
}
|
|
@@ -105,7 +105,19 @@ function updateEyes (player) {
|
|
|
105
105
|
const by = Math.floor(eyeY)
|
|
106
106
|
const bz = Math.floor(player.pos.z)
|
|
107
107
|
const fluid = player.world.fluidAt(bx, by, bz)
|
|
108
|
-
|
|
108
|
+
const top = fluid && fluid.type === 'water' ? by + player.world.fluidHeight(bx, by, bz, fluid) : -Infinity
|
|
109
|
+
// 26.1 EntityFluidInteraction counts the surface itself as inside
|
|
110
|
+
player.eyeInWater = player.V.fluidInteraction ? top >= eyeY : top > eyeY
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const lavaPush = (player) => player.env.ultraWarm() ? 0.007 : 0.0023333333333333335
|
|
114
|
+
|
|
115
|
+
// 26.1 Entity#updateFluidInteraction: water and lava together
|
|
116
|
+
function updateFluidInteraction (player) {
|
|
117
|
+
player.fluidHeight.water = 0
|
|
118
|
+
player.fluidHeight.lava = 0
|
|
119
|
+
updateWater(player)
|
|
120
|
+
touchFluid(player, 'lava', lavaPush(player))
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
// Player#updateSwimming
|
|
@@ -122,10 +134,7 @@ function updateSwimming (player) {
|
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
function updateFluids (player) {
|
|
125
|
-
player
|
|
126
|
-
player.fluidHeight.lava = 0
|
|
127
|
-
updateWater(player)
|
|
128
|
-
touchFluid(player, 'lava', player.env.ultraWarm() ? 0.007 : 0.0023333333333333335)
|
|
137
|
+
updateFluidInteraction(player)
|
|
129
138
|
updateEyes(player)
|
|
130
139
|
updateSwimming(player)
|
|
131
140
|
}
|
|
@@ -138,4 +147,4 @@ function fluidFallingAdjusted (player, gravity, falling) {
|
|
|
138
147
|
else player.vel.y = vy - gravity / 16
|
|
139
148
|
}
|
|
140
149
|
|
|
141
|
-
module.exports = { updateFluids, updateWater, fluidFallingAdjusted }
|
|
150
|
+
module.exports = { updateFluids, updateWater, updateFluidInteraction, fluidFallingAdjusted }
|
|
@@ -34,6 +34,13 @@ class PlayerPhysics {
|
|
|
34
34
|
this.world = world
|
|
35
35
|
this.V = V
|
|
36
36
|
this.env = { ...DEFAULT_ENV, ...env }
|
|
37
|
+
const player = this
|
|
38
|
+
this.collisionContext = {
|
|
39
|
+
get bottom () { return player.pos.y },
|
|
40
|
+
get fallDistance () { return player.fallDistance },
|
|
41
|
+
get walksOnPowderSnow () { return player.V.powderSnow && player.env.bootsItem() === 'leather_boots' },
|
|
42
|
+
get descending () { return player.input.shift }
|
|
43
|
+
}
|
|
37
44
|
this.reset()
|
|
38
45
|
}
|
|
39
46
|
|
|
@@ -72,6 +79,17 @@ class PlayerPhysics {
|
|
|
72
79
|
this.events = { abilitiesChanged: false }
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
// an independent copy for looking ahead: same world and environment, own state
|
|
83
|
+
clone () {
|
|
84
|
+
const copy = new PlayerPhysics(this.world, this.V, this.env)
|
|
85
|
+
for (const key of Object.keys(this)) {
|
|
86
|
+
if (key === 'world' || key === 'V' || key === 'env' || key === 'collisionContext') continue
|
|
87
|
+
const value = this[key]
|
|
88
|
+
copy[key] = value !== null && typeof value === 'object' ? structuredClone(value) : value
|
|
89
|
+
}
|
|
90
|
+
return copy
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
height () { return pose.POSES[this.pose].height }
|
|
76
94
|
eyeHeight () { return pose.POSES[this.pose].eye }
|
|
77
95
|
bbAt (x, y, z) { return pose.boxFor(this.pose, x, y, z) }
|
|
@@ -94,7 +112,18 @@ class PlayerPhysics {
|
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
tick (keys) {
|
|
97
|
-
this.
|
|
115
|
+
this.world.beginTick(this.collisionContext)
|
|
116
|
+
try {
|
|
117
|
+
this.tickInner(keys)
|
|
118
|
+
} finally {
|
|
119
|
+
this.world.endTick()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
tickInner (keys) {
|
|
124
|
+
this.tickStart.x = this.pos.x
|
|
125
|
+
this.tickStart.y = this.pos.y
|
|
126
|
+
this.tickStart.z = this.pos.z
|
|
98
127
|
this.movesThisTick.length = 0
|
|
99
128
|
this.baseTick()
|
|
100
129
|
|
|
@@ -9,6 +9,8 @@ const F_MIN_LENGTH = f32(1.0e-4)
|
|
|
9
9
|
|
|
10
10
|
const impulse = (positive, negative) => positive === negative ? 0 : (positive ? 1 : -1)
|
|
11
11
|
|
|
12
|
+
const slowedByItem = (player) => player.env.usingItem() && !player.riding
|
|
13
|
+
|
|
12
14
|
function sneakMultiplier (player) {
|
|
13
15
|
return player.V.efficiencyAttributes ? f32(player.env.attribute('sneaking_speed', 0.3)) : F_03
|
|
14
16
|
}
|
|
@@ -21,7 +23,7 @@ function legacyInput (player, left, forward, slow) {
|
|
|
21
23
|
const k = sneakMultiplier(player)
|
|
22
24
|
x = f32(x * k); z = f32(z * k)
|
|
23
25
|
}
|
|
24
|
-
if (player
|
|
26
|
+
if (slowedByItem(player)) { x = f32(x * F_02); z = f32(z * F_02) }
|
|
25
27
|
return [f32(x * F_098), f32(z * F_098)]
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -31,7 +33,7 @@ function modernInput (player, left, forward, slow) {
|
|
|
31
33
|
if (length < F_MIN_LENGTH) return [0, 0]
|
|
32
34
|
let x = f32(f32(left / length) * F_098)
|
|
33
35
|
let z = f32(f32(forward / length) * F_098)
|
|
34
|
-
if (player
|
|
36
|
+
if (slowedByItem(player)) { x = f32(x * F_02); z = f32(z * F_02) }
|
|
35
37
|
if (slow) {
|
|
36
38
|
const k = sneakMultiplier(player)
|
|
37
39
|
x = f32(x * k); z = f32(z * k)
|
|
@@ -54,6 +56,7 @@ function readKeys (player, keys, slow) {
|
|
|
54
56
|
const [xxa, zza] = player.V.inputModel === 'modern'
|
|
55
57
|
? modernInput(player, left, forward, slow)
|
|
56
58
|
: legacyInput(player, left, forward, slow)
|
|
59
|
+
if (slowedByItem(player)) player.sprintTriggerTime = 0
|
|
57
60
|
player.input = {
|
|
58
61
|
left: xxa,
|
|
59
62
|
forward: zza,
|
|
@@ -4,7 +4,7 @@ const { f32, sin, cos, degToRadF, equal } = require('../mth')
|
|
|
4
4
|
const { collideLegacy, collideModern } = require('../collision')
|
|
5
5
|
const { onPos, speedFactor, updateSupportingBlock } = require('./blocks')
|
|
6
6
|
const { checkInsideBlocksLegacy } = require('./effects')
|
|
7
|
-
const { updateWater } = require('./fluids')
|
|
7
|
+
const { updateWater, updateFluidInteraction } = require('./fluids')
|
|
8
8
|
|
|
9
9
|
const F_066 = f32(0.66)
|
|
10
10
|
const F_1EM5 = f32(1.0e-5)
|
|
@@ -78,9 +78,12 @@ function zeroBlockedAxes (player, vx, vz, r) {
|
|
|
78
78
|
return
|
|
79
79
|
}
|
|
80
80
|
// 1.14 - 1.18.1: the second write restores X when both axes are blocked
|
|
81
|
-
const
|
|
82
|
-
if (vx !== r.x) player.vel =
|
|
83
|
-
if (vz !== r.z)
|
|
81
|
+
const x = player.vel.x
|
|
82
|
+
if (vx !== r.x) player.vel.x = 0
|
|
83
|
+
if (vz !== r.z) {
|
|
84
|
+
if (V.keepsXOnCornerCollision) player.vel.x = x
|
|
85
|
+
player.vel.z = 0
|
|
86
|
+
}
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
// Entity#move(MoverType.SELF, movement)
|
|
@@ -95,7 +98,7 @@ function move (player, mx, my, mz) {
|
|
|
95
98
|
mx = bx; mz = bz
|
|
96
99
|
|
|
97
100
|
const bb = player.bb()
|
|
98
|
-
const from = { ...player.pos }
|
|
101
|
+
const from = V.blockEffectsPerAxis ? { ...player.pos } : null
|
|
99
102
|
const collide = V.modernStepUp ? collideModern : collideLegacy
|
|
100
103
|
const r = collide(player.world, mx, my, mz, bb, player.onGround, player.maxUpStep())
|
|
101
104
|
const lengthSq = r.x * r.x + r.y * r.y + r.z * r.z
|
|
@@ -111,7 +114,10 @@ function move (player, mx, my, mz) {
|
|
|
111
114
|
player.onGround = player.verticalCollision && my < 0
|
|
112
115
|
updateSupportingBlock(player, player.onGround, r.x, r.z)
|
|
113
116
|
|
|
114
|
-
if (V.fluidRecheckInMove && !player.wasTouchingWater && !player.riding)
|
|
117
|
+
if (V.fluidRecheckInMove && !player.wasTouchingWater && !player.riding) {
|
|
118
|
+
if (V.fluidInteraction) updateFluidInteraction(player)
|
|
119
|
+
else updateWater(player)
|
|
120
|
+
}
|
|
115
121
|
if (player.onGround) {
|
|
116
122
|
player.fallDistance = 0
|
|
117
123
|
} else if (r.y < 0) {
|
|
@@ -124,7 +130,7 @@ function move (player, mx, my, mz) {
|
|
|
124
130
|
if (my !== r.y) landOn(player, block)
|
|
125
131
|
if (player.onGround && !player.input.shift) stepOn(player, block)
|
|
126
132
|
|
|
127
|
-
if (V.blockEffectsPerAxis && applies) player.movesThisTick.push({ from, to: { ...player.pos } })
|
|
133
|
+
if (V.blockEffectsPerAxis && applies) player.movesThisTick.push({ from, to: { ...player.pos }, input: { x: mx, y: my, z: mz } })
|
|
128
134
|
if (!V.blockEffectsAfterTravel) checkInsideBlocksLegacy(player)
|
|
129
135
|
|
|
130
136
|
const factor = speedFactor(player)
|
|
@@ -49,7 +49,7 @@ function suffocatesAt (player, bx, bz) {
|
|
|
49
49
|
const block = player.world.blockAt(x, y, z)
|
|
50
50
|
if (!block.suffocating) continue
|
|
51
51
|
for (const s of block.shapes) {
|
|
52
|
-
if (box.
|
|
52
|
+
if (box.intersects(s[0] + x, s[1] + y, s[2] + z, s[3] + x, s[4] + y, s[5] + z)) return true
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|