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.
@@ -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 }
@@ -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] < this.maxX && b[3] > this.minX &&
38
- b[1] < this.maxY && b[4] > this.minY &&
39
- b[2] < this.maxZ && b[5] > this.minZ
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) }
@@ -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.pos.y = this.waterLevel - BOAT_H + 0.101
94
- this.vel.y = 0
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 += G
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 }