minecraft-renderer 0.1.64 → 0.1.66

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,14 +45,6 @@ export const TWEEN_DURATION = 120
45
45
 
46
46
  const degreesToRadians = (degrees: number) => degrees * (Math.PI / 180)
47
47
 
48
- const clamp01 = (v: number) => Math.max(0, Math.min(1, v))
49
- const clamp = (v: number, a: number, b: number) => Math.max(a, Math.min(b, v))
50
- const wrapPi = (a: number) => {
51
- a = (a + Math.PI) % (Math.PI * 2)
52
- if (a < 0) a += Math.PI * 2
53
- return a - Math.PI
54
- }
55
-
56
48
  function convert2sComplementToHex(complement: number) {
57
49
  if (complement < 0) {
58
50
  complement = (0xFF_FF_FF_FF + complement + 1) >>> 0
@@ -280,130 +272,45 @@ export class Entities {
280
272
  pendingModelOverrides = new Map<string, { parts: EntityModelOverridePart[] }>()
281
273
 
282
274
  private motionCache = new Map<string, { pos: THREE.Vector3, speed: number }>()
283
- private _wasThirdPerson = false
275
+ private readonly MOVE_ON = 0.05
276
+ private readonly MOVE_OFF = 0.02
277
+ private readonly RUN_ON = 4.8
278
+ private readonly RUN_OFF = 4.2
284
279
 
285
- private _tpBodyYaw = 0
286
- private _tpHasBodyYaw = false
287
-
288
- private updateAutoWalkFlags(entityKey: string, entity: SceneEntity, dt: number, samplePos?: THREE.Vector3) {
280
+ private updateAutoWalkFlags(entityKey: string, entity: SceneEntity, dt: number) {
289
281
  if (!entity.playerObject?.animation) return
290
282
  const anim: any = entity.playerObject.animation
283
+ if (!('isMoving' in anim) || !('isRunning' in anim)) return
291
284
  if (dt <= 0) return
292
285
 
293
- const pos = samplePos ?? entity.position
294
-
295
286
  const cached = this.motionCache.get(entityKey)
296
287
  if (!cached) {
297
- this.motionCache.set(entityKey, { pos: pos.clone(), speed: 0 })
288
+ this.motionCache.set(entityKey, { pos: entity.position.clone(), speed: 0 })
298
289
  anim.isMoving = false
299
290
  anim.isRunning = false
300
- anim.moveAmount = 0
301
- anim.runAmount = 0
302
291
  return
303
292
  }
304
293
 
305
- const dx = pos.x - cached.pos.x
306
- const dz = pos.z - cached.pos.z
307
- cached.pos.copy(pos)
294
+ const dx = entity.position.x - cached.pos.x
295
+ const dz = entity.position.z - cached.pos.z
296
+ cached.pos.copy(entity.position)
308
297
 
309
- const instSpeed = Math.hypot(dx, dz) / Math.max(dt, 1e-6) // blocks/sec
298
+ const instSpeed = Math.hypot(dx, dz) / Math.max(dt, 1e-6)
310
299
 
311
- const alpha = 1 - Math.exp(-dt * 12)
312
- cached.speed += (instSpeed - cached.speed) * alpha
313
-
314
- const speed = cached.speed
315
-
316
- const smoothstep = (a: number, b: number, x: number) => {
317
- const t = clamp01((x - a) / (b - a))
318
- return t * t * (3 - 2 * t)
319
- }
300
+ cached.speed = cached.speed * 0.8 + instSpeed * 0.2
320
301
 
321
- const MOVE_START = 0.08
322
- const MOVE_FULL = 0.6
302
+ const movingNow = anim.isMoving
303
+ ? cached.speed > this.MOVE_OFF
304
+ : cached.speed > this.MOVE_ON
323
305
 
324
- const WALK_MAX = 4.3
325
- const SPRINT_FULL = 5.6
306
+ const runningNow = anim.isRunning
307
+ ? cached.speed > this.RUN_OFF
308
+ : cached.speed > this.RUN_ON
326
309
 
327
- const moveAmount = smoothstep(MOVE_START, MOVE_FULL, speed)
328
- const runAmount = smoothstep(WALK_MAX, SPRINT_FULL, speed)
329
-
330
- anim.moveAmount = moveAmount
331
- anim.runAmount = runAmount
332
-
333
- anim.isMoving = moveAmount > 0.15
334
- anim.isRunning = runAmount > 0.55
335
- }
336
-
337
- private getLocalSneak(): boolean {
338
- const ps: any = this.worldRenderer.playerStateReactive as any
339
- return (
340
- ps?.movementState === 'SNEAKING' ||
341
- ps?.movementState === 'CROUCHING' ||
342
- !!ps?.isSneaking ||
343
- !!ps?.sneaking ||
344
- !!ps?.isCrouching ||
345
- !!ps?.crouching ||
346
- !!ps?.controls?.sneak ||
347
- !!ps?.controlState?.sneak
348
- )
310
+ anim.isMoving = movingNow
311
+ anim.isRunning = movingNow && runningNow
349
312
  }
350
313
 
351
- private updateThirdPersonHeadAndBody(entity: SceneEntity, dt: number) {
352
- const anim: any = entity.playerObject?.animation
353
- const rotation = this.worldRenderer.cameraShake.getBaseRotation()
354
-
355
- const camYaw = rotation.yaw
356
- const camPitch = rotation.pitch
357
-
358
- const move = clamp01(anim?.moveAmount ?? (anim?.isMoving ? 1 : 0))
359
- const run = clamp01(anim?.runAmount ?? (anim?.isRunning ? 1 : 0))
360
- const moving = move > 0.15
361
-
362
- if (!this._tpHasBodyYaw) {
363
- this._tpHasBodyYaw = true
364
- this._tpBodyYaw = camYaw
365
- }
366
-
367
- if (moving) {
368
- const k = 1 - Math.exp(-dt * (18 + 10 * run))
369
- this._tpBodyYaw = this._tpBodyYaw + wrapPi(camYaw - this._tpBodyYaw) * k
370
-
371
- entity.rotation.set(0, this._tpBodyYaw, 0)
372
-
373
- const headMax = ((25 - 8 * run) * Math.PI) / 180
374
- const headYaw = clamp(wrapPi(camYaw - this._tpBodyYaw), -headMax, headMax)
375
-
376
- if (anim && 'lookYaw' in anim) anim.lookYaw = headYaw
377
- if (anim && 'lookPitch' in anim) anim.lookPitch = -camPitch
378
- return
379
- }
380
-
381
- // idle behavior (head leads, body follows after threshold)
382
- const maxHeadYaw = (85 * Math.PI) / 180
383
- const bodyFollowStart = (55 * Math.PI) / 180
384
-
385
- let desiredHeadYaw = wrapPi(camYaw - this._tpBodyYaw)
386
- desiredHeadYaw = clamp(desiredHeadYaw, -maxHeadYaw, maxHeadYaw)
387
-
388
- const excess = Math.abs(desiredHeadYaw) - bodyFollowStart
389
- if (excess > 0) {
390
- const push = excess / (maxHeadYaw - bodyFollowStart)
391
- const targetBodyYaw = camYaw - Math.sign(desiredHeadYaw) * bodyFollowStart
392
- const k = 1 - Math.exp(-dt * (6 + 10 * push))
393
- this._tpBodyYaw = this._tpBodyYaw + wrapPi(targetBodyYaw - this._tpBodyYaw) * k
394
- desiredHeadYaw = wrapPi(camYaw - this._tpBodyYaw)
395
- desiredHeadYaw = clamp(desiredHeadYaw, -maxHeadYaw, maxHeadYaw)
396
- } else {
397
- const k = 1 - Math.exp(-dt * 5)
398
- this._tpBodyYaw = this._tpBodyYaw + wrapPi(camYaw - this._tpBodyYaw) * (k * 0.15)
399
- }
400
-
401
- entity.rotation.set(0, this._tpBodyYaw, 0)
402
- if (anim && 'lookYaw' in anim) anim.lookYaw = desiredHeadYaw
403
- if (anim && 'lookPitch' in anim) anim.lookPitch = -camPitch
404
- }
405
-
406
-
407
314
  get entitiesByName(): Record<string, SceneEntity[]> {
408
315
  const byName: Record<string, SceneEntity[]> = {}
409
316
  for (const entity of Object.values(this.entities)) {
@@ -477,8 +384,6 @@ export class Entities {
477
384
  this.currentSkinUrls = {}
478
385
 
479
386
  this.motionCache.clear()
480
- this._wasThirdPerson = false
481
- this._tpHasBodyYaw = false
482
387
 
483
388
  // Clean up player entity
484
389
  if (this.playerEntity) {
@@ -547,93 +452,67 @@ export class Entities {
547
452
  const botPos = this.worldRenderer.viewerChunkPosition
548
453
  const VISIBLE_DISTANCE = 10 * 10
549
454
 
550
- const thirdPersonNow = this.worldRenderer.playerStateUtils.isThirdPerson()
551
- if (thirdPersonNow !== this._wasThirdPerson) {
552
- this._wasThirdPerson = thirdPersonNow
553
- const key = String(this.playerEntity?.originalEntity.id ?? 'player_entity')
554
- this.motionCache.delete(key)
555
-
556
- const anim: any = this.playerEntity?.playerObject?.animation
557
- if (anim?.resetLocomotion) anim.resetLocomotion()
558
-
559
- this._tpHasBodyYaw = false
560
- }
561
-
562
455
  for (const [entityIdRaw, entity] of [...Object.entries(this.entities), ['player_entity', this.playerEntity] as [string, SceneEntity | null]]) {
563
456
  if (!entity) continue
564
457
 
458
+ let entityKey = entityIdRaw
565
459
  const isPlayerEntity = entityIdRaw === 'player_entity'
566
- const entityKey = isPlayerEntity ? String(this.playerEntity?.originalEntity.id ?? 'player_entity') : String(entityIdRaw)
567
460
 
568
461
  if (isPlayerEntity) {
569
- entity.visible = thirdPersonNow
462
+ const thirdPerson = this.worldRenderer.playerStateUtils.isThirdPerson()
463
+ entity.visible = thirdPerson
570
464
 
571
- if (thirdPersonNow) {
465
+ if (thirdPerson) {
572
466
  const yOffset = this.worldRenderer.playerStateReactive.eyeHeight
573
- // Set world position — proxy auto-converts to scene coords
574
467
  entity.position.set(
575
468
  this.worldRenderer.cameraWorldPos.x,
576
469
  this.worldRenderer.cameraWorldPos.y - yOffset,
577
470
  this.worldRenderer.cameraWorldPos.z
578
471
  )
579
-
580
- const p: any = (this.worldRenderer.playerStateReactive as any).position
581
- if (p && typeof p.x === 'number') {
582
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, new THREE.Vector3(p.x, p.y, p.z))
583
- } else {
584
- const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
585
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
586
- }
587
-
588
- this.updateThirdPersonHeadAndBody(entity, dt)
589
- } else {
590
- const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
591
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
592
472
  }
593
- } else {
594
- const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
595
- this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
473
+
474
+ entityKey = String(this.playerEntity?.originalEntity.id ?? 'player_entity')
596
475
  }
597
476
 
598
477
  const { playerObject } = entity
599
478
 
600
- if (playerObject?.animation) {
601
- const anim: any = playerObject.animation
602
-
603
- const flags = Number((entity.originalEntity?.metadata?.[0] ?? 0))
604
- const remoteSneak = (flags & 0x02) !== 0
605
-
606
- const localSneak = this.getLocalSneak()
607
-
608
- if ('isCrouched' in anim) anim.isCrouched = isPlayerEntity ? localSneak : remoteSneak
479
+ this.updateAutoWalkFlags(entityKey, entity, dtRaw)
609
480
 
481
+ if (playerObject?.animation) {
610
482
  playerObject.animation.update(playerObject, dt)
611
483
  }
612
484
 
613
- // Update GLTF animations
614
485
  entity.traverse(child => {
615
486
  if (child instanceof Entity.EntityMesh) {
616
487
  child.update(dt)
617
488
  }
618
489
  })
619
490
 
620
- // Update visibility based on distance and chunk load status
621
491
  if (!isPlayerEntity && botPos && entity.position) {
622
492
  const dx = entity.position.x - botPos.x
623
493
  const dy = entity.position.y - botPos.y
624
494
  const dz = entity.position.z - botPos.z
625
495
  const distanceSquared = dx * dx + dy * dy + dz * dz
626
496
 
627
- // Entity is visible if within 20 blocks OR in a finished chunk
628
497
  entity.visible = !!(distanceSquared < VISIBLE_DISTANCE || this.worldRenderer.shouldObjectVisible(entity))
629
498
 
630
499
  this.maybeRenderPlayerSkin(entityIdRaw)
631
500
  }
632
501
 
633
502
  if (entity.visible) {
634
- // Update armor positions
635
503
  this.syncArmorPositions(entity)
636
504
  }
505
+
506
+ if (isPlayerEntity && entity.visible) {
507
+ const rotation = this.worldRenderer.cameraShake.getBaseRotation()
508
+ entity.rotation.set(0, rotation.yaw, 0)
509
+
510
+ entity.traverse((c) => {
511
+ if (c.name === 'head') {
512
+ c.rotation.set(-rotation.pitch, 0, 0)
513
+ }
514
+ })
515
+ }
637
516
  }
638
517
  }
639
518
 
@@ -2,27 +2,7 @@
2
2
  import { PlayerAnimation } from 'skinview3d'
3
3
 
4
4
  const clamp01 = (v) => Math.max(0, Math.min(1, v))
5
- const clamp = (v, a, b) => Math.max(a, Math.min(b, v))
6
5
  const mix = (a, b, t) => a + (b - a) * t
7
- const wrapPi = (a) => {
8
- a = (a + Math.PI) % (Math.PI * 2)
9
- if (a < 0) a += Math.PI * 2
10
- return a - Math.PI
11
- }
12
-
13
- /**
14
- * @typedef {{
15
- * playerRot: any,
16
- * bodyPos: any, bodyRot: any,
17
- * leftArmPos: any, leftArmRot: any,
18
- * rightArmPos: any, rightArmRot: any,
19
- * leftLegPos: any, leftLegRot: any,
20
- * rightLegPos: any, rightLegRot: any,
21
- * headPos: any, headRot: any,
22
- * capePos: any, capeRot: any,
23
- * elytraPos: any, elytraRot: any,
24
- * }} Defaults
25
- */
26
6
 
27
7
  function updateElytraRightWing(player) {
28
8
  const elytra = player?.elytra
@@ -53,32 +33,24 @@ export class WalkingGeneralSwing extends PlayerAnimation {
53
33
  isMoving = true
54
34
  isCrouched = false
55
35
 
56
- /** @type {number} 0..1 */
57
- moveAmount = 0
58
- /** @type {number} 0..1 */
59
- runAmount = 0
60
-
61
- /** @type {number} radians */
62
- lookYaw = 0
63
- /** @type {number} radians */
64
- lookPitch = 0
65
-
66
36
  _dt = 0
67
37
  _phase = 0
68
- _idlePhase = 0
69
-
70
38
  _moveBlend = 0
71
- _runBlend = 0
72
- _crouchBlend = 0
73
-
74
- _lookYawBlend = 0
75
- _lookPitchBlend = 0
76
39
 
77
40
  /** @type {number | null} */
78
41
  _swingTime = null
79
42
  _swingDuration = 0.25
80
43
 
81
- /** @type {Defaults | null} */
44
+ /** @type {{
45
+ bodyPos: any, bodyRot: any,
46
+ leftArmPos: any, leftArmRot: any,
47
+ rightArmPos: any, rightArmRot: any,
48
+ leftLegPos: any, leftLegRot: any,
49
+ rightLegPos: any, rightLegRot: any,
50
+ headPos: any, headRot: any,
51
+ capePos: any, capeRot: any,
52
+ elytraPos: any, elytraRot: any,
53
+ } | null} */
82
54
  _defaults = null
83
55
 
84
56
  update(player, delta) {
@@ -96,39 +68,29 @@ export class WalkingGeneralSwing extends PlayerAnimation {
96
68
  }
97
69
  }
98
70
 
99
- resetLocomotion() {
100
- this._moveBlend = 0
101
- this._runBlend = 0
102
- this._crouchBlend = 0
103
- this._phase = 0
104
- }
105
-
106
71
  _captureDefaults(player) {
107
- const skin = player?.skin
108
72
  this._defaults = {
109
- playerRot: player?.rotation?.clone?.(),
73
+ bodyPos: player.skin.body.position.clone(),
74
+ bodyRot: player.skin.body.rotation.clone(),
110
75
 
111
- bodyPos: skin?.body?.position?.clone?.(),
112
- bodyRot: skin?.body?.rotation?.clone?.(),
76
+ leftArmPos: player.skin.leftArm.position.clone(),
77
+ leftArmRot: player.skin.leftArm.rotation.clone(),
78
+ rightArmPos: player.skin.rightArm.position.clone(),
79
+ rightArmRot: player.skin.rightArm.rotation.clone(),
113
80
 
114
- leftArmPos: skin?.leftArm?.position?.clone?.(),
115
- leftArmRot: skin?.leftArm?.rotation?.clone?.(),
116
- rightArmPos: skin?.rightArm?.position?.clone?.(),
117
- rightArmRot: skin?.rightArm?.rotation?.clone?.(),
81
+ leftLegPos: player.skin.leftLeg.position.clone(),
82
+ leftLegRot: player.skin.leftLeg.rotation.clone(),
83
+ rightLegPos: player.skin.rightLeg.position.clone(),
84
+ rightLegRot: player.skin.rightLeg.rotation.clone(),
118
85
 
119
- leftLegPos: skin?.leftLeg?.position?.clone?.(),
120
- leftLegRot: skin?.leftLeg?.rotation?.clone?.(),
121
- rightLegPos: skin?.rightLeg?.position?.clone?.(),
122
- rightLegRot: skin?.rightLeg?.rotation?.clone?.(),
86
+ headPos: player.skin.head.position.clone(),
87
+ headRot: player.skin.head.rotation.clone(),
123
88
 
124
- headPos: skin?.head?.position?.clone?.(),
125
- headRot: skin?.head?.rotation?.clone?.(),
89
+ capePos: player.cape.position.clone(),
90
+ capeRot: player.cape.rotation.clone(),
126
91
 
127
- capePos: player?.cape?.position?.clone?.(),
128
- capeRot: player?.cape?.rotation?.clone?.(),
129
-
130
- elytraPos: player?.elytra?.position?.clone?.(),
131
- elytraRot: player?.elytra?.rotation?.clone?.(),
92
+ elytraPos: player.elytra.position.clone(),
93
+ elytraRot: player.elytra.rotation.clone(),
132
94
  }
133
95
  }
134
96
 
@@ -136,152 +98,102 @@ export class WalkingGeneralSwing extends PlayerAnimation {
136
98
  const d = this._defaults
137
99
  if (!d) return
138
100
 
139
- const skin = player?.skin
140
- const cape = player?.cape
141
- const elytra = player?.elytra
142
-
143
- if (d.playerRot && player?.rotation) player.rotation.copy(d.playerRot)
101
+ player.skin.body.position.copy(d.bodyPos)
102
+ player.skin.body.rotation.copy(d.bodyRot)
144
103
 
145
- if (d.bodyPos && skin?.body?.position) skin.body.position.copy(d.bodyPos)
146
- if (d.bodyRot && skin?.body?.rotation) skin.body.rotation.copy(d.bodyRot)
104
+ player.skin.leftArm.position.copy(d.leftArmPos)
105
+ player.skin.leftArm.rotation.copy(d.leftArmRot)
106
+ player.skin.rightArm.position.copy(d.rightArmPos)
107
+ player.skin.rightArm.rotation.copy(d.rightArmRot)
147
108
 
148
- if (d.leftArmPos && skin?.leftArm?.position) skin.leftArm.position.copy(d.leftArmPos)
149
- if (d.leftArmRot && skin?.leftArm?.rotation) skin.leftArm.rotation.copy(d.leftArmRot)
109
+ player.skin.leftLeg.position.copy(d.leftLegPos)
110
+ player.skin.leftLeg.rotation.copy(d.leftLegRot)
111
+ player.skin.rightLeg.position.copy(d.rightLegPos)
112
+ player.skin.rightLeg.rotation.copy(d.rightLegRot)
150
113
 
151
- if (d.rightArmPos && skin?.rightArm?.position) skin.rightArm.position.copy(d.rightArmPos)
152
- if (d.rightArmRot && skin?.rightArm?.rotation) skin.rightArm.rotation.copy(d.rightArmRot)
114
+ player.skin.head.position.copy(d.headPos)
115
+ player.skin.head.rotation.copy(d.headRot)
153
116
 
154
- if (d.leftLegPos && skin?.leftLeg?.position) skin.leftLeg.position.copy(d.leftLegPos)
155
- if (d.leftLegRot && skin?.leftLeg?.rotation) skin.leftLeg.rotation.copy(d.leftLegRot)
117
+ player.cape.position.copy(d.capePos)
118
+ player.cape.rotation.copy(d.capeRot)
156
119
 
157
- if (d.rightLegPos && skin?.rightLeg?.position) skin.rightLeg.position.copy(d.rightLegPos)
158
- if (d.rightLegRot && skin?.rightLeg?.rotation) skin.rightLeg.rotation.copy(d.rightLegRot)
159
-
160
- if (d.headPos && skin?.head?.position) skin.head.position.copy(d.headPos)
161
- if (d.headRot && skin?.head?.rotation) skin.head.rotation.copy(d.headRot)
162
-
163
- if (d.capePos && cape?.position) cape.position.copy(d.capePos)
164
- if (d.capeRot && cape?.rotation) cape.rotation.copy(d.capeRot)
165
-
166
- if (d.elytraPos && elytra?.position) elytra.position.copy(d.elytraPos)
167
- if (d.elytraRot && elytra?.rotation) elytra.rotation.copy(d.elytraRot)
120
+ player.elytra.position.copy(d.elytraPos)
121
+ player.elytra.rotation.copy(d.elytraRot)
168
122
  }
169
123
 
170
124
  animate(player) {
171
125
  const dt = this._dt || 0
126
+
172
127
  if (!this._defaults) this._captureDefaults(player)
173
128
  this._applyDefaults(player)
174
129
 
175
- const externalMove = typeof this.moveAmount === 'number' ? this.moveAmount : (this.isMoving ? 1 : 0)
176
- const externalRun = typeof this.runAmount === 'number' ? this.runAmount : (this.isRunning ? 1 : 0)
177
-
178
- const targetMove = clamp01(externalMove)
179
- const targetRun = clamp01(externalRun)
180
- const targetCrouch = this.isCrouched ? 1 : 0
181
-
182
- const kMove = Math.min(1, dt * 8)
183
- const kRun = Math.min(1, dt * 6)
184
- const kCrouch = Math.min(1, dt * 7)
185
-
130
+ const targetMove = this.isMoving ? 1 : 0
131
+ const kMove = Math.min(1, dt * 20)
186
132
  this._moveBlend += (targetMove - this._moveBlend) * kMove
187
- this._runBlend += (targetRun - this._runBlend) * kRun
188
- this._crouchBlend += (targetCrouch - this._crouchBlend) * kCrouch
189
133
 
190
- const moveBlend = clamp01(this._moveBlend)
191
- const runBlend = clamp01(this._runBlend)
192
- const crouchBlend = clamp01(this._crouchBlend)
134
+ const speed = this.isRunning ? 10 : 8
135
+ this._phase += dt * speed * this._moveBlend
193
136
 
194
- const baseSpeed = mix(8, 10, runBlend)
195
- const crouchSpeedMul = mix(1, 0.55, crouchBlend)
196
- const speed = baseSpeed * crouchSpeedMul
137
+ const t = this._phase + (this.isRunning ? Math.PI * 0.5 : 0)
138
+ let reset = false
197
139
 
198
- this._phase += dt * speed * moveBlend
199
- this._idlePhase += dt * 1.15
140
+ applyCrouchPose(player, this.isCrouched ? 1 : 0)
200
141
 
201
- const t = this._phase + (runBlend > 0.5 ? Math.PI * 0.5 : 0)
202
-
203
- if (this.switchAnimationCallback) {
204
- const boundary = Math.abs(Math.sin(t))
205
- if (boundary < 0.02) {
206
- const cb = this.switchAnimationCallback
207
- this.switchAnimationCallback = null
208
- cb?.()
142
+ const boundary = this.isRunning ? Math.cos(t) : Math.sin(t)
143
+ if (Math.abs(boundary) < 0.02) {
144
+ if (this.switchAnimationCallback) {
145
+ reset = true
209
146
  }
210
147
  }
211
148
 
212
- applyCrouchPose(player, crouchBlend)
213
-
214
- const maxYaw = (80 * Math.PI) / 180
215
- const maxPitch = (75 * Math.PI) / 180
216
- const targetLookYaw = clamp(wrapPi(this.lookYaw || 0), -maxYaw, maxYaw)
217
- const targetLookPitch = clamp(this.lookPitch || 0, -maxPitch, maxPitch)
218
-
219
- const kLook = Math.min(1, dt * 14)
220
- this._lookYawBlend += (targetLookYaw - this._lookYawBlend) * kLook
221
- this._lookPitchBlend += (targetLookPitch - this._lookPitchBlend) * kLook
222
-
223
- if (player?.skin?.head?.rotation) {
224
- player.skin.head.rotation.y += this._lookYawBlend
225
- player.skin.head.rotation.x += this._lookPitchBlend
149
+ if (this.isRunning) {
150
+ player.skin.leftLeg.rotation.x = Math.cos(t + Math.PI) * 1.3
151
+ player.skin.rightLeg.rotation.x = Math.cos(t) * 1.3
152
+ } else {
153
+ player.skin.leftLeg.rotation.x = Math.sin(t) * 0.5
154
+ player.skin.rightLeg.rotation.x = Math.sin(t + Math.PI) * 0.5
226
155
  }
227
156
 
228
- const idleStrength = (1 - moveBlend) * (1 - 0.25 * runBlend)
229
- if (idleStrength > 0.0001 && player?.skin) {
230
- const b = Math.sin(this._idlePhase)
231
- player.skin.body.rotation.x += b * 0.02 * idleStrength
232
- player.skin.head.rotation.x += -b * 0.015 * idleStrength
233
- player.skin.leftArm.rotation.x += b * 0.03 * idleStrength
234
- player.skin.rightArm.rotation.x += -b * 0.03 * idleStrength
235
- if (player?.cape?.rotation) player.cape.rotation.x += Math.sin(this._idlePhase * 0.7) * 0.03 * idleStrength
236
- }
237
-
238
- if (moveBlend > 0.0001 && player?.skin) {
239
- const legAmp = mix(1, 0.85, crouchBlend)
240
- const armAmp = mix(1, 0.7, crouchBlend)
241
-
242
- const walkLegL = Math.sin(t) * 0.5
243
- const walkLegR = Math.sin(t + Math.PI) * 0.5
244
- const runLegL = Math.cos(t + Math.PI) * 1.3
245
- const runLegR = Math.cos(t) * 1.3
246
-
247
- player.skin.leftLeg.rotation.x += mix(walkLegL, runLegL, runBlend) * moveBlend * legAmp
248
- player.skin.rightLeg.rotation.x += mix(walkLegR, runLegR, runBlend) * moveBlend * legAmp
249
-
250
- const walkArmL = Math.sin(t + Math.PI) * 0.5
251
- const walkArmR = Math.sin(t) * 0.5
252
- const runArmL = Math.cos(t) * 1.5
253
- const runArmR = Math.cos(t + Math.PI) * 1.5
254
-
255
- player.skin.leftArm.rotation.x += mix(walkArmL, runArmL, runBlend) * moveBlend * armAmp
256
- player.skin.rightArm.rotation.x += mix(walkArmR, runArmR, runBlend) * moveBlend * armAmp
257
-
258
- const walkArmZBase = Math.PI * 0.02
259
- const runArmZBase = Math.PI * 0.1
260
- const armZBase = mix(walkArmZBase, runArmZBase, runBlend)
157
+ if (this.isRunning) {
158
+ player.skin.leftArm.rotation.x = Math.cos(t) * 1.5
159
+ player.skin.rightArm.rotation.x = Math.cos(t + Math.PI) * 1.5
261
160
 
262
- const walkArmZL = Math.cos(t) * 0.03 + walkArmZBase
263
- const walkArmZR = Math.cos(t + Math.PI) * 0.03 - walkArmZBase
264
- const runArmZL = Math.cos(t) * 0.1 + runArmZBase
265
- const runArmZR = Math.cos(t + Math.PI) * 0.1 - runArmZBase
161
+ const basicArmRotationZ = Math.PI * 0.1
162
+ player.skin.leftArm.rotation.z = Math.cos(t) * 0.1 + basicArmRotationZ
163
+ player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.1 - basicArmRotationZ
164
+ } else {
165
+ player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.5
166
+ player.skin.rightArm.rotation.x = Math.sin(t) * 0.5
266
167
 
267
- player.skin.leftArm.rotation.z += (mix(walkArmZL, runArmZL, runBlend) * moveBlend + armZBase * 0.15 * moveBlend) * armAmp
268
- player.skin.rightArm.rotation.z += (mix(walkArmZR, runArmZR, runBlend) * moveBlend - armZBase * 0.15 * moveBlend) * armAmp
269
-
270
- if (this._defaults?.playerRot) {
271
- player.rotation.z = this._defaults.playerRot.z + Math.cos(t + Math.PI) * 0.01 * runBlend * moveBlend
272
- }
273
-
274
- const capeBase = mix(Math.PI * 0.06, Math.PI * 0.3, runBlend)
275
- const capeWave = mix(Math.sin(t / 1.5) * 0.06, Math.sin(t * 2) * 0.1, runBlend)
276
- if (player?.cape?.rotation) player.cape.rotation.x += (capeBase + capeWave) * moveBlend
168
+ const basicArmRotationZ = Math.PI * 0.02
169
+ player.skin.leftArm.rotation.z = Math.cos(t) * 0.03 + basicArmRotationZ
170
+ player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.03 - basicArmRotationZ
277
171
  }
278
172
 
279
173
  if (this._swingTime !== null) {
280
174
  this._swingTime += dt
281
175
  const p = Math.min(this._swingTime / this._swingDuration, 1)
282
- HitAnimation.animate(p, player, moveBlend > 0.2)
176
+ HitAnimation.animate(p, player, this.isMoving)
283
177
  if (p >= 1) this._swingTime = null
284
178
  }
179
+
180
+ if (this.isRunning) {
181
+ player.rotation.z = Math.cos(t + Math.PI) * 0.01
182
+ }
183
+
184
+ if (this.isRunning) {
185
+ const basicCapeRotationX = Math.PI * 0.3
186
+ player.cape.rotation.x = Math.sin(t * 2) * 0.1 + basicCapeRotationX
187
+ } else {
188
+ const basicCapeRotationX = Math.PI * 0.06
189
+ player.cape.rotation.x = Math.sin(t / 1.5) * 0.06 + basicCapeRotationX
190
+ }
191
+
192
+ if (reset) {
193
+ const cb = this.switchAnimationCallback
194
+ this.switchAnimationCallback = null
195
+ cb?.()
196
+ }
285
197
  }
286
198
  }
287
199
 
@@ -289,11 +201,6 @@ const HitAnimation = {
289
201
  animate(progress, player, isMovingOrRunning) {
290
202
  if (!player?.skin?.rightArm?.rotation) return
291
203
 
292
- // One swing = one arc. `swing` rises to its peak at the middle of the swing
293
- // (progress 0.5) and returns to rest at both ends, matching vanilla's
294
- // `sin(swingProgress * PI)` and the first-person hand. Driving the trig with
295
- // `progress * 18` previously ran ~3 sine cycles per click, which made other
296
- // players' arms look like they were swinging several times per hit.
297
204
  const swing = Math.sin(progress * Math.PI)
298
205
  player.skin.rightArm.rotation.x = -0.4537860552 * 2 - 2 * swing * 0.3
299
206
 
@@ -354,4 +261,4 @@ function applyCrouchPose(player, crouchBlend) {
354
261
 
355
262
  skin.rightLeg.position.z += -3.4500310377 * s
356
263
  skin.leftLeg.position.z += -3.4500310377 * s
357
- }
264
+ }