@adatechnology/game-characters-three 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -1017,6 +1017,9 @@ declare const registerBodyMaterial: (world: {
1017
1017
  readonly materials: Map<string, unknown>;
1018
1018
  }) => void;
1019
1019
 
1020
+ /** The person builder's spec for a profession of the people pack (look and gear as numbers; the builder draws the rest). */
1021
+ declare const personSpecOf: (profession: ProfessionCard) => Record<string, any>;
1022
+
1020
1023
  /** The voice limiter group of the undead (`undead_voice`: five at once, one every 0.25 s, nearer than 40 m). */
1021
1024
  declare const UNDEAD_VOICE_GROUP = "undead_voice";
1022
1025
  /** The sounds of the game and of the art scene for the bodies, in one bank. */
@@ -1041,6 +1044,11 @@ type PlayerAvatarOptions = {
1041
1044
  type AvatarEvent = PoseClipEvent & {
1042
1045
  clip: string;
1043
1046
  };
1047
+ type AvatarItemUsePose = {
1048
+ readonly id: string;
1049
+ readonly position: readonly [number, number, number];
1050
+ readonly rotation: readonly [number, number, number];
1051
+ };
1044
1052
  type AvatarUpdate = {
1045
1053
  /** Ground speed, m/s. Ties cadence and swing to the ground (the game's `walkCad` and `walkGait`). */
1046
1054
  speed?: number;
@@ -1054,6 +1062,10 @@ type AvatarUpdate = {
1054
1062
  pitch?: number;
1055
1063
  /** Leg yaw toward the step while armed (the game's `P.hip`, radians). */
1056
1064
  hip?: number;
1065
+ /** Whether the player is carrying an item in the right hand. */
1066
+ holding?: boolean;
1067
+ /** Active scripted item use (`drink`, `sip`, `chug`, `pour`, `open`, `close`, `tilt`). */
1068
+ itemUse?: AvatarItemUsePose | null;
1057
1069
  };
1058
1070
  type PlayPoseOptions = {
1059
1071
  onEvent?: (event: AvatarEvent) => void;
@@ -1169,6 +1181,8 @@ type FirstPersonArmsOptions = {
1169
1181
  clips?: readonly FirstPersonClip[];
1170
1182
  /** Seconds to move from one stance (or from a held tool to the rest) to the next. Default 0.18. */
1171
1183
  blend?: number;
1184
+ /** In the `relaxed` stance with no clip playing (walking, standing, nothing in the hands) the hands drop out of the view and are not drawn; a strike, a shove, a held tool or `fists` bring them back. Default false. */
1185
+ hideWhenIdle?: boolean;
1172
1186
  bob?: Partial<FirstPersonBob>;
1173
1187
  };
1174
1188
  type FirstPersonArmsState = {
@@ -1229,6 +1243,8 @@ type RoomAvatarLike = {
1229
1243
  down: boolean;
1230
1244
  armed: boolean;
1231
1245
  pitch: number;
1246
+ holding?: boolean;
1247
+ itemUse?: AvatarItemUsePose | null;
1232
1248
  }): void;
1233
1249
  hand(side: 'R' | 'L'): THREE_NS.Object3D;
1234
1250
  /** The arms of the first-person view (`RoomAvatar.firstPerson`); absent when `toRoomAvatar` was told to make none. */
@@ -1341,4 +1357,4 @@ type TwoBone = {
1341
1357
  */
1342
1358
  declare const solveTwoBone: (root: THREE_NS.Vector3, target: THREE_NS.Vector3, upper: number, lower: number, pole: THREE_NS.Vector3, out: TwoBone) => TwoBone;
1343
1359
 
1344
- export { AVATAR_AREA_ID, type AreaContext, type AvatarEvent, type AvatarTestOptions, type AvatarUpdate, BAKE_NEUTRAL_POSE, BODY_CHUNK, BODY_MATERIAL_DEF, BODY_SOUNDS, type BakeBuilders, type BakeSpec, type BakedBody, type BestBuilder, type BestBuilderOptions, type BestTypeCard, type BloodAge, type BloodLook, type BloodLookOptions, type BloodSink, type BodyBatch, type BodyBatchOptions, type BodyChunkOptions, type BodyInstance, type BodyJoints, type BodyMaterialParams, type BodySoundRule, type BodyVariant, type BuildInstancedBodyParams, CHARACTERS_AREA_ID, CHARACTER_SOUNDS, CHARACTER_SOUND_BANK, type CharacterSound, type CharacterSoundOptions, type CharacterSoundSink, type CharacterSoundStyle, type CharactersTestOptions, EXTRAS, type EyeOptions, type Eyes, FACE_FEATURES, FIRST_PERSON_BOB, FIRST_PERSON_COLORS, FIRST_PERSON_RIG, FIRST_PERSON_STANCES, type FilmSink, type FirstPersonArms, type FirstPersonArmsOptions, type FirstPersonArmsState, type FirstPersonBob, type FirstPersonColors, type FirstPersonEvent, type FirstPersonRig, type FluidsBridge, type FluidsBridgeOptions, type FlyingLimb, type FlyingLimbs, type FlyingLimbsOptions, type FlyingPiece, type FlyingPieces, type FlyingPiecesOptions, HAND_ITEMS, HATS, HIT_PRESETS, type HitPreset, LOD_BAKE, LOD_BATCH_CAPACITY, LOD_CASTS_SHADOW, LOD_DISTANCES, LOD_SEGMENTS, type LimbEffects, type LimbLaunch, PENDULUM, PERSON_ITEM_PREFIX, PLAYER_SOUNDS, type Pendulum, type PersonBuilder, type PersonBuilderOptions, type PlayPoseOptions, type PlayerAvatar, type PlayerAvatarColors, type PlayerAvatarOptions, type PlayerAvatarSpec, type PoseTargets, type ProfessionCard, type RestPose, type RichRig, type RigAnimator, type RigAnimatorOptions, type RoomAvatarLike, type StanceDef, type Three, type ToRoomAvatarOptions, type TwoBone, UNDEAD_VOICES, UNDEAD_VOICE_GROUP, UNDEAD_VOICE_SOURCES, type UndeadCard, type UndeadLike, type UndeadVoice, type VariantType, type VoiceMoment, type VoiceStep, type WoundBody, type WoundMaterials, type WoundNode, type WoundSpray, type Wounds, type WoundsOptions, ZOMBIE_ITEM_PREFIX, bakeBody, bakeVariants, blobGeometry, blobRadii, bloodLookOfAge, buildInstancedBody, createAvatarTestModule, createBestBuilder, createBodyBatch, createBodyChunk, createBodyMaterial, createCharacterSound, createCharactersTestModule, createEye, createFirstPersonArms, createFluidsBridge, createFlyingLimbs, createFlyingPieces, createGlowTexture, createPendulum, createPersonBuilder, createPlayerAvatar, createRigAnimator, createVariantBatches, createWoundMaterials, createWounds, exitWound, explodeChunks, findEyes, gouge, jointsOf, lodAt, makeCut, makeStump, registerBodyMaterial, ringWound, solveTwoBone, starGeometry, stepPendulum, toRoomAvatar, woundBodyOf, woundDecal };
1360
+ export { AVATAR_AREA_ID, type AreaContext, type AvatarEvent, type AvatarItemUsePose, type AvatarTestOptions, type AvatarUpdate, BAKE_NEUTRAL_POSE, BODY_CHUNK, BODY_MATERIAL_DEF, BODY_SOUNDS, type BakeBuilders, type BakeSpec, type BakedBody, type BestBuilder, type BestBuilderOptions, type BestTypeCard, type BloodAge, type BloodLook, type BloodLookOptions, type BloodSink, type BodyBatch, type BodyBatchOptions, type BodyChunkOptions, type BodyInstance, type BodyJoints, type BodyMaterialParams, type BodySoundRule, type BodyVariant, type BuildInstancedBodyParams, CHARACTERS_AREA_ID, CHARACTER_SOUNDS, CHARACTER_SOUND_BANK, type CharacterSound, type CharacterSoundOptions, type CharacterSoundSink, type CharacterSoundStyle, type CharactersTestOptions, EXTRAS, type EyeOptions, type Eyes, FACE_FEATURES, FIRST_PERSON_BOB, FIRST_PERSON_COLORS, FIRST_PERSON_RIG, FIRST_PERSON_STANCES, type FilmSink, type FirstPersonArms, type FirstPersonArmsOptions, type FirstPersonArmsState, type FirstPersonBob, type FirstPersonColors, type FirstPersonEvent, type FirstPersonRig, type FluidsBridge, type FluidsBridgeOptions, type FlyingLimb, type FlyingLimbs, type FlyingLimbsOptions, type FlyingPiece, type FlyingPieces, type FlyingPiecesOptions, HAND_ITEMS, HATS, HIT_PRESETS, type HitPreset, LOD_BAKE, LOD_BATCH_CAPACITY, LOD_CASTS_SHADOW, LOD_DISTANCES, LOD_SEGMENTS, type LimbEffects, type LimbLaunch, PENDULUM, PERSON_ITEM_PREFIX, PLAYER_SOUNDS, type Pendulum, type PersonBuilder, type PersonBuilderOptions, type PlayPoseOptions, type PlayerAvatar, type PlayerAvatarColors, type PlayerAvatarOptions, type PlayerAvatarSpec, type PoseTargets, type ProfessionCard, type RestPose, type RichRig, type RigAnimator, type RigAnimatorOptions, type RoomAvatarLike, type StanceDef, type Three, type ToRoomAvatarOptions, type TwoBone, UNDEAD_VOICES, UNDEAD_VOICE_GROUP, UNDEAD_VOICE_SOURCES, type UndeadCard, type UndeadLike, type UndeadVoice, type VariantType, type VoiceMoment, type VoiceStep, type WoundBody, type WoundMaterials, type WoundNode, type WoundSpray, type Wounds, type WoundsOptions, ZOMBIE_ITEM_PREFIX, bakeBody, bakeVariants, blobGeometry, blobRadii, bloodLookOfAge, buildInstancedBody, createAvatarTestModule, createBestBuilder, createBodyBatch, createBodyChunk, createBodyMaterial, createCharacterSound, createCharactersTestModule, createEye, createFirstPersonArms, createFluidsBridge, createFlyingLimbs, createFlyingPieces, createGlowTexture, createPendulum, createPersonBuilder, createPlayerAvatar, createRigAnimator, createVariantBatches, createWoundMaterials, createWounds, exitWound, explodeChunks, findEyes, gouge, jointsOf, lodAt, makeCut, makeStump, personSpecOf, registerBodyMaterial, ringWound, solveTwoBone, starGeometry, stepPendulum, toRoomAvatar, woundBodyOf, woundDecal };
@@ -84,6 +84,7 @@ var GameCharactersThree = (() => {
84
84
  lodAt: () => lodAt,
85
85
  makeCut: () => makeCut,
86
86
  makeStump: () => makeStump,
87
+ personSpecOf: () => personSpecOf,
87
88
  registerBodyMaterial: () => registerBodyMaterial,
88
89
  ringWound: () => ringWound,
89
90
  solveTwoBone: () => solveTwoBone,
@@ -1716,6 +1717,13 @@ var GameCharactersThree = (() => {
1716
1717
  legRBroken: 256,
1717
1718
  jaw: 512
1718
1719
  };
1720
+ var KIT_ORIGIN_KEY = "kitOrigin";
1721
+ function markKitOrigin(object2, origin) {
1722
+ const node = object2;
1723
+ node.userData = node.userData ?? {};
1724
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
1725
+ return object2;
1726
+ }
1719
1727
 
1720
1728
  // ../game-characters/dist/index.js
1721
1729
  var __defProp2 = Object.defineProperty;
@@ -5430,7 +5438,8 @@ var GameCharactersThree = (() => {
5430
5438
  root.userData.rig = rig;
5431
5439
  return root;
5432
5440
  }
5433
- return { build, TYPES, setLOD(k) {
5441
+ const marked = (...args) => markKitOrigin(build(...args), { owner: "game-characters-three", kind: "person" });
5442
+ return { build: marked, TYPES, setLOD(k) {
5434
5443
  LK = k;
5435
5444
  SEG = Math.max(5, Math.round(16 * k));
5436
5445
  }, topM: () => topM_ref };
@@ -6656,7 +6665,11 @@ var GameCharactersThree = (() => {
6656
6665
  chars.forEach((c) => pose(c, c.z, 0, 0));
6657
6666
  return { root, chars };
6658
6667
  }
6659
- return { build, pose, zombify, buildPerson };
6668
+ const mark = (c) => {
6669
+ markKitOrigin(c.root, { owner: "game-characters-three", kind: "person" });
6670
+ return c;
6671
+ };
6672
+ return { build: (sp, idx) => mark(build(sp, idx)), pose, zombify, buildPerson: (p2, zed) => mark(buildPerson(p2, zed)) };
6660
6673
  };
6661
6674
 
6662
6675
  // src/eyes.ts
@@ -8608,7 +8621,17 @@ var GameCharactersThree = (() => {
8608
8621
  fem: false,
8609
8622
  life: 1,
8610
8623
  hs: "short",
8611
- hair: "2a1e16"
8624
+ hair: "2a1e16",
8625
+ pose: {
8626
+ armL: 0,
8627
+ armR: 0,
8628
+ elbL: -0.12,
8629
+ elbR: -0.12,
8630
+ armOut: 0.11,
8631
+ lean: 0.04,
8632
+ headX: 0,
8633
+ headZ: 0
8634
+ }
8612
8635
  },
8613
8636
  shirtColor: "3d5a73"
8614
8637
  };
@@ -8619,9 +8642,12 @@ var GameCharactersThree = (() => {
8619
8642
  var CLIP_BLEND = 0.12;
8620
8643
  var FALL_SECONDS2 = 0.3;
8621
8644
  var HIP_HEIGHT = 0.95;
8622
- var WALK_ARM = 0.45;
8645
+ var WALK_ARM = 0.28;
8623
8646
  var WALK_BOB = 0.035;
8624
8647
  var WALK_LEAN = 0.04;
8648
+ var RUN_LEAN = 0.16;
8649
+ var RUN_BOB = 0.05;
8650
+ var REST_ARM_Z = 0.18;
8625
8651
  var CROUCH = { thigh: -0.95, thighWalk: 0.3, shin: 1.9, lean: 0.25, drop: -0.35 };
8626
8652
  var AIMED = { shoulder: -1.42, perAim: 0.85, swing: 0.02, zRight: -0.54, zLeft: 0.36, elbowRight: -0.04, elbowLeft: -0.12 };
8627
8653
  var DOWN = { thighWalk: 0.5, shinSwing: 0.85, shinBend: 1.3, armHigh: -2.8, armLow: -0.35, armWave: 0.3, armZ: 0.5, armZWave: 0.3, tilt: 1.5, roll: 0.3, hipY: 0.16, hipZ: 0.6, hipZFlip: 1.2 };
@@ -8630,7 +8656,18 @@ var GameCharactersThree = (() => {
8630
8656
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
8631
8657
  var fract = (value) => value - Math.floor(value);
8632
8658
  var createPlayerAvatar = (THREE, options = {}) => {
8633
- const spec = { ...player_avatar_default.spec, ...options.spec };
8659
+ const pose = {
8660
+ armL: 0,
8661
+ armR: 0,
8662
+ elbL: -0.08,
8663
+ elbR: -0.08,
8664
+ lean: 0.04,
8665
+ headX: 0,
8666
+ headZ: 0,
8667
+ ...player_avatar_default.spec.pose,
8668
+ ...options.spec?.pose
8669
+ };
8670
+ const spec = { ...player_avatar_default.spec, ...options.spec, pose };
8634
8671
  for (const key of ["skin", "hair"]) if (typeof spec[key] === "string") spec[key] = hexOf(spec[key]);
8635
8672
  const best = createBestBuilder(THREE, { types: [{ name: String(spec.type), eye: 0 }], random: createRng(Number(spec.seed ?? 1)) });
8636
8673
  const body = best.build(spec);
@@ -8665,6 +8702,41 @@ var GameCharactersThree = (() => {
8665
8702
  sockets[side].position.set(HAND_OFFSET.x, HAND_OFFSET.y, HAND_OFFSET.z);
8666
8703
  armOf[side].el.add(sockets[side]);
8667
8704
  }
8705
+ const addMesh = (parent, geometry, material, x = 0, y = 0, z = 0, rx = 0, ry = 0, rz = 0) => {
8706
+ const mesh2 = new THREE.Mesh(geometry, material);
8707
+ mesh2.position.set(x, y, z);
8708
+ if (rx || ry || rz) mesh2.rotation.set(rx, ry, rz);
8709
+ mesh2.castShadow = true;
8710
+ mesh2.receiveShadow = true;
8711
+ parent.add(mesh2);
8712
+ return mesh2;
8713
+ };
8714
+ const hands = [];
8715
+ rig.arms.forEach((arm, index) => {
8716
+ const el = arm.el;
8717
+ const side = index === 0 ? 1 : -1;
8718
+ const oldHand = meshesOf(el).find((mesh2) => Math.abs(mesh2.position.y + 0.33) < 0.025);
8719
+ const handMaterial = oldHand?.material ?? new THREE.MeshLambertMaterial({ color: colors.skin });
8720
+ if (oldHand) {
8721
+ oldHand.geometry?.dispose();
8722
+ el.remove(oldHand);
8723
+ }
8724
+ const hg = new THREE.Group();
8725
+ hg.name = side > 0 ? "hand_group_L" : "hand_group_R";
8726
+ hg.position.set(side * 0.02, -0.3, 0.01);
8727
+ el.add(hg);
8728
+ const palm = addMesh(hg, new THREE.SphereGeometry(0.048, 14, 10), handMaterial, 0, -0.035, 0);
8729
+ palm.scale.set(0.6, 1.05, 1);
8730
+ const built = buildHand({ THREE, add: addMesh, hg, handM: handMaterial, s: side, detailed: true });
8731
+ hands.push({ side, hg, fingers: built.fingers, th: built.th });
8732
+ });
8733
+ const setHandGrip = (hand, grip, wristX = 0) => {
8734
+ hand.fingers.forEach((finger, j) => {
8735
+ finger.rotation.z = -hand.side * grip * (j % 2 ? 1.1 : 0.9);
8736
+ });
8737
+ if (hand.th) hand.th.rotation.z = -hand.side * (0.32 + grip * 0.25);
8738
+ hand.hg.rotation.x = wristX;
8739
+ };
8668
8740
  const legRestX = rig.legs.map((leg) => leg.g.position.x);
8669
8741
  const legSide = (index) => index === 0 ? 1 : -1;
8670
8742
  let cycles = 0;
@@ -8706,6 +8778,42 @@ var GameCharactersThree = (() => {
8706
8778
  });
8707
8779
  return { armR: shoulder, armL: shoulder, armRz: AIMED.zRight, armLz: AIMED.zLeft, elbR: AIMED.elbowRight, elbL: AIMED.elbowLeft };
8708
8780
  };
8781
+ const holdingPose = (gait, itemUse) => {
8782
+ const bob = Math.sin(cycles * TWO_PI2) * 0.05 * gait;
8783
+ if (!itemUse) {
8784
+ return { armL: -0.45 + bob, armLz: 0.22, elbL: -1.05 };
8785
+ }
8786
+ const { id, position, rotation } = itemUse;
8787
+ if (id === "drink" || id === "sip" || id === "chug") {
8788
+ const raise2 = clamp((position[1] + 0.2) / 0.22, 0, 1);
8789
+ const tilt3 = clamp(Math.abs(rotation[0]) / 1.22, 0, 1.15);
8790
+ return {
8791
+ armL: -0.45 - 0.95 * raise2 - 0.22 * tilt3,
8792
+ armLz: 0.22 + 0.44 * raise2,
8793
+ elbL: -1.05 - 0.85 * raise2 + 0.12 * tilt3,
8794
+ headX: -0.26 * tilt3
8795
+ };
8796
+ }
8797
+ if (id === "open" || id === "close") {
8798
+ const reach = clamp((0.22 - position[0]) / 0.12, 0, 1);
8799
+ return {
8800
+ armL: -0.45 - 0.35 * reach,
8801
+ armLz: 0.22 + 0.28 * reach,
8802
+ elbL: -1.05 - 0.25 * reach,
8803
+ armR: -0.78 * reach,
8804
+ armRz: -0.48 * reach,
8805
+ elbR: -1.25 * reach
8806
+ };
8807
+ }
8808
+ const raise = clamp((position[1] + 0.2) / 0.25, 0, 1);
8809
+ const tilt2 = clamp(Math.abs(rotation[0]) / 1.75, 0, 1.2);
8810
+ const ext = Math.max(raise, tilt2 * 0.75);
8811
+ return {
8812
+ armL: -0.45 - 0.48 * ext,
8813
+ armLz: 0.22 + 0.08 * ext,
8814
+ elbL: -1.05 + 0.38 * ext
8815
+ };
8816
+ };
8709
8817
  const resetLegYaw = () => {
8710
8818
  rig.legs.forEach((leg, index) => {
8711
8819
  leg.g.rotation.y = 0;
@@ -8713,6 +8821,32 @@ var GameCharactersThree = (() => {
8713
8821
  leg.g.position.z = 0;
8714
8822
  });
8715
8823
  };
8824
+ const runningPose = (gait, busyHands = false) => {
8825
+ const ph = cycles * TWO_PI2;
8826
+ const sine = Math.sin(ph);
8827
+ const cosine = Math.cos(ph);
8828
+ const legL = sine * 0.65 * gait;
8829
+ const legR = -sine * 0.65 * gait;
8830
+ const kneeL = Math.max(0, -cosine) * 1.15 * gait;
8831
+ const kneeR = Math.max(0, cosine) * 1.15 * gait;
8832
+ const targets = {
8833
+ lean: RUN_LEAN * Math.min(1, gait),
8834
+ drop: Math.abs(cosine) * RUN_BOB * gait,
8835
+ legL,
8836
+ legR,
8837
+ kneeL,
8838
+ kneeR
8839
+ };
8840
+ if (!busyHands) {
8841
+ targets.armR = -0.32 - sine * 0.65 * gait;
8842
+ targets.armL = -0.32 + sine * 0.65 * gait;
8843
+ targets.elbR = -1.25 + Math.max(0, sine) * 0.3 * gait;
8844
+ targets.elbL = -1.25 + Math.max(0, -sine) * 0.3 * gait;
8845
+ targets.armRz = 0.22;
8846
+ targets.armLz = -0.22;
8847
+ }
8848
+ return targets;
8849
+ };
8716
8850
  const crouchPose = (gait) => {
8717
8851
  const swing = (leg) => {
8718
8852
  const legS = legSide(leg);
@@ -8808,23 +8942,44 @@ var GameCharactersThree = (() => {
8808
8942
  update(dt, state = {}) {
8809
8943
  const speed = state.speed ?? 0;
8810
8944
  const moving = speed >= 0.05;
8811
- if (moving) cycles += dt * walkCadence(speed);
8945
+ const down = !!state.down;
8946
+ const running = Boolean(state.running && moving && !down && !state.crouch);
8947
+ if (moving) cycles += dt * (running ? walkCadence(speed) * 1.3 : walkCadence(speed));
8812
8948
  const gait = Math.min(MAX_GAIT, walkGait(speed));
8813
- profile.arm = WALK_ARM * gait;
8814
- profile.bob = WALK_BOB * gait;
8815
- time = cycles / walkCadence(Math.max(speed, 0));
8949
+ profile.arm = (running ? 0.55 : WALK_ARM) * gait;
8950
+ profile.bob = (running ? RUN_BOB : WALK_BOB) * gait;
8951
+ time = cycles / (walkCadence(Math.max(speed, 0)) * (running ? 1.3 : 1));
8816
8952
  animator.animate(body, time, { speed });
8817
- const down = !!state.down;
8818
8953
  fall = fallSeconds <= 0 ? down ? 1 : 0 : clamp(fall + (down ? dt : -dt) / fallSeconds, 0, 1);
8819
8954
  resetLegYaw();
8820
8955
  if (down) animator.setPose(rig, downPose());
8821
8956
  else {
8957
+ if (running) animator.setPose(rig, runningPose(gait, Boolean(state.armed || state.holding || state.itemUse)));
8822
8958
  if (state.crouch) animator.setPose(rig, crouchPose(gait));
8823
8959
  if (state.armed) animator.setPose(rig, armed(state.pitch ?? 0, gait, state.hip ?? 0));
8960
+ else if (state.holding || state.itemUse) animator.setPose(rig, holdingPose(gait, state.itemUse));
8961
+ else if (!running) {
8962
+ animator.setPose(rig, {
8963
+ armRz: REST_ARM_Z,
8964
+ armLz: -REST_ARM_Z,
8965
+ elbR: -0.12,
8966
+ elbL: -0.12
8967
+ });
8968
+ }
8824
8969
  }
8825
8970
  placeBody(fall);
8826
8971
  stepClip(dt, down);
8827
8972
  if (smooth) animator.cascade(rig, dt);
8973
+ const clipId = run ? run.clip.id : null;
8974
+ const ph = cycles * TWO_PI2;
8975
+ hands.forEach((hand) => {
8976
+ const isRight = hand.side < 0;
8977
+ const holdRight = isRight && (!!state.holding || !!state.itemUse);
8978
+ const holdLeft = !isRight && (state.itemUse?.id === "open" || state.itemUse?.id === "close");
8979
+ const grip = clipId === "punch_right" ? 1.4 : clipId === "shove_two_hands" ? 0.15 : state.armed || holdRight || holdLeft ? 1.22 : running ? 0.85 : 0.12;
8980
+ const wristSwing = holdRight || state.armed || running ? 0 : -Math.min(1, gait) * Math.sin(ph - 0.6) * 0.12 * hand.side;
8981
+ setHandGrip(hand, grip, wristSwing);
8982
+ });
8828
8983
  },
8829
8984
  play(clip, playOptions = {}) {
8830
8985
  const found = typeof clip === "string" ? clips.get(clip) : clip;
@@ -9152,7 +9307,7 @@ var GameCharactersThree = (() => {
9152
9307
  },
9153
9308
  stances: {
9154
9309
  relaxed: {
9155
- R: { p: [0.2, -0.2, -0.36], r: [0.2, 1.45, -0.4], grip: 0.35 },
9310
+ R: { p: [0.2, -0.22, -0.36], r: [0.05, 1.57, -0.05], grip: 0.2 },
9156
9311
  L: "mirror"
9157
9312
  },
9158
9313
  fists: {
@@ -9296,6 +9451,10 @@ var GameCharactersThree = (() => {
9296
9451
  var CONTACT_PALM = 0.03;
9297
9452
  var FOOT_LENGTH = 0.24;
9298
9453
  var FOOT_SHOWN = 1e-3;
9454
+ var AWAY_DROP = 0.55;
9455
+ var AWAY_OUT = 7;
9456
+ var AWAY_IN = 20;
9457
+ var AWAY_HIDDEN = 0.97;
9299
9458
  var FILL = 0.3;
9300
9459
  var FIRST_PERSON_STANCES = first_person_arms_default.stances;
9301
9460
  var FIRST_PERSON_RIG = first_person_arms_default.rig;
@@ -9354,7 +9513,7 @@ var GameCharactersThree = (() => {
9354
9513
  const bob = { ...FIRST_PERSON_BOB, ...options.bob };
9355
9514
  const blendSeconds = options.blend ?? DEFAULT_BLEND;
9356
9515
  const clips = new Map((options.clips ?? []).map((clip) => [clip.id, clip]));
9357
- const root = new THREE.Group();
9516
+ const root = markKitOrigin(new THREE.Group(), { owner: "game-characters-three", kind: "person", id: "first-person-arms" });
9358
9517
  root.name = "first-person-arms";
9359
9518
  root.userData.viewmodel = true;
9360
9519
  const lit = (color2) => new THREE.MeshLambertMaterial({ color: color2, emissive: scaleHex(color2, FILL) });
@@ -9409,7 +9568,7 @@ var GameCharactersThree = (() => {
9409
9568
  const cuff = limb2(root, rig.radius.cuff, rig.radius.cuff, 0.03, materials.cuff);
9410
9569
  const elbow = ball(root, rig.radius.elbow * 1.02, materials.sleeve);
9411
9570
  elbow.name = `first-person-elbow-${side}`;
9412
- arms[side] = { side, sign: sign2, hand, anchor, upper, fore, cuff, elbow, shown: newPose(THREE), from: newPose(THREE), mode: null, blend: 1, drawn: newPose(THREE), joints: { shoulder: new THREE.Vector3(), elbow: new THREE.Vector3(), wrist: new THREE.Vector3() } };
9571
+ arms[side] = { side, sign: sign2, hand, anchor, upper, fore, cuff, elbow, shown: newPose(THREE), from: newPose(THREE), mode: null, blend: 1, drawn: newPose(THREE), joints: { shoulder: new THREE.Vector3(), elbow: new THREE.Vector3(), wrist: new THREE.Vector3() }, away: options.hideWhenIdle ? 1 : 0 };
9413
9572
  }
9414
9573
  const leg = new THREE.Group();
9415
9574
  leg.name = "first-person-leg";
@@ -9503,6 +9662,7 @@ var GameCharactersThree = (() => {
9503
9662
  const desired = newPose(THREE);
9504
9663
  const channelPose = newPose(THREE);
9505
9664
  const mixed = newPose(THREE);
9665
+ const dropped = newPose(THREE);
9506
9666
  let indexCurl;
9507
9667
  const bone = createTwoBone(THREE);
9508
9668
  const shoulder = new THREE.Vector3();
@@ -9627,11 +9787,22 @@ var GameCharactersThree = (() => {
9627
9787
  if (arm.blend >= 1) copyPose(arm.shown, desired);
9628
9788
  else mixPose(THREE, arm.shown, arm.from, desired, ease2(arm.blend));
9629
9789
  const channel = sample?.[side];
9790
+ let pose;
9630
9791
  if (channel && channel.w > 0) {
9631
9792
  fromChannel(channel, side, arm.shown, channelPose);
9632
9793
  mixPose(THREE, mixed, arm.shown, channelPose, channel.w);
9633
- drawArm(arm, mode === "target" ? mixed : keepOutside(mixed, handReach(mixed)));
9634
- } else drawArm(arm, mode === "target" ? arm.shown : keepOutside(arm.shown, handReach(arm.shown)));
9794
+ pose = mode === "target" ? mixed : keepOutside(mixed, handReach(mixed));
9795
+ } else pose = mode === "target" ? arm.shown : keepOutside(arm.shown, handReach(arm.shown));
9796
+ const idle = options.hideWhenIdle === true && mode === "rest:relaxed" && !(channel && channel.w > 0);
9797
+ arm.away = options.hideWhenIdle ? Math.min(1, Math.max(0, arm.away + ((idle ? 1 : 0) - arm.away) * Math.min(1, dt * (idle ? AWAY_OUT : AWAY_IN)))) : 0;
9798
+ if (arm.away > 0) {
9799
+ copyPose(dropped, pose);
9800
+ dropped.p.y -= AWAY_DROP * ease2(arm.away);
9801
+ pose = dropped;
9802
+ }
9803
+ drawArm(arm, pose);
9804
+ const shown = arm.away < AWAY_HIDDEN;
9805
+ for (const part of [arm.anchor, arm.upper, arm.fore, arm.cuff, arm.elbow]) part.visible = shown;
9635
9806
  }
9636
9807
  const foot = sample?.F;
9637
9808
  leg.visible = !!foot && foot.w > FOOT_SHOWN;
@@ -9678,6 +9849,7 @@ var GameCharactersThree = (() => {
9678
9849
  const arms = options.firstPerson === false ? null : createFirstPersonArms(avatar.three, {
9679
9850
  colors: { skin: avatar.colors.skin, sleeve: avatar.colors.longSleeves ? avatar.colors.shirt : avatar.colors.skin, trouser: avatar.colors.trousers, shoe: avatar.colors.shoes, ...options.firstPerson?.colors },
9680
9851
  clips: options.firstPersonClips ?? FIRST_PERSON_CLIPS,
9852
+ hideWhenIdle: true,
9681
9853
  ...options.firstPerson
9682
9854
  });
9683
9855
  const strikes = new Map((options.strikeClips ?? ATTACK_CLIPS).map((clip) => [clip.id, clip]));
@@ -9692,7 +9864,16 @@ var GameCharactersThree = (() => {
9692
9864
  avatar,
9693
9865
  arms,
9694
9866
  ...arms ? { firstPerson: { root: arms.root, update: (dt, state) => arms.update(dt, state) } } : {},
9695
- update: (dt, state) => avatar.update(dt, { speed: state.speed, running: state.running, crouch: state.crouch, down: state.down, armed: state.armed, pitch: -state.pitch }),
9867
+ update: (dt, state) => avatar.update(dt, {
9868
+ speed: state.speed,
9869
+ running: state.running,
9870
+ crouch: state.crouch,
9871
+ down: state.down,
9872
+ armed: state.armed,
9873
+ pitch: -state.pitch,
9874
+ ...state.holding !== void 0 ? { holding: state.holding } : {},
9875
+ ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {}
9876
+ }),
9696
9877
  hand: (side) => avatar.hand(side),
9697
9878
  strike: (clipId, strikeOptions) => {
9698
9879
  const clip = strikes.get(clipId);