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.
@@ -0,0 +1,780 @@
1
+ 'use strict'
2
+ // Client-side player movement: a port of LocalPlayer#aiStep -> Player#aiStep -> LivingEntity#aiStep
3
+ // -> LivingEntity#travel -> Entity#move, following vanilla ordering and float rounding.
4
+
5
+ const AABB = require('./aabb')
6
+ const WorldView = require('./world')
7
+ const { f32, sin, cos, degToRadF, clamp, equal } = require('./mth')
8
+ const { collideLegacy, collideModern } = require('./collision')
9
+
10
+ const WIDTH = f32(0.6)
11
+ const HALF = f32(WIDTH / 2)
12
+ const HEIGHT_STAND = f32(1.8)
13
+ const HEIGHT_CROUCH = f32(1.5)
14
+ const EYE_STAND = f32(1.62)
15
+ const EYE_CROUCH = f32(1.27)
16
+ const STEP = f32(0.6)
17
+ const F_098 = f32(0.98)
18
+ const F_091 = f32(0.91)
19
+ const F_080 = f32(0.8)
20
+ const F_090 = f32(0.9)
21
+ const F_002 = f32(0.02)
22
+ const F_03 = f32(0.3)
23
+ const F_02 = f32(0.2)
24
+ const F_042 = f32(0.42)
25
+ const F_01 = f32(0.1)
26
+ const F_0216 = f32(0.21600002)
27
+ const F_015 = f32(0.15)
28
+ const F_004 = f32(0.04)
29
+ const F_005 = f32(0.05)
30
+ const F_0026 = f32(0.025999999)
31
+ const F_011111111 = f32(0.11111111)
32
+ const F_05000001 = 0.5000001
33
+ const F_0500001 = f32(0.500001)
34
+ const F_ONPOS = f32(0.2)
35
+ const F_066 = f32(0.66)
36
+ const F_096 = f32(0.96)
37
+ const F_1EM5 = f32(1.0e-5)
38
+ const F_ACOS_MINOR = f32(0.13962634)
39
+
40
+ class PlayerPhysics {
41
+ constructor (world, V, env) {
42
+ this.world = world // WorldView
43
+ this.V = V
44
+ this.env = env // { attribute(name, def), effect(name) -> amplifier|null, food(), usingItem(), gameMode() }
45
+ this.reset()
46
+ }
47
+
48
+ reset () {
49
+ this.pos = { x: 0, y: 0, z: 0 }
50
+ this.vel = { x: 0, y: 0, z: 0 }
51
+ this.yRot = 0
52
+ this.xRot = 0
53
+ this.onGround = false
54
+ this.horizontalCollision = false
55
+ this.minorHorizontalCollision = false
56
+ this.verticalCollision = false
57
+ this.fallDistance = 0
58
+ this.sprinting = false
59
+ this.crouching = false // pose (affects bounding box), updated at the end of the tick
60
+ this.noJumpDelay = 0
61
+ this.jumpTriggerTime = 0
62
+ this.sprintTriggerTime = 0
63
+ this.speed = f32(0.1) // LivingEntity#speed, refreshed at the end of Player#aiStep
64
+ this.legacyFlyingSpeed = F_002
65
+ this.stuck = null
66
+ this.wasTouchingWater = false
67
+ this.fluidHeight = { water: 0, lava: 0 }
68
+ this.eyeInWater = false
69
+ this.wasEyeInWater = false
70
+ this.firstTick = true
71
+ this.supportingBlock = null
72
+ this.abilities = { invulnerable: false, flying: false, mayfly: false, instabuild: false, flyingSpeed: f32(0.05), walkingSpeed: f32(0.1) }
73
+ this.input = { left: 0, forward: 0, jumping: false, shift: false, keys: {} }
74
+ this.xxa = 0
75
+ this.zza = 0
76
+ this.events = { abilitiesChanged: false }
77
+ }
78
+
79
+ // ---------- geometry ----------
80
+ height () { return this.crouching ? HEIGHT_CROUCH : HEIGHT_STAND }
81
+ eyeHeight () { return this.crouching ? EYE_CROUCH : EYE_STAND }
82
+ bbAt (x, y, z, h = this.height()) { return new AABB(x - HALF, y, z - HALF, x + HALF, y + h, z + HALF) }
83
+ bb () { return this.bbAt(this.pos.x, this.pos.y, this.pos.z) }
84
+
85
+ canEnterPose (crouch) {
86
+ const h = crouch ? HEIGHT_CROUCH : HEIGHT_STAND
87
+ return this.world.noCollision(this.bbAt(this.pos.x, this.pos.y, this.pos.z, h).deflate(1.0e-7))
88
+ }
89
+
90
+ blockPosition () {
91
+ return { x: Math.floor(this.pos.x), y: Math.floor(this.pos.y), z: Math.floor(this.pos.z) }
92
+ }
93
+
94
+ maxUpStep () {
95
+ return this.V.attributes2005 ? f32(this.env.attribute('step_height', 0.6)) : STEP
96
+ }
97
+
98
+ gravity () {
99
+ return this.V.attributes2005 ? this.env.attribute('gravity', 0.08) : 0.08
100
+ }
101
+
102
+ isAffectedByFluids () { return !this.abilities.flying }
103
+ isInWater () { return this.wasTouchingWater }
104
+ isInLava () { return !this.firstTick && this.fluidHeight.lava > 0 }
105
+ isUnderWater () { return this.wasEyeInWater && this.isInWater() }
106
+
107
+ // ---------- fluids (Entity#updateInWaterStateAndDoFluidPushing) ----------
108
+ updateFluids () {
109
+ this.fluidHeight.water = 0
110
+ this.fluidHeight.lava = 0
111
+ if (this.updateFluidHeightAndDoFluidPushing('water', 0.014)) {
112
+ this.fallDistance = 0
113
+ this.wasTouchingWater = true
114
+ } else {
115
+ this.wasTouchingWater = false
116
+ }
117
+ if (!this.wasTouchingWater) {
118
+ this.updateFluidHeightAndDoFluidPushing('lava', this.env.ultraWarm() ? 0.007 : 0.0023333333333333335)
119
+ }
120
+ // updateFluidOnEyes
121
+ this.wasEyeInWater = this.eyeInWater
122
+ const ey = this.pos.y + this.eyeHeight() - F_011111111
123
+ const bx = Math.floor(this.pos.x); const by = Math.floor(ey); const bz = Math.floor(this.pos.z)
124
+ const fl = this.world.fluidAt(bx, by, bz)
125
+ this.eyeInWater = !!(fl && fl.type === 'water' && (by + this.world.fluidHeight(bx, by, bz, fl)) > ey)
126
+ }
127
+
128
+ updateFluidHeightAndDoFluidPushing (type, scale) {
129
+ const bb = this.bb().deflate(0.001)
130
+ const x0 = Math.floor(bb.minX); const x1 = Math.ceil(bb.maxX)
131
+ const y0 = Math.floor(bb.minY); const y1 = Math.ceil(bb.maxY)
132
+ const z0 = Math.floor(bb.minZ); const z1 = Math.ceil(bb.maxZ)
133
+ let height = 0
134
+ const pushed = !this.abilities.flying
135
+ let touching = false
136
+ let fx = 0; let fy = 0; let fz = 0; let n = 0
137
+ for (let x = x0; x < x1; x++) {
138
+ for (let y = y0; y < y1; y++) {
139
+ for (let z = z0; z < z1; z++) {
140
+ const fl = this.world.fluidAt(x, y, z)
141
+ if (!fl || fl.type !== type) continue
142
+ const top = y + this.world.fluidHeight(x, y, z, fl)
143
+ if (top >= bb.minY) {
144
+ touching = true
145
+ height = Math.max(top - bb.minY, height)
146
+ if (pushed) {
147
+ const f = this.flow(x, y, z, fl)
148
+ let k = 1
149
+ if (height < 0.4) k = height
150
+ fx += f.x * k; fy += f.y * k; fz += f.z * k
151
+ n++
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ const len = Math.sqrt(fx * fx + fy * fy + fz * fz)
158
+ if (len > 0) {
159
+ if (n > 0) { fx /= n; fy /= n; fz /= n }
160
+ fx *= scale; fy *= scale; fz *= scale
161
+ if (Math.abs(this.vel.x) < 0.003 && Math.abs(this.vel.z) < 0.003 && Math.sqrt(fx * fx + fy * fy + fz * fz) < 0.0045000000000000005) {
162
+ const l = Math.sqrt(fx * fx + fy * fy + fz * fz)
163
+ fx = fx / l * 0.0045000000000000005; fy = fy / l * 0.0045000000000000005; fz = fz / l * 0.0045000000000000005
164
+ }
165
+ this.vel.x += fx; this.vel.y += fy; this.vel.z += fz
166
+ }
167
+ this.fluidHeight[type] = height
168
+ return touching
169
+ }
170
+
171
+ // FlowingFluid#getFlow
172
+ flow (x, y, z, fl) {
173
+ const own = WorldView.ownHeight(fl)
174
+ let dx = 0; let dz = 0
175
+ const dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]]
176
+ for (const [sx, sz] of dirs) {
177
+ const nx = x + sx; const nz = z + sz
178
+ const nf = this.world.fluidAt(nx, y, nz)
179
+ const affects = !nf || nf.type === fl.type
180
+ if (!affects) continue
181
+ let h = nf ? WorldView.ownHeight(nf) : 0
182
+ let d = 0
183
+ if (h === 0) {
184
+ const b = this.world.blockAt(nx, y, nz)
185
+ if (!(b.shapes.length > 0 && !b.fluid)) {
186
+ const below = this.world.fluidAt(nx, y - 1, nz)
187
+ if (!below || below.type === fl.type) {
188
+ h = below ? WorldView.ownHeight(below) : 0
189
+ if (h > 0) d = f32(own - f32(h - f32(0.8888889)))
190
+ }
191
+ }
192
+ } else if (h > 0) {
193
+ d = f32(own - h)
194
+ }
195
+ if (d !== 0) { dx += sx * d; dz += sz * d }
196
+ }
197
+ let vx = dx; let vy = 0; let vz = dz
198
+ if (fl.falling) {
199
+ for (const [sx, sz] of dirs) {
200
+ const a = this.world.blockAt(x + sx, y, z + sz); const b = this.world.blockAt(x + sx, y + 1, z + sz)
201
+ if ((a.fullCube && !a.fluid) || (b.fullCube && !b.fluid)) {
202
+ const l = Math.sqrt(vx * vx + vz * vz)
203
+ if (l > 1.0e-4) { vx /= l; vz /= l } else { vx = 0; vz = 0 }
204
+ vy = -6
205
+ break
206
+ }
207
+ }
208
+ }
209
+ const l = Math.sqrt(vx * vx + vy * vy + vz * vz)
210
+ return l < 1.0e-4 ? { x: 0, y: 0, z: 0 } : { x: vx / l, y: vy / l, z: vz / l }
211
+ }
212
+
213
+ // ---------- block helpers ----------
214
+ onPos (yOffset) {
215
+ const V = this.V
216
+ if (V.supportingBlock && this.supportingBlock) {
217
+ const sb = this.supportingBlock
218
+ if (!(yOffset > 1.0e-5)) return sb
219
+ const st = this.world.blockAt(sb.x, sb.y, sb.z)
220
+ if (!(yOffset <= 0.5) || !st.isFenceLike) return { x: sb.x, y: Math.floor(this.pos.y - yOffset), z: sb.z }
221
+ return sb
222
+ }
223
+ const p = { x: Math.floor(this.pos.x), y: Math.floor(this.pos.y - yOffset), z: Math.floor(this.pos.z) }
224
+ if (!V.supportingBlock && yOffset === F_ONPOS) {
225
+ const b = this.world.blockAt(p.x, p.y, p.z)
226
+ if (b.isAir) {
227
+ const below = this.world.blockAt(p.x, p.y - 1, p.z)
228
+ if (below.isFenceLike) return { x: p.x, y: p.y - 1, z: p.z }
229
+ }
230
+ }
231
+ return p
232
+ }
233
+
234
+ posBelowAffectingMovement () {
235
+ if (this.V.supportingBlock) return this.onPos(F_0500001)
236
+ return { x: Math.floor(this.pos.x), y: Math.floor(this.bb().minY - F_05000001), z: Math.floor(this.pos.z) }
237
+ }
238
+
239
+ checkSupportingBlock (onGround, mx, my, mz) {
240
+ if (!this.V.supportingBlock) return
241
+ if (!onGround) { this.supportingBlock = null; this.onGroundNoBlocks = false; return }
242
+ const bb = this.bb()
243
+ const feet = new AABB(bb.minX, bb.minY - 1.0e-6, bb.minZ, bb.maxX, bb.minY, bb.maxZ)
244
+ const find = (box) => {
245
+ let best = null; let bestD = Infinity
246
+ const x0 = Math.floor(box.minX) - 1; const x1 = Math.floor(box.maxX) + 1
247
+ const y0 = Math.floor(box.minY) - 1; const y1 = Math.floor(box.maxY) + 1
248
+ const z0 = Math.floor(box.minZ) - 1; const z1 = Math.floor(box.maxZ) + 1
249
+ for (let x = x0; x <= x1; x++) {
250
+ for (let y = y0; y <= y1; y++) {
251
+ for (let z = z0; z <= z1; z++) {
252
+ const info = this.world.blockAt(x, y, z)
253
+ let hit = false
254
+ for (const s of info.shapes) {
255
+ if (box.intersectsBox([s[0] + x, s[1] + y, s[2] + z, s[3] + x, s[4] + y, s[5] + z])) { hit = true; break }
256
+ }
257
+ if (!hit) continue
258
+ const cx = x + 0.5 - this.pos.x; const cy = y + 0.5 - this.pos.y; const cz = z + 0.5 - this.pos.z
259
+ const d = cx * cx + cy * cy + cz * cz
260
+ // Vec3i#compareTo orders by y, then z, then x; vanilla keeps the greater one on ties
261
+ const greater = best && (y !== best.y ? y > best.y : (z !== best.z ? z > best.z : x > best.x))
262
+ if (d < bestD || (d === bestD && (!best || greater))) {
263
+ bestD = d; best = { x, y, z }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return best
269
+ }
270
+ let found = find(feet)
271
+ if (!found && !this.onGroundNoBlocks) {
272
+ found = find(feet.move(-mx, 0, -mz))
273
+ this.supportingBlock = found
274
+ } else {
275
+ this.onGroundNoBlocks = false
276
+ this.supportingBlock = found
277
+ }
278
+ this.onGroundNoBlocks = !found
279
+ }
280
+
281
+ onClimbable () {
282
+ const p = this.blockPosition()
283
+ const st = this.world.blockAt(p.x, p.y, p.z)
284
+ if (st.climbable) return true
285
+ if (st.isTrapdoor && (st.props.open === true || st.props.open === 'true')) {
286
+ const below = this.world.blockAt(p.x, p.y - 1, p.z)
287
+ if (below.isLadder && below.props.facing === st.props.facing) return true
288
+ }
289
+ return false
290
+ }
291
+
292
+ blockSpeedFactor () {
293
+ const p = this.blockPosition()
294
+ const b = this.world.blockAt(p.x, p.y, p.z)
295
+ const f = b.speedFactor
296
+ if (b.name !== 'water' && b.name !== 'bubble_column') {
297
+ if (f === 1.0) {
298
+ const q = this.posBelowAffectingMovement()
299
+ return this.world.blockAt(q.x, q.y, q.z).speedFactor
300
+ }
301
+ return f
302
+ }
303
+ return f
304
+ }
305
+
306
+ blockJumpFactor () {
307
+ const p = this.blockPosition()
308
+ const f = this.world.blockAt(p.x, p.y, p.z).jumpFactor
309
+ const q = this.posBelowAffectingMovement()
310
+ const f1 = this.world.blockAt(q.x, q.y, q.z).jumpFactor
311
+ return f === 1.0 ? f1 : f
312
+ }
313
+
314
+ // ---------- movement primitives ----------
315
+ moveRelative (speed, ix, iy, iz) {
316
+ const lsq = ix * ix + iy * iy + iz * iz
317
+ if (lsq < 1.0e-7) return
318
+ let x = ix; let y = iy; let z = iz
319
+ if (lsq > 1) { const l = Math.sqrt(lsq); x /= l; y /= l; z /= l }
320
+ x *= speed; y *= speed; z *= speed
321
+ const r = degToRadF(this.yRot)
322
+ const s = sin(r); const c = cos(r)
323
+ this.vel.x += x * c - z * s
324
+ this.vel.y += y
325
+ this.vel.z += z * c + x * s
326
+ }
327
+
328
+ isStayingOnGroundSurface () { return this.input.shift }
329
+
330
+ isAboveGround (step) {
331
+ if (this.onGround) return true
332
+ if (this.V.modernBackOff) {
333
+ return this.fallDistance < step && !this.canFallAtLeast(0, 0, step - this.fallDistance)
334
+ }
335
+ return this.fallDistance < step && !this.world.noCollision(this.bb().move(0, this.fallDistance - step, 0))
336
+ }
337
+
338
+ canFallAtLeast (x, z, dist) {
339
+ const bb = this.bb()
340
+ return this.world.noCollision(new AABB(bb.minX + 1.0e-7 + x, bb.minY - dist - 1.0e-7, bb.minZ + 1.0e-7 + z,
341
+ bb.maxX - 1.0e-7 + x, bb.minY, bb.maxZ - 1.0e-7 + z))
342
+ }
343
+
344
+ maybeBackOffFromEdge (vx, vy, vz) {
345
+ const step = this.maxUpStep()
346
+ if (this.abilities.flying || !this.isStayingOnGroundSurface()) return [vx, vz]
347
+ if (this.V.modernBackOff) {
348
+ if (vy > 0 || !this.isAboveGround(step)) return [vx, vz]
349
+ let d0 = vx; let d1 = vz
350
+ const d3 = Math.sign(d0) * 0.05; const d4 = Math.sign(d1) * 0.05
351
+ while (d0 !== 0 && this.canFallAtLeast(d0, 0, step)) { if (Math.abs(d0) <= 0.05) { d0 = 0; break } d0 -= d3 }
352
+ while (d1 !== 0 && this.canFallAtLeast(0, d1, step)) { if (Math.abs(d1) <= 0.05) { d1 = 0; break } d1 -= d4 }
353
+ while (d0 !== 0 && d1 !== 0 && this.canFallAtLeast(d0, d1, step)) {
354
+ if (Math.abs(d0) <= 0.05) d0 = 0; else d0 -= d3
355
+ if (Math.abs(d1) <= 0.05) d1 = 0; else d1 -= d4
356
+ }
357
+ return [d0, d1]
358
+ }
359
+ if (!(vy <= 0) || !this.isAboveGround(step)) return [vx, vz]
360
+ let d0 = vx; let d1 = vz
361
+ const bb = this.bb()
362
+ const free = (x, z) => this.world.noCollision(bb.move(x, -step, z))
363
+ while (d0 !== 0 && free(d0, 0)) { if (d0 < 0.05 && d0 >= -0.05) d0 = 0; else if (d0 > 0) d0 -= 0.05; else d0 += 0.05 }
364
+ while (d1 !== 0 && free(0, d1)) { if (d1 < 0.05 && d1 >= -0.05) d1 = 0; else if (d1 > 0) d1 -= 0.05; else d1 += 0.05 }
365
+ while (d0 !== 0 && d1 !== 0 && free(d0, d1)) {
366
+ if (d0 < 0.05 && d0 >= -0.05) d0 = 0; else if (d0 > 0) d0 -= 0.05; else d0 += 0.05
367
+ if (d1 < 0.05 && d1 >= -0.05) d1 = 0; else if (d1 > 0) d1 -= 0.05; else d1 += 0.05
368
+ }
369
+ return [d0, d1]
370
+ }
371
+
372
+ isHorizontalCollisionMinor (mx, mz) {
373
+ const f = degToRadF(this.yRot)
374
+ const d0 = sin(f); const d1 = cos(f)
375
+ const d2 = this.xxa * d1 - this.zza * d0
376
+ const d3 = this.zza * d1 + this.xxa * d0
377
+ const d4 = d2 * d2 + d3 * d3
378
+ const d5 = mx * mx + mz * mz
379
+ if (!(d4 < F_1EM5) && !(d5 < F_1EM5)) {
380
+ const d6 = d2 * mx + d3 * mz
381
+ return Math.acos(d6 / Math.sqrt(d4 * d5)) < F_ACOS_MINOR
382
+ }
383
+ return false
384
+ }
385
+
386
+ // Entity#move(MoverType.SELF, vec)
387
+ move (vx, vy, vz) {
388
+ if (this.stuck) {
389
+ vx *= this.stuck.x; vy *= this.stuck.y; vz *= this.stuck.z
390
+ this.stuck = null
391
+ this.vel.x = 0; this.vel.y = 0; this.vel.z = 0
392
+ }
393
+ const bo = this.maybeBackOffFromEdge(vx, vy, vz)
394
+ vx = bo[0]; vz = bo[1]
395
+ const bb = this.bb()
396
+ const collide = this.V.modernStepUp ? collideModern : collideLegacy
397
+ const r = collide(this.world, vx, vy, vz, bb, this.onGround, this.maxUpStep())
398
+ const lsq = r.x * r.x + r.y * r.y + r.z * r.z
399
+ if (lsq > 1.0e-7) {
400
+ if (this.V.posFromBoundingBox) {
401
+ const nb = bb.move(r.x, r.y, r.z)
402
+ this.pos.x = (nb.minX + nb.maxX) / 2
403
+ this.pos.y = nb.minY
404
+ this.pos.z = (nb.minZ + nb.maxZ) / 2
405
+ } else {
406
+ this.pos.x += r.x; this.pos.y += r.y; this.pos.z += r.z
407
+ }
408
+ }
409
+ const hx = !equal(vx, r.x); const hz = !equal(vz, r.z)
410
+ this.horizontalCollision = hx || hz
411
+ this.minorHorizontalCollision = this.V.minorCollision && this.horizontalCollision && this.isHorizontalCollisionMinor(r.x, r.z)
412
+ this.verticalCollision = vy !== r.y
413
+ this.onGround = this.verticalCollision && vy < 0
414
+ this.checkSupportingBlock(this.onGround, r.x, r.y, r.z)
415
+
416
+ const op = this.onPos(F_ONPOS)
417
+ const onBlock = this.world.blockAt(op.x, op.y, op.z)
418
+ // checkFallDamage
419
+ if (this.onGround) {
420
+ this.fallDistance = 0
421
+ } else if (r.y < 0) {
422
+ this.fallDistance = this.V.fallDistanceDouble ? this.fallDistance - r.y : f32(this.fallDistance - r.y)
423
+ }
424
+ if (vx !== r.x || vz !== r.z) {
425
+ if (vx !== r.x) this.vel.x = 0
426
+ if (vz !== r.z) this.vel.z = 0
427
+ }
428
+ if (vy !== r.y) this.updateEntityAfterFallOn(onBlock)
429
+ if (this.onGround && !this.input.shift) this.stepOn(onBlock)
430
+ this.checkInsideBlocks()
431
+ const sf = this.blockSpeedFactor()
432
+ this.vel.x *= sf
433
+ this.vel.z *= sf
434
+ }
435
+
436
+ updateEntityAfterFallOn (block) {
437
+ const suppress = this.input.shift
438
+ if (block.isSlime && !suppress) {
439
+ if (this.vel.y < 0) this.vel.y = -this.vel.y
440
+ return
441
+ }
442
+ if (block.isBed && !suppress) {
443
+ if (this.vel.y < 0) this.vel.y = -this.vel.y * F_066
444
+ return
445
+ }
446
+ this.vel.y = 0
447
+ }
448
+
449
+ stepOn (block) {
450
+ if (block.isSlime) {
451
+ const d0 = Math.abs(this.vel.y)
452
+ if (d0 < 0.1) {
453
+ const d1 = 0.4 + d0 * 0.2
454
+ this.vel.x *= d1; this.vel.z *= d1
455
+ }
456
+ }
457
+ }
458
+
459
+ checkInsideBlocks () {
460
+ const bb = this.bb()
461
+ const x0 = Math.floor(bb.minX + 0.001); const x1 = Math.floor(bb.maxX - 0.001)
462
+ const y0 = Math.floor(bb.minY + 0.001); const y1 = Math.floor(bb.maxY - 0.001)
463
+ const z0 = Math.floor(bb.minZ + 0.001); const z1 = Math.floor(bb.maxZ - 0.001)
464
+ for (let x = x0; x <= x1; x++) {
465
+ for (let y = y0; y <= y1; y++) {
466
+ for (let z = z0; z <= z1; z++) {
467
+ const b = this.world.blockAt(x, y, z)
468
+ if (b.isCobweb) this.makeStuck(0.25, F_005, 0.25)
469
+ else if (b.isBerryBush) this.makeStuck(F_080, 0.75, F_080)
470
+ else if (b.isPowderSnow) this.makeStuck(F_090, 1.5, F_090)
471
+ else if (b.isHoney) this.honeySlide(x, y, z)
472
+ }
473
+ }
474
+ }
475
+ }
476
+
477
+ makeStuck (x, y, z) {
478
+ this.fallDistance = 0
479
+ this.stuck = { x, y, z }
480
+ }
481
+
482
+ honeySlide (bx, by, bz) {
483
+ if (this.onGround) return
484
+ if (this.pos.y > by + 0.9375 - 1.0e-7) return
485
+ if (this.vel.y >= -0.08) return
486
+ const d0 = Math.abs(bx + 0.5 - this.pos.x); const d1 = Math.abs(bz + 0.5 - this.pos.z)
487
+ const d2 = 0.4375 + HALF
488
+ if (!(d0 + 1.0e-7 > d2 || d1 + 1.0e-7 > d2)) return
489
+ if (this.vel.y < -0.13) {
490
+ const k = -0.05 / this.vel.y
491
+ this.vel.x *= k; this.vel.y = -0.05; this.vel.z *= k
492
+ } else {
493
+ this.vel.y = -0.05
494
+ }
495
+ this.fallDistance = 0
496
+ }
497
+
498
+ isFree (x, y, z) {
499
+ const bb = this.bb().move(x, y, z)
500
+ return this.world.noCollision(bb) && !this.world.containsAnyLiquid(bb)
501
+ }
502
+
503
+ fluidFallingAdjusted (gravity, falling) {
504
+ if (!this.sprinting) {
505
+ const vy = this.vel.y
506
+ if (falling && Math.abs(vy - 0.005) >= 0.003 && Math.abs(vy - gravity / 16.0) < 0.003) this.vel.y = -0.003
507
+ else this.vel.y = vy - gravity / 16.0
508
+ }
509
+ }
510
+
511
+ flyingSpeed () {
512
+ if (this.V.flyingSpeedLive) {
513
+ if (this.abilities.flying) return this.sprinting ? f32(this.abilities.flyingSpeed * 2) : this.abilities.flyingSpeed
514
+ return this.sprinting ? F_0026 : F_002
515
+ }
516
+ return this.legacyFlyingSpeed
517
+ }
518
+
519
+ frictionInfluencedSpeed (friction) {
520
+ if (this.onGround) return f32(this.speed * f32(F_0216 / f32(f32(friction * friction) * friction)))
521
+ return this.flyingSpeed()
522
+ }
523
+
524
+ handleOnClimbable () {
525
+ if (!this.onClimbable()) return
526
+ this.fallDistance = 0
527
+ this.vel.x = clamp(this.vel.x, -F_015, F_015)
528
+ this.vel.z = clamp(this.vel.z, -F_015, F_015)
529
+ let d2 = Math.max(this.vel.y, -F_015)
530
+ const p = this.blockPosition()
531
+ if (d2 < 0 && !this.world.blockAt(p.x, p.y, p.z).isScaffolding && this.input.shift) d2 = 0
532
+ this.vel.y = d2
533
+ }
534
+
535
+ // LivingEntity#travel
536
+ travel (ix, iy, iz) {
537
+ const V = this.V
538
+ const falling = this.vel.y <= 0
539
+ let gravity = this.gravity()
540
+ if (falling && this.env.effect('slow_falling') != null) {
541
+ gravity = V.attributes2005 ? Math.min(gravity, 0.01) : 0.01
542
+ this.fallDistance = 0
543
+ }
544
+ if (this.isInWater() && this.isAffectedByFluids()) {
545
+ const y0 = this.pos.y
546
+ let f5 = this.sprinting ? F_090 : F_080
547
+ const f6 = F_002
548
+ if (this.env.effect('dolphins_grace') != null) f5 = F_096
549
+ this.moveRelative(f6, ix, iy, iz)
550
+ this.move(this.vel.x, this.vel.y, this.vel.z)
551
+ if (this.horizontalCollision && this.onClimbable()) this.vel.y = 0.2
552
+ this.vel.x *= f5; this.vel.y *= F_080; this.vel.z *= f5
553
+ this.fluidFallingAdjusted(gravity, falling)
554
+ if (this.horizontalCollision && this.isFree(this.vel.x, this.vel.y + 0.6000000238418579 - this.pos.y + y0, this.vel.z)) this.vel.y = 0.30000001192092896
555
+ } else if (this.isInLava() && this.isAffectedByFluids()) {
556
+ const y0 = this.pos.y
557
+ this.moveRelative(F_002, ix, iy, iz)
558
+ this.move(this.vel.x, this.vel.y, this.vel.z)
559
+ if (this.fluidHeight.lava <= 0.4) {
560
+ this.vel.x *= 0.5; this.vel.y *= F_080; this.vel.z *= 0.5
561
+ this.fluidFallingAdjusted(gravity, falling)
562
+ } else {
563
+ this.vel.x *= 0.5; this.vel.y *= 0.5; this.vel.z *= 0.5
564
+ }
565
+ this.vel.y += -gravity / 4.0
566
+ if (this.horizontalCollision && this.isFree(this.vel.x, this.vel.y + 0.6000000238418579 - this.pos.y + y0, this.vel.z)) this.vel.y = 0.30000001192092896
567
+ } else {
568
+ const below = this.posBelowAffectingMovement()
569
+ const friction = this.world.blockAt(below.x, below.y, below.z).friction
570
+ const f4 = this.onGround ? f32(friction * F_091) : F_091
571
+ // handleRelativeFrictionAndCalculateMovement
572
+ this.moveRelative(this.frictionInfluencedSpeed(friction), ix, iy, iz)
573
+ this.handleOnClimbable()
574
+ this.move(this.vel.x, this.vel.y, this.vel.z)
575
+ if ((this.horizontalCollision || this.input.jumping) && this.onClimbable()) this.vel.y = 0.2
576
+ let d2 = this.vel.y
577
+ const lev = this.env.effect('levitation')
578
+ if (lev != null) {
579
+ d2 += (0.05 * (lev + 1) - this.vel.y) * 0.2
580
+ this.fallDistance = 0
581
+ } else if (!this.world.hasChunk(below.x, below.z)) {
582
+ d2 = this.pos.y > this.world.minY ? -0.1 : 0
583
+ } else {
584
+ d2 -= gravity
585
+ }
586
+ this.vel.x *= f4
587
+ this.vel.y = d2 * F_098
588
+ this.vel.z *= f4
589
+ }
590
+ }
591
+
592
+ // Player#travel wrapper (flying)
593
+ playerTravel (ix, iy, iz) {
594
+ if (this.abilities.flying) {
595
+ const d5 = this.vel.y
596
+ const old = this.legacyFlyingSpeed
597
+ if (!this.V.flyingSpeedLive) this.legacyFlyingSpeed = f32(this.abilities.flyingSpeed * (this.sprinting ? 2 : 1))
598
+ this.travel(ix, iy, iz)
599
+ this.vel.y = d5 * 0.6
600
+ if (!this.V.flyingSpeedLive) this.legacyFlyingSpeed = old
601
+ this.fallDistance = 0
602
+ } else {
603
+ this.travel(ix, iy, iz)
604
+ }
605
+ }
606
+
607
+ jumpFromGround () {
608
+ let f
609
+ if (this.V.attributes2005) f = f32(f32(this.env.attribute('jump_strength', 0.42)) * this.blockJumpFactor())
610
+ else f = f32(F_042 * this.blockJumpFactor())
611
+ const jb = this.env.effect('jump_boost')
612
+ if (jb != null) f = f32(f + f32(F_01 * (jb + 1)))
613
+ if (this.V.jumpKeepsHigherY) {
614
+ if (f <= 1.0e-5) return
615
+ this.vel.y = Math.max(f, this.vel.y)
616
+ } else {
617
+ this.vel.y = f
618
+ }
619
+ if (this.sprinting) {
620
+ const r = degToRadF(this.yRot)
621
+ this.vel.x += -sin(r) * F_02
622
+ this.vel.z += cos(r) * F_02
623
+ }
624
+ }
625
+
626
+ // ---------- input ----------
627
+ computeInput (keys, slow) {
628
+ const V = this.V
629
+ const fwd = keys.forward === keys.back ? 0 : (keys.forward ? 1 : -1)
630
+ const left = keys.left === keys.right ? 0 : (keys.left ? 1 : -1)
631
+ let lx = left; let fz = fwd
632
+ if (V.normalizedInput) {
633
+ const l = f32(Math.sqrt(f32(lx * lx + fz * fz)))
634
+ if (l < f32(1.0e-4)) { lx = 0; fz = 0 } else { lx = f32(lx / l); fz = f32(fz / l) }
635
+ if (this.env.usingItem()) { lx = f32(lx * F_02); fz = f32(fz * F_02) }
636
+ if (slow) {
637
+ const s = f32(this.env.attribute('sneaking_speed', 0.3))
638
+ lx = f32(lx * s); fz = f32(fz * s)
639
+ }
640
+ if (V.squareInput) {
641
+ const len = f32(Math.sqrt(f32(lx * lx + fz * fz)))
642
+ if (len > 0) {
643
+ const nx = f32(lx / len); const nz = f32(fz / len)
644
+ const ax = Math.abs(nx); const az = Math.abs(nz)
645
+ const r = az > ax ? f32(ax / az) : f32(az / ax)
646
+ const dist = f32(Math.sqrt(f32(1 + f32(r * r))))
647
+ const k = Math.min(f32(len * dist), 1)
648
+ lx = f32(nx * k); fz = f32(nz * k)
649
+ }
650
+ }
651
+ } else {
652
+ if (slow) { lx = f32(lx * 0.3); fz = f32(fz * 0.3) }
653
+ if (this.env.usingItem()) { lx = f32(lx * F_02); fz = f32(fz * F_02) }
654
+ }
655
+ this.input.left = lx
656
+ this.input.forward = fz
657
+ this.input.rawForward = fwd
658
+ this.input.jumping = !!keys.jump
659
+ this.input.shift = !!keys.sneak
660
+ this.input.keys = { ...keys }
661
+ }
662
+
663
+ hasEnoughImpulseToStartSprinting () {
664
+ if (this.isUnderWater()) return this.input.forward > F_1EM5
665
+ return (this.V.normalizedInput ? this.input.rawForward : this.input.forward) >= 0.8
666
+ }
667
+
668
+ setSprinting (v) {
669
+ this.sprinting = v
670
+ }
671
+
672
+ // ---------- one client tick (LocalPlayer#tick -> aiStep) ----------
673
+ tick (keys) {
674
+ const V = this.V
675
+ // Entity#baseTick
676
+ this.updateFluids()
677
+ this.firstTick = false
678
+
679
+ // LocalPlayer#aiStep
680
+ if (this.sprintTriggerTime > 0) this.sprintTriggerTime--
681
+ const prevJump = this.input.jumping
682
+ const prevShift = this.input.shift
683
+ const prevEnough = this.hasEnoughImpulseToStartSprinting()
684
+ const crouchingNow = !this.abilities.flying && prevShift && this.canEnterPose(true)
685
+ this.computeInput(keys, crouchingNow)
686
+ const canSprintFood = this.env.food() > 6 || this.abilities.mayfly
687
+ const blind = this.env.effect('blindness') != null
688
+ if (prevShift) this.sprintTriggerTime = 0
689
+ if ((this.onGround || this.isUnderWater()) && !prevShift && !prevEnough && this.hasEnoughImpulseToStartSprinting() &&
690
+ !this.sprinting && canSprintFood && !this.env.usingItem() && !blind) {
691
+ if (this.sprintTriggerTime <= 0 && !keys.sprint) this.sprintTriggerTime = 7
692
+ else this.setSprinting(true)
693
+ }
694
+ if (!this.sprinting && (!this.isInWater() || this.isUnderWater()) && this.hasEnoughImpulseToStartSprinting() &&
695
+ canSprintFood && !this.env.usingItem() && !blind && keys.sprint && !this.input.shift) {
696
+ this.setSprinting(true)
697
+ }
698
+ if (this.sprinting) {
699
+ const noForward = !(this.input.forward > F_1EM5) || !canSprintFood
700
+ const collided = V.minorCollision ? (this.horizontalCollision && !this.minorHorizontalCollision) : this.horizontalCollision
701
+ if (noForward || collided || (this.isInWater() && !this.isUnderWater()) || (V.normalizedInput && this.input.shift)) this.setSprinting(false)
702
+ }
703
+ // double-tap jump toggles flight when allowed
704
+ if (this.abilities.mayfly && this.env.gameMode() !== 'spectator') {
705
+ if (!prevJump && this.input.jumping) {
706
+ if (this.jumpTriggerTime === 0) {
707
+ this.jumpTriggerTime = 7
708
+ } else {
709
+ this.abilities.flying = !this.abilities.flying
710
+ this.events.abilitiesChanged = true
711
+ this.jumpTriggerTime = 0
712
+ }
713
+ }
714
+ }
715
+ if (this.isInWater() && this.input.shift && this.isAffectedByFluids()) this.vel.y -= F_004
716
+ if (this.abilities.flying) {
717
+ let j = 0
718
+ if (this.input.shift) j--
719
+ if (this.input.jumping) j++
720
+ if (j !== 0) this.vel.y += f32(f32(j * this.abilities.flyingSpeed) * 3)
721
+ }
722
+
723
+ // Player#aiStep
724
+ if (this.jumpTriggerTime > 0) this.jumpTriggerTime--
725
+
726
+ // LivingEntity#aiStep
727
+ if (this.noJumpDelay > 0) this.noJumpDelay--
728
+ if (Math.abs(this.vel.x) < 0.003) this.vel.x = 0
729
+ if (Math.abs(this.vel.y) < 0.003) this.vel.y = 0
730
+ if (Math.abs(this.vel.z) < 0.003) this.vel.z = 0
731
+ this.xxa = this.input.left
732
+ this.zza = this.input.forward
733
+ const jumping = this.input.jumping
734
+ if (jumping && this.isAffectedByFluids()) {
735
+ const d7 = this.isInLava() ? this.fluidHeight.lava : this.fluidHeight.water
736
+ const inW = this.isInWater() && d7 > 0
737
+ const d8 = 0.4
738
+ if (!inW || (this.onGround && !(d7 > d8))) {
739
+ if (!this.isInLava() || (this.onGround && !(d7 > d8))) {
740
+ if ((this.onGround || (inW && d7 <= d8)) && this.noJumpDelay === 0) {
741
+ this.jumpFromGround()
742
+ this.noJumpDelay = 10
743
+ }
744
+ } else {
745
+ this.vel.y += F_004
746
+ }
747
+ } else {
748
+ this.vel.y += F_004
749
+ }
750
+ } else {
751
+ this.noJumpDelay = 0
752
+ }
753
+ this.xxa = f32(this.xxa * F_098)
754
+ this.zza = f32(this.zza * F_098)
755
+ if (this.env.gameMode() === 'spectator') {
756
+ this.pos.x += this.vel.x; this.pos.y += this.vel.y; this.pos.z += this.vel.z
757
+ } else {
758
+ this.playerTravel(this.xxa, 0, this.zza)
759
+ }
760
+
761
+ // end of Player#aiStep: values used on the *next* tick
762
+ this.legacyFlyingSpeed = this.sprinting ? f32(F_002 + F_002 * 0.3) : F_002
763
+ this.speed = f32(this.env.movementSpeed(this.sprinting))
764
+
765
+ // LocalPlayer#aiStep tail
766
+ if (this.onGround && this.abilities.flying && this.env.gameMode() !== 'spectator') {
767
+ this.abilities.flying = false
768
+ this.events.abilitiesChanged = true
769
+ }
770
+
771
+ // Player#tick -> updatePlayerPose
772
+ const wantCrouch = !this.abilities.flying && this.input.shift
773
+ if (wantCrouch !== this.crouching) {
774
+ if (wantCrouch) this.crouching = this.canEnterPose(true)
775
+ else if (this.canEnterPose(false)) this.crouching = false
776
+ }
777
+ }
778
+ }
779
+
780
+ module.exports = { PlayerPhysics, HALF, HEIGHT_STAND }