spectra-physics 1.0.0 → 1.2.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 +51 -23
- package/index.js +2 -2
- package/lib/client/clock.js +38 -0
- package/lib/client/connection.js +103 -0
- package/lib/client/controls.js +78 -0
- package/lib/client/environment.js +94 -0
- package/lib/client/index.js +81 -0
- 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/reporter.js +91 -0
- package/lib/client/riding.js +69 -0
- package/lib/client/server.js +103 -0
- package/lib/client/teleport.js +108 -0
- package/lib/client/ticker.js +129 -0
- package/lib/engine/aabb.js +5 -3
- package/lib/engine/boat.js +57 -7
- package/lib/engine/player/blocks.js +134 -0
- package/lib/engine/player/effects.js +115 -0
- package/lib/engine/player/fluids.js +150 -0
- package/lib/engine/player/index.js +220 -0
- package/lib/engine/player/input.js +89 -0
- package/lib/engine/player/move.js +141 -0
- package/lib/engine/player/pose.js +95 -0
- package/lib/engine/player/push.js +27 -0
- package/lib/engine/player/travel.js +203 -0
- package/lib/engine/player/traversal.js +290 -0
- package/lib/engine/version.js +52 -27
- package/lib/engine/world.js +85 -20
- package/package.json +2 -1
- package/lib/engine/physics.js +0 -780
- package/lib/plugin.js +0 -574
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Minecraft#tick for the local player plus the timer that drives it.
|
|
3
|
+
|
|
4
|
+
const { startTicking, stopTicking } = require('./clock')
|
|
5
|
+
|
|
6
|
+
const LOAD_TIMEOUT_TICKS = 60
|
|
7
|
+
const MAX_TURN_DELAY = 40
|
|
8
|
+
|
|
9
|
+
const released = () => ({ forward: false, backward: false, left: false, right: false, jump: false, shift: false, sprint: false })
|
|
10
|
+
|
|
11
|
+
function installTicker (ctx) {
|
|
12
|
+
const { bot, client, V, player, state, world } = ctx
|
|
13
|
+
|
|
14
|
+
ctx.syncEntity = () => {
|
|
15
|
+
const e = bot.entity
|
|
16
|
+
if (!e) return
|
|
17
|
+
e.position.set(player.pos.x, player.pos.y, player.pos.z)
|
|
18
|
+
e.velocity.set(player.vel.x, player.vel.y, player.vel.z)
|
|
19
|
+
e.onGround = player.onGround
|
|
20
|
+
e.isInWater = player.isInWater()
|
|
21
|
+
e.isInLava = player.isInLava()
|
|
22
|
+
e.height = player.height()
|
|
23
|
+
e.eyeHeight = player.eyeHeight()
|
|
24
|
+
e.isCollidedHorizontally = player.horizontalCollision
|
|
25
|
+
e.isCollidedVertically = player.verticalCollision
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resetPlayer () {
|
|
29
|
+
const abilities = player.abilities
|
|
30
|
+
player.reset()
|
|
31
|
+
player.abilities = abilities
|
|
32
|
+
Object.assign(state, {
|
|
33
|
+
positionReceived: false,
|
|
34
|
+
clientLoaded: false,
|
|
35
|
+
loadedReadyTicks: 0,
|
|
36
|
+
loadTimeout: LOAD_TIMEOUT_TICKS,
|
|
37
|
+
last: { x: 0, y: 0, z: 0, yRot: 0, xRot: 0, onGround: false, horizontalCollision: false },
|
|
38
|
+
positionReminder: 0,
|
|
39
|
+
creeping: false,
|
|
40
|
+
turnDelay: 0,
|
|
41
|
+
wasSprinting: false,
|
|
42
|
+
wasShift: false,
|
|
43
|
+
lastInput: released(),
|
|
44
|
+
boat: null
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 1.21.4+: the player only ticks once the level screen closes, which is when player_loaded is sent
|
|
49
|
+
function waitForLevel () {
|
|
50
|
+
const outside = player.pos.y < world.minY || player.pos.y >= world.maxY
|
|
51
|
+
state.loadedReadyTicks = (outside || world.hasChunk(player.pos.x, player.pos.z)) ? state.loadedReadyTicks + 1 : 0
|
|
52
|
+
if (state.loadedReadyTicks < 2 && --state.loadTimeout > 0) return false
|
|
53
|
+
state.clientLoaded = true
|
|
54
|
+
ctx.send('player_loaded', {})
|
|
55
|
+
bot.emit('clientLoaded')
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function playerTick () {
|
|
60
|
+
if (!state.positionReceived) return
|
|
61
|
+
if (!world.hasChunk(player.pos.x, player.pos.z)) return
|
|
62
|
+
if (V.clientLoaded && !state.clientLoaded) {
|
|
63
|
+
waitForLevel()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
if (bot.isAlive === false) return
|
|
67
|
+
player.riding = false
|
|
68
|
+
ctx.updateItemUse()
|
|
69
|
+
// 1.17 - 1.18.1: a turn while creeping through the air can't be reported cleanly, the mouse waits a moment
|
|
70
|
+
if (state.creeping && state.turnDelay < MAX_TURN_DELAY) state.turnDelay++
|
|
71
|
+
else {
|
|
72
|
+
ctx.turn(state.turnDelay >= MAX_TURN_DELAY ? Infinity : undefined)
|
|
73
|
+
state.turnDelay = 0
|
|
74
|
+
}
|
|
75
|
+
ctx.flushActions()
|
|
76
|
+
if (bot.physicsEnabled) {
|
|
77
|
+
player.tick(ctx.keys())
|
|
78
|
+
if (player.events.abilitiesChanged) {
|
|
79
|
+
player.events.abilitiesChanged = false
|
|
80
|
+
ctx.send('abilities', { flags: player.abilities.flying ? 2 : 0 })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
ctx.syncEntity()
|
|
84
|
+
if (V.playerInputPacket) {
|
|
85
|
+
ctx.sendShift()
|
|
86
|
+
ctx.sendInput()
|
|
87
|
+
}
|
|
88
|
+
ctx.sendPosition()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function clientTick () {
|
|
92
|
+
if (client.state !== 'play' || !state.inLevel || !bot.entity) return
|
|
93
|
+
if (state.selectedSlot !== state.carriedSlot) {
|
|
94
|
+
state.carriedSlot = state.selectedSlot
|
|
95
|
+
ctx.send('held_item_slot', { slotId: state.selectedSlot })
|
|
96
|
+
}
|
|
97
|
+
ctx.flushHeldBrand()
|
|
98
|
+
const vehicle = bot.vehicle
|
|
99
|
+
if (vehicle && vehicle !== bot.entity && state.positionReceived) {
|
|
100
|
+
ctx.ridingTick(vehicle)
|
|
101
|
+
} else {
|
|
102
|
+
state.boat = null
|
|
103
|
+
playerTick()
|
|
104
|
+
}
|
|
105
|
+
ctx.flushActions()
|
|
106
|
+
if (V.tickEnd) ctx.send('tick_end', {})
|
|
107
|
+
bot.emit('physicsTick')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const tick = () => {
|
|
111
|
+
try { clientTick() } catch (err) { bot.emit('error', err) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
client.on('login', () => {
|
|
115
|
+
state.inLevel = true
|
|
116
|
+
state.playSettingsSent = false
|
|
117
|
+
state.selectedSlot = 0
|
|
118
|
+
state.carriedSlot = 0
|
|
119
|
+
resetPlayer()
|
|
120
|
+
startTicking(tick)
|
|
121
|
+
})
|
|
122
|
+
client.on('respawn', resetPlayer)
|
|
123
|
+
client.on('start_configuration', () => { state.inLevel = false })
|
|
124
|
+
bot.on('end', () => stopTicking(tick))
|
|
125
|
+
|
|
126
|
+
resetPlayer()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { installTicker }
|
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
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const AABB = require('../aabb')
|
|
4
|
+
const { f32 } = require('../mth')
|
|
5
|
+
|
|
6
|
+
const ON_POS = f32(0.2)
|
|
7
|
+
const BELOW_MODERN = f32(0.500001)
|
|
8
|
+
const BELOW_LEGACY = 0.5000001
|
|
9
|
+
|
|
10
|
+
// Entity#getOnPos(yOffset)
|
|
11
|
+
function onPos (player, yOffset = ON_POS) {
|
|
12
|
+
const { pos, world } = player
|
|
13
|
+
const support = player.supportingBlock
|
|
14
|
+
if (player.V.supportingBlock && support) {
|
|
15
|
+
if (!(yOffset > 1.0e-5)) return support
|
|
16
|
+
const block = world.blockAt(support.x, support.y, support.z)
|
|
17
|
+
if (!(yOffset <= 0.5) || !block.isFenceLike) return { x: support.x, y: Math.floor(pos.y - yOffset), z: support.z }
|
|
18
|
+
return support
|
|
19
|
+
}
|
|
20
|
+
const p = { x: Math.floor(pos.x), y: Math.floor(pos.y - yOffset), z: Math.floor(pos.z) }
|
|
21
|
+
if (!player.V.supportingBlock && yOffset === ON_POS && world.blockAt(p.x, p.y, p.z).isAir) {
|
|
22
|
+
if (world.blockAt(p.x, p.y - 1, p.z).isFenceLike) return { x: p.x, y: p.y - 1, z: p.z }
|
|
23
|
+
}
|
|
24
|
+
return p
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function belowPos (player) {
|
|
28
|
+
if (player.V.supportingBlock) return onPos(player, BELOW_MODERN)
|
|
29
|
+
return { x: Math.floor(player.pos.x), y: Math.floor(player.pos.y - BELOW_LEGACY), z: Math.floor(player.pos.z) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function feetBlock (player) {
|
|
33
|
+
return player.world.blockAt(Math.floor(player.pos.x), Math.floor(player.pos.y), Math.floor(player.pos.z))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function friction (player) {
|
|
37
|
+
const p = belowPos(player)
|
|
38
|
+
return player.world.blockAt(p.x, p.y, p.z).friction
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function soulSpeed (player, block) {
|
|
42
|
+
return block.name === 'soul_sand' && player.V.soulSpeedBoots && player.env.bootsEnchant('soul_speed') > 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function blockSpeed (player, block) {
|
|
46
|
+
return soulSpeed(player, block) ? 1 : block.speedFactor
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Entity#getBlockSpeedFactor
|
|
50
|
+
function speedFactor (player) {
|
|
51
|
+
if (player.abilities.flying) return 1
|
|
52
|
+
const inside = feetBlock(player)
|
|
53
|
+
let factor = blockSpeed(player, inside)
|
|
54
|
+
const keepsOwn = inside.name === 'water' || inside.name === 'bubble_column' ||
|
|
55
|
+
(!player.V.supportingBlock && inside.name === 'soul_sand')
|
|
56
|
+
if (factor === 1 && !keepsOwn) {
|
|
57
|
+
const p = belowPos(player)
|
|
58
|
+
factor = blockSpeed(player, player.world.blockAt(p.x, p.y, p.z))
|
|
59
|
+
}
|
|
60
|
+
if (player.V.efficiencyAttributes) {
|
|
61
|
+
const efficiency = f32(player.env.attribute('movement_efficiency', 0))
|
|
62
|
+
factor = f32(factor + efficiency * (1 - factor))
|
|
63
|
+
}
|
|
64
|
+
return factor
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function jumpFactor (player) {
|
|
68
|
+
const inside = feetBlock(player).jumpFactor
|
|
69
|
+
if (inside !== 1) return inside
|
|
70
|
+
const p = belowPos(player)
|
|
71
|
+
return player.world.blockAt(p.x, p.y, p.z).jumpFactor
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function onClimbable (player) {
|
|
75
|
+
const { world } = player
|
|
76
|
+
const bx = Math.floor(player.pos.x)
|
|
77
|
+
const by = Math.floor(player.pos.y)
|
|
78
|
+
const bz = Math.floor(player.pos.z)
|
|
79
|
+
const block = world.blockAt(bx, by, bz)
|
|
80
|
+
if (block.climbable) return true
|
|
81
|
+
if (block.isTrapdoor && (block.props.open === true || block.props.open === 'true')) {
|
|
82
|
+
const below = world.blockAt(bx, by - 1, bz)
|
|
83
|
+
return below.isLadder && below.props.facing === block.props.facing
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onPowderSnowWithBoots (player) {
|
|
89
|
+
return player.V.powderSnow && feetBlock(player).isPowderSnow && player.env.bootsItem() === 'leather_boots'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findSupport (player, box) {
|
|
93
|
+
let best = null
|
|
94
|
+
let bestDistance = Infinity
|
|
95
|
+
const x0 = Math.floor(box.minX) - 1; const x1 = Math.floor(box.maxX) + 1
|
|
96
|
+
const y0 = Math.floor(box.minY) - 1; const y1 = Math.floor(box.maxY) + 1
|
|
97
|
+
const z0 = Math.floor(box.minZ) - 1; const z1 = Math.floor(box.maxZ) + 1
|
|
98
|
+
for (let x = x0; x <= x1; x++) {
|
|
99
|
+
for (let y = y0; y <= y1; y++) {
|
|
100
|
+
for (let z = z0; z <= z1; z++) {
|
|
101
|
+
const shapes = player.world.blockAt(x, y, z).shapes
|
|
102
|
+
if (!shapes.some(s => box.intersectsBox([s[0] + x, s[1] + y, s[2] + z, s[3] + x, s[4] + y, s[5] + z]))) continue
|
|
103
|
+
const cx = x + 0.5 - player.pos.x
|
|
104
|
+
const cy = y + 0.5 - player.pos.y
|
|
105
|
+
const cz = z + 0.5 - player.pos.z
|
|
106
|
+
const distance = cx * cx + cy * cy + cz * cz
|
|
107
|
+
const greater = best && (y !== best.y ? y > best.y : (z !== best.z ? z > best.z : x > best.x))
|
|
108
|
+
if (distance < bestDistance || (distance === bestDistance && (!best || greater))) {
|
|
109
|
+
bestDistance = distance
|
|
110
|
+
best = { x, y, z }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return best
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Entity#checkSupportingBlock, 1.20+
|
|
119
|
+
function updateSupportingBlock (player, onGround, mx, mz) {
|
|
120
|
+
if (!player.V.supportingBlock) return
|
|
121
|
+
if (!onGround) {
|
|
122
|
+
player.supportingBlock = null
|
|
123
|
+
player.onGroundNoBlocks = false
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
const bb = player.bb()
|
|
127
|
+
const feet = new AABB(bb.minX, bb.minY - 1.0e-6, bb.minZ, bb.maxX, bb.minY, bb.maxZ)
|
|
128
|
+
let found = findSupport(player, feet)
|
|
129
|
+
if (!found && !player.onGroundNoBlocks) found = findSupport(player, feet.move(-mx, 0, -mz))
|
|
130
|
+
player.supportingBlock = found
|
|
131
|
+
player.onGroundNoBlocks = !found
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { onPos, belowPos, feetBlock, friction, speedFactor, jumpFactor, onClimbable, onPowderSnowWithBoots, updateSupportingBlock }
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { f32 } = require('../mth')
|
|
4
|
+
const { traverseMoves, BlockList } = require('./traversal')
|
|
5
|
+
|
|
6
|
+
const F_098 = f32(0.98)
|
|
7
|
+
const F_08 = f32(0.8)
|
|
8
|
+
const F_09 = f32(0.9)
|
|
9
|
+
const F_005 = f32(0.05)
|
|
10
|
+
const HALF_WIDTH = f32(0.3)
|
|
11
|
+
|
|
12
|
+
const STUCK = {
|
|
13
|
+
cobweb: { x: 0.25, y: F_005, z: 0.25 },
|
|
14
|
+
cobwebWeaving: { x: 0.5, y: 0.25, z: 0.5 },
|
|
15
|
+
berryBush: { x: F_08, y: 0.75, z: F_08 },
|
|
16
|
+
powderSnow: { x: F_09, y: 1.5, z: F_09 }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeStuck (player, multiplier) {
|
|
20
|
+
player.fallDistance = 0
|
|
21
|
+
player.stuck = multiplier
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 1.21.2 moved block effects after gravity and drag; honey keeps its old numbers by converting back and forth
|
|
25
|
+
const oldDeltaY = (player, vy) => player.V.blockEffectsAfterTravel ? vy / F_098 + 0.08 : vy
|
|
26
|
+
const newDeltaY = (player, vy) => player.V.blockEffectsAfterTravel ? (vy - 0.08) * F_098 : vy
|
|
27
|
+
|
|
28
|
+
function honeySlide (player, bx, by, bz) {
|
|
29
|
+
if (player.onGround) return
|
|
30
|
+
if (player.pos.y > by + 0.9375 - 1.0e-7) return
|
|
31
|
+
const vy = oldDeltaY(player, player.vel.y)
|
|
32
|
+
if (vy >= -0.08) return
|
|
33
|
+
const dx = Math.abs(bx + 0.5 - player.pos.x)
|
|
34
|
+
const dz = Math.abs(bz + 0.5 - player.pos.z)
|
|
35
|
+
const edge = 0.4375 + HALF_WIDTH
|
|
36
|
+
if (!(dx + 1.0e-7 > edge || dz + 1.0e-7 > edge)) return
|
|
37
|
+
if (vy < -0.13) {
|
|
38
|
+
const k = -0.05 / vy
|
|
39
|
+
player.vel.x *= k
|
|
40
|
+
player.vel.z *= k
|
|
41
|
+
}
|
|
42
|
+
player.vel.y = newDeltaY(player, -0.05)
|
|
43
|
+
player.fallDistance = 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function bubbleColumn (player, block, bx, by, bz) {
|
|
47
|
+
const surface = player.world.blockAt(bx, by + 1, bz).isAir
|
|
48
|
+
const vy = player.vel.y
|
|
49
|
+
if (block.bubbleDrag) player.vel.y = surface ? Math.max(-0.9, vy - 0.03) : Math.max(-0.3, vy - 0.03)
|
|
50
|
+
else player.vel.y = surface ? Math.min(1.8, vy + 0.1) : Math.min(0.7, vy + 0.06)
|
|
51
|
+
player.fallDistance = 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function onInsideBlock (player, block, bx, by, bz, withVelocityEffects) {
|
|
55
|
+
if (block.isCobweb) {
|
|
56
|
+
makeStuck(player, player.env.effect('weaving') != null ? STUCK.cobwebWeaving : STUCK.cobweb)
|
|
57
|
+
} else if (block.isBerryBush) {
|
|
58
|
+
makeStuck(player, STUCK.berryBush)
|
|
59
|
+
} else if (block.isPowderSnow) {
|
|
60
|
+
if (bx === Math.floor(player.pos.x) && by === Math.floor(player.pos.y) && bz === Math.floor(player.pos.z)) {
|
|
61
|
+
makeStuck(player, STUCK.powderSnow)
|
|
62
|
+
}
|
|
63
|
+
} else if (block.isBubbleColumn) {
|
|
64
|
+
if (withVelocityEffects) bubbleColumn(player, block, bx, by, bz)
|
|
65
|
+
} else if (block.isHoney) {
|
|
66
|
+
honeySlide(player, bx, by, bz)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const visited = new BlockList()
|
|
71
|
+
|
|
72
|
+
function forEachBlockIn (box, visit) {
|
|
73
|
+
const x0 = Math.floor(box.minX); const x1 = Math.floor(box.maxX)
|
|
74
|
+
const y0 = Math.floor(box.minY); const y1 = Math.floor(box.maxY)
|
|
75
|
+
const z0 = Math.floor(box.minZ); const z1 = Math.floor(box.maxZ)
|
|
76
|
+
for (let x = x0; x <= x1; x++) {
|
|
77
|
+
for (let y = y0; y <= y1; y++) {
|
|
78
|
+
for (let z = z0; z <= z1; z++) visit(x, y, z)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function settleStuck (player) {
|
|
84
|
+
if (player.abilities.flying) player.stuck = null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Entity#checkInsideBlocks, before 1.21.2: runs inside Entity#move against the new box
|
|
88
|
+
function checkInsideBlocksLegacy (player) {
|
|
89
|
+
const box = player.bb().deflate(player.V.insideBlockDeflate)
|
|
90
|
+
forEachBlockIn(box, (x, y, z) => {
|
|
91
|
+
const block = player.world.blockAt(x, y, z)
|
|
92
|
+
if (!block.isAir) onInsideBlock(player, block, x, y, z, true)
|
|
93
|
+
})
|
|
94
|
+
settleStuck(player)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Entity#applyEffectsFromBlocks, 1.21.2+: after travel, along every movement made this tick
|
|
98
|
+
function applyEffectsAfterTravel (player) {
|
|
99
|
+
if (!player.V.blockEffectsAfterTravel) return
|
|
100
|
+
player.stuck = null
|
|
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 }]
|
|
103
|
+
const last = moves[moves.length - 1].to
|
|
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
|
+
})
|
|
111
|
+
player.movesThisTick.length = 0
|
|
112
|
+
settleStuck(player)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { checkInsideBlocksLegacy, applyEffectsAfterTravel }
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const WorldView = require('../world')
|
|
4
|
+
const { f32 } = require('../mth')
|
|
5
|
+
|
|
6
|
+
const EYE_OFFSET = f32(0.11111111)
|
|
7
|
+
const WATER_PUSH = 0.014
|
|
8
|
+
const MIN_PUSH = 0.0045000000000000005
|
|
9
|
+
const SIDES = [[0, -1], [0, 1], [-1, 0], [1, 0]]
|
|
10
|
+
|
|
11
|
+
// FlowingFluid#getFlow
|
|
12
|
+
function flowAt (world, x, y, z, fluid) {
|
|
13
|
+
const own = WorldView.ownHeight(fluid)
|
|
14
|
+
let dx = 0
|
|
15
|
+
let dz = 0
|
|
16
|
+
for (const [sx, sz] of SIDES) {
|
|
17
|
+
const nx = x + sx
|
|
18
|
+
const nz = z + sz
|
|
19
|
+
const other = world.fluidAt(nx, y, nz)
|
|
20
|
+
if (other && other.type !== fluid.type) continue
|
|
21
|
+
let height = other ? WorldView.ownHeight(other) : 0
|
|
22
|
+
let diff = 0
|
|
23
|
+
if (height === 0) {
|
|
24
|
+
const block = world.blockAt(nx, y, nz)
|
|
25
|
+
if (block.shapes.length === 0 || block.fluid) {
|
|
26
|
+
const below = world.fluidAt(nx, y - 1, nz)
|
|
27
|
+
if (!below || below.type === fluid.type) {
|
|
28
|
+
height = below ? WorldView.ownHeight(below) : 0
|
|
29
|
+
if (height > 0) diff = f32(own - f32(height - f32(0.8888889)))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
diff = f32(own - height)
|
|
34
|
+
}
|
|
35
|
+
dx += sx * diff
|
|
36
|
+
dz += sz * diff
|
|
37
|
+
}
|
|
38
|
+
let vx = dx; let vy = 0; let vz = dz
|
|
39
|
+
if (fluid.falling) {
|
|
40
|
+
for (const [sx, sz] of SIDES) {
|
|
41
|
+
const side = world.blockAt(x + sx, y, z + sz)
|
|
42
|
+
const sideAbove = world.blockAt(x + sx, y + 1, z + sz)
|
|
43
|
+
if ((side.fullCube && !side.fluid) || (sideAbove.fullCube && !sideAbove.fluid)) {
|
|
44
|
+
const l = Math.sqrt(vx * vx + vz * vz)
|
|
45
|
+
if (l < 1.0e-4) { vx = 0; vz = 0 } else { vx /= l; vz /= l }
|
|
46
|
+
vy = -6
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const length = Math.sqrt(vx * vx + vy * vy + vz * vz)
|
|
52
|
+
return length < 1.0e-4 ? { x: 0, y: 0, z: 0 } : { x: vx / length, y: vy / length, z: vz / length }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Entity#updateFluidHeightAndDoFluidPushing
|
|
56
|
+
function touchFluid (player, type, scale) {
|
|
57
|
+
const bb = player.bb().deflate(0.001)
|
|
58
|
+
const x0 = Math.floor(bb.minX); const x1 = Math.ceil(bb.maxX)
|
|
59
|
+
const y0 = Math.floor(bb.minY); const y1 = Math.ceil(bb.maxY)
|
|
60
|
+
const z0 = Math.floor(bb.minZ); const z1 = Math.ceil(bb.maxZ)
|
|
61
|
+
const pushed = !player.abilities.flying
|
|
62
|
+
let height = 0
|
|
63
|
+
let touching = false
|
|
64
|
+
let px = 0; let py = 0; let pz = 0; let count = 0
|
|
65
|
+
for (let x = x0; x < x1; x++) {
|
|
66
|
+
for (let y = y0; y < y1; y++) {
|
|
67
|
+
for (let z = z0; z < z1; z++) {
|
|
68
|
+
const fluid = player.world.fluidAt(x, y, z)
|
|
69
|
+
if (!fluid || fluid.type !== type) continue
|
|
70
|
+
const top = y + player.world.fluidHeight(x, y, z, fluid)
|
|
71
|
+
if (top < bb.minY) continue
|
|
72
|
+
touching = true
|
|
73
|
+
height = Math.max(top - bb.minY, height)
|
|
74
|
+
if (!pushed) continue
|
|
75
|
+
const flow = flowAt(player.world, x, y, z, fluid)
|
|
76
|
+
const k = height < 0.4 ? height : 1
|
|
77
|
+
px += flow.x * k; py += flow.y * k; pz += flow.z * k
|
|
78
|
+
count++
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (px * px + py * py + pz * pz > 0) {
|
|
83
|
+
px /= count; py /= count; pz /= count
|
|
84
|
+
px *= scale; py *= scale; pz *= scale
|
|
85
|
+
const length = Math.sqrt(px * px + py * py + pz * pz)
|
|
86
|
+
if (Math.abs(player.vel.x) < 0.003 && Math.abs(player.vel.z) < 0.003 && length < MIN_PUSH) {
|
|
87
|
+
px = px / length * MIN_PUSH; py = py / length * MIN_PUSH; pz = pz / length * MIN_PUSH
|
|
88
|
+
}
|
|
89
|
+
player.vel.x += px; player.vel.y += py; player.vel.z += pz
|
|
90
|
+
}
|
|
91
|
+
player.fluidHeight[type] = height
|
|
92
|
+
return touching
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Entity#updateInWaterStateAndDoWaterCurrentPushing
|
|
96
|
+
function updateWater (player) {
|
|
97
|
+
player.wasTouchingWater = touchFluid(player, 'water', WATER_PUSH)
|
|
98
|
+
if (player.wasTouchingWater) player.fallDistance = 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function updateEyes (player) {
|
|
102
|
+
player.wasEyeInWater = player.eyeInWater
|
|
103
|
+
const eyeY = player.pos.y + player.eyeHeight() - EYE_OFFSET
|
|
104
|
+
const bx = Math.floor(player.pos.x)
|
|
105
|
+
const by = Math.floor(eyeY)
|
|
106
|
+
const bz = Math.floor(player.pos.z)
|
|
107
|
+
const fluid = player.world.fluidAt(bx, by, bz)
|
|
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))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Player#updateSwimming
|
|
124
|
+
function updateSwimming (player) {
|
|
125
|
+
if (player.abilities.flying || player.riding) {
|
|
126
|
+
player.swimming = false
|
|
127
|
+
} else if (player.swimming) {
|
|
128
|
+
player.swimming = player.sprinting && player.isInWater()
|
|
129
|
+
} else {
|
|
130
|
+
const fluid = player.world.fluidAt(Math.floor(player.pos.x), Math.floor(player.pos.y), Math.floor(player.pos.z))
|
|
131
|
+
const feet = !player.V.swimmingNeedsFeetInWater || (fluid && fluid.type === 'water')
|
|
132
|
+
player.swimming = player.sprinting && player.isUnderWater() && !!feet
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function updateFluids (player) {
|
|
137
|
+
updateFluidInteraction(player)
|
|
138
|
+
updateEyes(player)
|
|
139
|
+
updateSwimming(player)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// LivingEntity#getFluidFallingAdjustedMovement
|
|
143
|
+
function fluidFallingAdjusted (player, gravity, falling) {
|
|
144
|
+
if (player.sprinting) return
|
|
145
|
+
const vy = player.vel.y
|
|
146
|
+
if (falling && Math.abs(vy - 0.005) >= 0.003 && Math.abs(vy - gravity / 16) < 0.003) player.vel.y = -0.003
|
|
147
|
+
else player.vel.y = vy - gravity / 16
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { updateFluids, updateWater, updateFluidInteraction, fluidFallingAdjusted }
|