@adatechnology/game-characters-three 0.7.0 → 0.7.2

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.js CHANGED
@@ -2675,6 +2675,7 @@ var ringWound = (three, materials, parent, radius, y = 0) => {
2675
2675
  };
2676
2676
 
2677
2677
  // src/wounds/flying.ts
2678
+ import { discardObject3D } from "@adatechnology/game-shapes-three";
2678
2679
  var GRAVITY2 = 9.8;
2679
2680
  var FLOOR_OFFSET = 0.08;
2680
2681
  var BOUNCE_LIMIT = 1.2;
@@ -2687,7 +2688,10 @@ var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40
2687
2688
  const quaternion = new three.Quaternion();
2688
2689
  const euler = new three.Euler();
2689
2690
  const trim = () => {
2690
- while (pieces.length > max) pieces.shift()?.object.removeFromParent();
2691
+ while (pieces.length > max) {
2692
+ const piece = pieces.shift();
2693
+ if (piece) discardObject3D(piece.object);
2694
+ }
2691
2695
  };
2692
2696
  const add = (object, velocity, spin, tag) => {
2693
2697
  const piece = { object, velocity, spin, rest: false, ...tag ? { tag } : {} };
@@ -2736,7 +2740,7 @@ var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40
2736
2740
  }
2737
2741
  },
2738
2742
  clear() {
2739
- pieces.splice(0).forEach((piece) => piece.object.removeFromParent());
2743
+ discardObject3D(pieces.splice(0).map((piece) => piece.object));
2740
2744
  }
2741
2745
  };
2742
2746
  };
@@ -4154,6 +4158,8 @@ function createCharactersTestModule(options = {}) {
4154
4158
  time += dt;
4155
4159
  for (const [id, entry] of entries) {
4156
4160
  if (!entry.root.parent) {
4161
+ if (entry.body) wounds?.untrack(entry.body);
4162
+ if (selected === entry) selected = null;
4157
4163
  entries.delete(id);
4158
4164
  continue;
4159
4165
  }
@@ -5007,8 +5013,236 @@ var createPlayerAvatar = (THREE, options = {}) => {
5007
5013
  };
5008
5014
 
5009
5015
  // src/player/room-avatar.ts
5010
- import { ATTACK_CLIPS, FIRST_PERSON_CLIPS } from "@adatechnology/game-characters-people";
5016
+ import { ATTACK_CLIPS } from "@adatechnology/game-characters-people";
5011
5017
  import { mirrorPoseClip } from "@adatechnology/game-characters";
5018
+ var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.t ?? null;
5019
+ var isPunch = (clip) => clip.events?.some((event) => event.type === "impact" && event.data?.kind === "punch") ?? false;
5020
+ var GUARD_SPEED = 0.3;
5021
+ var toRoomAvatar = (avatar, options = {}) => {
5022
+ const strikes = new Map((options.strikeClips ?? ATTACK_CLIPS).map((clip) => [clip.id, clip]));
5023
+ const mirrored = /* @__PURE__ */ new Map();
5024
+ const leftOf = (clip) => {
5025
+ let found = mirrored.get(clip.id);
5026
+ if (!found) mirrored.set(clip.id, found = mirrorPoseClip(clip, `${clip.id}_left`));
5027
+ return found;
5028
+ };
5029
+ const guard = options.guardClip === false ? void 0 : strikes.get(options.guardClip ?? "fight_guard");
5030
+ const guardHold = options.guardHold ?? 1.2;
5031
+ const punchBlend = options.punchBlend ?? 0.05;
5032
+ let strikeLeft = 0;
5033
+ let guardLeft = 0;
5034
+ let guarding = false;
5035
+ const stepGuard = (dt, state) => {
5036
+ if (!guard) return;
5037
+ const free = state.speed < GUARD_SPEED && !state.down && !state.armed && !state.holding && !state.push && !state.door && !state.itemUse && !state.running;
5038
+ if (strikeLeft > 0) {
5039
+ strikeLeft -= dt;
5040
+ if (strikeLeft <= 0 && guardLeft > 0 && free) {
5041
+ avatar.play(guard);
5042
+ guarding = true;
5043
+ }
5044
+ return;
5045
+ }
5046
+ guardLeft = Math.max(0, guardLeft - dt);
5047
+ if (guarding && (guardLeft <= 0 || !free || avatar.playing !== guard.id)) {
5048
+ if (avatar.playing === guard.id) avatar.stop();
5049
+ guarding = false;
5050
+ }
5051
+ };
5052
+ return {
5053
+ root: avatar.root,
5054
+ avatar,
5055
+ eye: (out) => avatar.eye(out),
5056
+ setView: (mode, camera) => avatar.setView(mode, camera),
5057
+ update: (dt, state) => {
5058
+ stepGuard(dt, state);
5059
+ avatar.update(dt, {
5060
+ speed: state.speed,
5061
+ running: state.running,
5062
+ crouch: state.crouch,
5063
+ down: state.down,
5064
+ armed: state.armed,
5065
+ pitch: -state.pitch,
5066
+ ...state.holding !== void 0 ? { holding: state.holding } : {},
5067
+ ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {},
5068
+ ...state.push !== void 0 ? { push: state.push } : {},
5069
+ ...state.seat !== void 0 ? { seat: state.seat } : {},
5070
+ ...state.door !== void 0 ? { door: state.door } : {}
5071
+ });
5072
+ },
5073
+ hand: (side) => avatar.hand(side),
5074
+ shoulder: (side) => avatar.shoulder(side),
5075
+ reach: (targets, view) => avatar.reach(targets, view),
5076
+ strike: (clipId, strikeOptions) => {
5077
+ const clip = strikes.get(clipId);
5078
+ if (!clip) return null;
5079
+ const left = strikeOptions?.leg === "left" || strikeOptions?.hand === "left";
5080
+ const punch = guard !== void 0 && isPunch(clip);
5081
+ avatar.play(left ? leftOf(clip) : clip, punch ? { blend: punchBlend } : void 0);
5082
+ strikeLeft = punch ? clip.duration : 0;
5083
+ guardLeft = punch ? guardHold : 0;
5084
+ guarding = false;
5085
+ return impactOf(clip);
5086
+ }
5087
+ };
5088
+ };
5089
+
5090
+ // src/player/test-module.ts
5091
+ import { POSE_CLIPS as POSE_CLIPS2, POSE_CLIP_LOCALES } from "@adatechnology/game-characters";
5092
+
5093
+ // src/player/test-text.ts
5094
+ var AVATAR_TEXT = {
5095
+ en: {
5096
+ group: "Avatar",
5097
+ name: "Player body",
5098
+ actions: {
5099
+ camera_first: "Camera: 1st person",
5100
+ camera_third: "Camera: 3rd person",
5101
+ crouch: "Crouch",
5102
+ stand: "Stand up",
5103
+ fall: "Fall down",
5104
+ get_up: "Get up",
5105
+ armed_on: "Armed: on",
5106
+ armed_off: "Armed: off",
5107
+ stop_clip: "Stop clip"
5108
+ },
5109
+ play: "Play",
5110
+ metrics: { camera: "Camera", clip: "Clip playing", last_event: "Last clip event", state: "State", joints: "Joints" },
5111
+ none: "none"
5112
+ },
5113
+ "pt-BR": {
5114
+ group: "Avatar",
5115
+ name: "Corpo do jogador",
5116
+ actions: {
5117
+ camera_first: "C\xE2mera: 1\xAA pessoa",
5118
+ camera_third: "C\xE2mera: 3\xAA pessoa",
5119
+ crouch: "Agachar",
5120
+ stand: "Levantar",
5121
+ fall: "Cair",
5122
+ get_up: "Levantar do ch\xE3o",
5123
+ armed_on: "Armado: ligado",
5124
+ armed_off: "Armado: desligado",
5125
+ stop_clip: "Parar clipe"
5126
+ },
5127
+ play: "Tocar",
5128
+ metrics: { camera: "C\xE2mera", clip: "Clipe tocando", last_event: "\xDAltimo evento do clipe", state: "Estado", joints: "Juntas" },
5129
+ none: "nenhum"
5130
+ }
5131
+ };
5132
+
5133
+ // src/player/test-module.ts
5134
+ var AVATAR_AREA_ID = "avatar_area";
5135
+ var round = (value) => (Math.round(value * 100) / 100).toString();
5136
+ function createAvatarTestModule(options) {
5137
+ const clips = options.clips ?? POSE_CLIPS2;
5138
+ const textOf = (locale) => AVATAR_TEXT[locale ?? options.locale ?? "pt-BR"] ?? AVATAR_TEXT["pt-BR"];
5139
+ const phaseLabel = (label, locale) => {
5140
+ const id = locale ?? options.locale ?? "pt-BR";
5141
+ const table = { ...POSE_CLIP_LOCALES[id] ?? POSE_CLIP_LOCALES["pt-BR"], ...options.clipLocales?.[id] ?? options.clipLocales?.["pt-BR"] };
5142
+ return typeof label === "string" ? table[label] ?? label : "";
5143
+ };
5144
+ const state = { crouch: false, down: false, armed: null };
5145
+ let host = null;
5146
+ let lastEvent = "";
5147
+ const log = (action, extra = {}) => {
5148
+ const entry = { type: "avatar", action, ...extra };
5149
+ host?.log(JSON.stringify(entry), entry);
5150
+ };
5151
+ const push = (action, patch, extra = {}) => {
5152
+ Object.assign(state, patch);
5153
+ options.state?.set(patch);
5154
+ log(action, { crouch: state.crouch, down: state.down, armed: state.armed, ...extra });
5155
+ };
5156
+ const setCamera = (mode) => {
5157
+ options.camera?.set(mode);
5158
+ log(`camera_${mode}`, { mode: options.camera?.mode() ?? null });
5159
+ };
5160
+ const onChange = (change) => {
5161
+ if (change.kind !== "action") return;
5162
+ switch (change.id) {
5163
+ case "camera_first":
5164
+ return setCamera("first");
5165
+ case "camera_third":
5166
+ return setCamera("third");
5167
+ case "crouch":
5168
+ return push("crouch", { crouch: true });
5169
+ case "stand":
5170
+ return push("stand", { crouch: false });
5171
+ case "fall":
5172
+ return push("fall", { down: true });
5173
+ case "get_up":
5174
+ return push("get_up", { down: false });
5175
+ case "armed_on":
5176
+ return push("armed_on", { armed: true });
5177
+ case "armed_off":
5178
+ return push("armed_off", { armed: false });
5179
+ case "stop_clip":
5180
+ options.avatar.stop();
5181
+ return log("stop_clip", { playing: options.avatar.playing });
5182
+ default: {
5183
+ const clip = clips.find((item) => `play_${item.id}` === change.id);
5184
+ if (!clip) return;
5185
+ options.avatar.play(clip);
5186
+ log("play_clip", { clip: clip.id, duration: clip.duration, loop: clip.loop ?? false, channels: clip.channels ?? "full" });
5187
+ }
5188
+ }
5189
+ };
5190
+ const joints = () => {
5191
+ const rig = options.avatar.rig;
5192
+ const [armA, armB] = rig.arms;
5193
+ const [legA, legB] = rig.legs;
5194
+ return `arms ${round(armA.g.rotation.x)}/${round(armB.g.rotation.x)} elbows ${round(armA.el.rotation.x)}/${round(armB.el.rotation.x)} legs ${round(legA.g.rotation.x)}/${round(legB.g.rotation.x)} knees ${round(legA.kn.rotation.x)}/${round(legB.kn.rotation.x)} lean ${round(rig.upper.rotation.x)}`;
5195
+ };
5196
+ return {
5197
+ id: AVATAR_AREA_ID,
5198
+ name: textOf().name,
5199
+ ground: { surface: "concrete_slab" },
5200
+ group: textOf().group,
5201
+ state,
5202
+ items: () => [],
5203
+ area: () => ({ placements: [], view: { eye: [0, 1.7, 6], target: [0, 1.2, 0] } }),
5204
+ controls: (locale) => {
5205
+ const text = textOf(locale);
5206
+ const action = (id, name, hint) => ({ kind: "action", id, name, ...hint ? { hint } : {} });
5207
+ return [
5208
+ action("camera_first", text.actions.camera_first),
5209
+ action("camera_third", text.actions.camera_third),
5210
+ action("crouch", text.actions.crouch),
5211
+ action("stand", text.actions.stand),
5212
+ action("fall", text.actions.fall),
5213
+ action("get_up", text.actions.get_up),
5214
+ action("armed_on", text.actions.armed_on),
5215
+ action("armed_off", text.actions.armed_off),
5216
+ ...clips.map((clip) => action(`play_${clip.id}`, `${text.play}: ${clip.id}`)),
5217
+ action("stop_clip", text.actions.stop_clip)
5218
+ ];
5219
+ },
5220
+ metrics: (locale) => {
5221
+ const text = textOf(locale);
5222
+ return [
5223
+ { id: "camera", name: text.metrics.camera, read: () => options.camera?.mode() ?? text.none },
5224
+ { id: "clip", name: text.metrics.clip, read: () => options.avatar.playing ?? text.none },
5225
+ { id: "last_event", name: text.metrics.last_event, read: () => lastEvent || text.none },
5226
+ { id: "state", name: text.metrics.state, read: () => `${state.crouch ? "crouch " : ""}${state.down ? "down " : ""}${state.armed === null ? "" : state.armed ? "armed" : "unarmed"}`.trim() || text.none },
5227
+ { id: "joints", name: text.metrics.joints, read: joints }
5228
+ ];
5229
+ },
5230
+ attach(lent) {
5231
+ host = lent;
5232
+ const stopChange = lent.bench?.onChange(onChange);
5233
+ const stopEvents = options.avatar.on("event", (event) => {
5234
+ lastEvent = `${event.clip} ${event.type} ${round(event.t)}s${event.data && "label" in event.data ? ` ${phaseLabel(event.data.label)}` : ""}`;
5235
+ log("clip_event", { clip: event.clip, event: event.type, t: event.t, data: event.data ?? null });
5236
+ if (event.type === "impact") log("impact", { clip: event.clip, t: event.t, kind: event.data?.kind ?? null });
5237
+ });
5238
+ return () => {
5239
+ stopChange?.();
5240
+ stopEvents();
5241
+ host = null;
5242
+ };
5243
+ }
5244
+ };
5245
+ }
5012
5246
 
5013
5247
  // src/first-person/arms.ts
5014
5248
  import { clipEventsBetween as clipEventsBetween2, sampleFirstPersonClip } from "@adatechnology/game-characters";
@@ -5544,244 +5778,6 @@ function createFirstPersonArms(THREE, options = {}) {
5544
5778
  }
5545
5779
  };
5546
5780
  }
5547
-
5548
- // src/player/room-avatar.ts
5549
- var impactOf = (clip) => clip.events?.find((event) => event.type === "impact")?.t ?? null;
5550
- var isPunch = (clip) => clip.events?.some((event) => event.type === "impact" && event.data?.kind === "punch") ?? false;
5551
- var GUARD_SPEED = 0.3;
5552
- var toRoomAvatar = (avatar, options = {}) => {
5553
- const arms = options.firstPerson === false || options.firstPerson === void 0 ? null : createFirstPersonArms(avatar.three, {
5554
- 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 },
5555
- clips: options.firstPersonClips ?? FIRST_PERSON_CLIPS,
5556
- hideWhenIdle: true,
5557
- ...options.firstPerson
5558
- });
5559
- const strikes = new Map((options.strikeClips ?? ATTACK_CLIPS).map((clip) => [clip.id, clip]));
5560
- const mirrored = /* @__PURE__ */ new Map();
5561
- const leftOf = (clip) => {
5562
- let found = mirrored.get(clip.id);
5563
- if (!found) mirrored.set(clip.id, found = mirrorPoseClip(clip, `${clip.id}_left`));
5564
- return found;
5565
- };
5566
- const guard = options.guardClip === false ? void 0 : strikes.get(options.guardClip ?? "fight_guard");
5567
- const guardHold = options.guardHold ?? 1.2;
5568
- const punchBlend = options.punchBlend ?? 0.05;
5569
- let strikeLeft = 0;
5570
- let guardLeft = 0;
5571
- let guarding = false;
5572
- const stepGuard = (dt, state) => {
5573
- if (!guard) return;
5574
- const free = state.speed < GUARD_SPEED && !state.down && !state.armed && !state.holding && !state.push && !state.door && !state.itemUse && !state.running;
5575
- if (strikeLeft > 0) {
5576
- strikeLeft -= dt;
5577
- if (strikeLeft <= 0 && guardLeft > 0 && free) {
5578
- avatar.play(guard);
5579
- guarding = true;
5580
- }
5581
- return;
5582
- }
5583
- guardLeft = Math.max(0, guardLeft - dt);
5584
- if (guarding && (guardLeft <= 0 || !free || avatar.playing !== guard.id)) {
5585
- if (avatar.playing === guard.id) avatar.stop();
5586
- guarding = false;
5587
- }
5588
- };
5589
- return {
5590
- root: avatar.root,
5591
- avatar,
5592
- arms,
5593
- eye: (out) => avatar.eye(out),
5594
- setView: (mode, camera) => avatar.setView(mode, camera),
5595
- update: (dt, state) => {
5596
- stepGuard(dt, state);
5597
- avatar.update(dt, {
5598
- speed: state.speed,
5599
- running: state.running,
5600
- crouch: state.crouch,
5601
- down: state.down,
5602
- armed: state.armed,
5603
- pitch: -state.pitch,
5604
- ...state.holding !== void 0 ? { holding: state.holding } : {},
5605
- ...state.itemUse !== void 0 ? { itemUse: state.itemUse } : {},
5606
- ...state.push !== void 0 ? { push: state.push } : {},
5607
- ...state.seat !== void 0 ? { seat: state.seat } : {},
5608
- ...state.door !== void 0 ? { door: state.door } : {}
5609
- });
5610
- },
5611
- hand: (side) => avatar.hand(side),
5612
- shoulder: (side) => avatar.shoulder(side),
5613
- reach: (targets, view) => avatar.reach(targets, view),
5614
- strike: (clipId, strikeOptions) => {
5615
- const clip = strikes.get(clipId);
5616
- if (!clip) return null;
5617
- const left = strikeOptions?.leg === "left" || strikeOptions?.hand === "left";
5618
- const punch = guard !== void 0 && isPunch(clip);
5619
- avatar.play(left ? leftOf(clip) : clip, punch ? { blend: punchBlend } : void 0);
5620
- strikeLeft = punch ? clip.duration : 0;
5621
- guardLeft = punch ? guardHold : 0;
5622
- guarding = false;
5623
- arms?.play(clipId, strikeOptions?.leg === "left" ? { leg: "left" } : strikeOptions?.hand === "left" ? { hand: "left" } : void 0);
5624
- return impactOf(clip);
5625
- }
5626
- };
5627
- };
5628
-
5629
- // src/player/test-module.ts
5630
- import { POSE_CLIPS as POSE_CLIPS2, POSE_CLIP_LOCALES } from "@adatechnology/game-characters";
5631
-
5632
- // src/player/test-text.ts
5633
- var AVATAR_TEXT = {
5634
- en: {
5635
- group: "Avatar",
5636
- name: "Player body",
5637
- actions: {
5638
- camera_first: "Camera: 1st person",
5639
- camera_third: "Camera: 3rd person",
5640
- crouch: "Crouch",
5641
- stand: "Stand up",
5642
- fall: "Fall down",
5643
- get_up: "Get up",
5644
- armed_on: "Armed: on",
5645
- armed_off: "Armed: off",
5646
- stop_clip: "Stop clip"
5647
- },
5648
- play: "Play",
5649
- metrics: { camera: "Camera", clip: "Clip playing", last_event: "Last clip event", state: "State", joints: "Joints" },
5650
- none: "none"
5651
- },
5652
- "pt-BR": {
5653
- group: "Avatar",
5654
- name: "Corpo do jogador",
5655
- actions: {
5656
- camera_first: "C\xE2mera: 1\xAA pessoa",
5657
- camera_third: "C\xE2mera: 3\xAA pessoa",
5658
- crouch: "Agachar",
5659
- stand: "Levantar",
5660
- fall: "Cair",
5661
- get_up: "Levantar do ch\xE3o",
5662
- armed_on: "Armado: ligado",
5663
- armed_off: "Armado: desligado",
5664
- stop_clip: "Parar clipe"
5665
- },
5666
- play: "Tocar",
5667
- metrics: { camera: "C\xE2mera", clip: "Clipe tocando", last_event: "\xDAltimo evento do clipe", state: "Estado", joints: "Juntas" },
5668
- none: "nenhum"
5669
- }
5670
- };
5671
-
5672
- // src/player/test-module.ts
5673
- var AVATAR_AREA_ID = "avatar_area";
5674
- var round = (value) => (Math.round(value * 100) / 100).toString();
5675
- function createAvatarTestModule(options) {
5676
- const clips = options.clips ?? POSE_CLIPS2;
5677
- const textOf = (locale) => AVATAR_TEXT[locale ?? options.locale ?? "pt-BR"] ?? AVATAR_TEXT["pt-BR"];
5678
- const phaseLabel = (label, locale) => {
5679
- const id = locale ?? options.locale ?? "pt-BR";
5680
- const table = { ...POSE_CLIP_LOCALES[id] ?? POSE_CLIP_LOCALES["pt-BR"], ...options.clipLocales?.[id] ?? options.clipLocales?.["pt-BR"] };
5681
- return typeof label === "string" ? table[label] ?? label : "";
5682
- };
5683
- const state = { crouch: false, down: false, armed: null };
5684
- let host = null;
5685
- let lastEvent = "";
5686
- const log = (action, extra = {}) => {
5687
- const entry = { type: "avatar", action, ...extra };
5688
- host?.log(JSON.stringify(entry), entry);
5689
- };
5690
- const push = (action, patch, extra = {}) => {
5691
- Object.assign(state, patch);
5692
- options.state?.set(patch);
5693
- log(action, { crouch: state.crouch, down: state.down, armed: state.armed, ...extra });
5694
- };
5695
- const setCamera = (mode) => {
5696
- options.camera?.set(mode);
5697
- log(`camera_${mode}`, { mode: options.camera?.mode() ?? null });
5698
- };
5699
- const onChange = (change) => {
5700
- if (change.kind !== "action") return;
5701
- switch (change.id) {
5702
- case "camera_first":
5703
- return setCamera("first");
5704
- case "camera_third":
5705
- return setCamera("third");
5706
- case "crouch":
5707
- return push("crouch", { crouch: true });
5708
- case "stand":
5709
- return push("stand", { crouch: false });
5710
- case "fall":
5711
- return push("fall", { down: true });
5712
- case "get_up":
5713
- return push("get_up", { down: false });
5714
- case "armed_on":
5715
- return push("armed_on", { armed: true });
5716
- case "armed_off":
5717
- return push("armed_off", { armed: false });
5718
- case "stop_clip":
5719
- options.avatar.stop();
5720
- return log("stop_clip", { playing: options.avatar.playing });
5721
- default: {
5722
- const clip = clips.find((item) => `play_${item.id}` === change.id);
5723
- if (!clip) return;
5724
- options.avatar.play(clip);
5725
- log("play_clip", { clip: clip.id, duration: clip.duration, loop: clip.loop ?? false, channels: clip.channels ?? "full" });
5726
- }
5727
- }
5728
- };
5729
- const joints = () => {
5730
- const rig = options.avatar.rig;
5731
- const [armA, armB] = rig.arms;
5732
- const [legA, legB] = rig.legs;
5733
- return `arms ${round(armA.g.rotation.x)}/${round(armB.g.rotation.x)} elbows ${round(armA.el.rotation.x)}/${round(armB.el.rotation.x)} legs ${round(legA.g.rotation.x)}/${round(legB.g.rotation.x)} knees ${round(legA.kn.rotation.x)}/${round(legB.kn.rotation.x)} lean ${round(rig.upper.rotation.x)}`;
5734
- };
5735
- return {
5736
- id: AVATAR_AREA_ID,
5737
- name: textOf().name,
5738
- ground: { surface: "concrete_slab" },
5739
- group: textOf().group,
5740
- state,
5741
- items: () => [],
5742
- area: () => ({ placements: [], view: { eye: [0, 1.7, 6], target: [0, 1.2, 0] } }),
5743
- controls: (locale) => {
5744
- const text = textOf(locale);
5745
- const action = (id, name, hint) => ({ kind: "action", id, name, ...hint ? { hint } : {} });
5746
- return [
5747
- action("camera_first", text.actions.camera_first),
5748
- action("camera_third", text.actions.camera_third),
5749
- action("crouch", text.actions.crouch),
5750
- action("stand", text.actions.stand),
5751
- action("fall", text.actions.fall),
5752
- action("get_up", text.actions.get_up),
5753
- action("armed_on", text.actions.armed_on),
5754
- action("armed_off", text.actions.armed_off),
5755
- ...clips.map((clip) => action(`play_${clip.id}`, `${text.play}: ${clip.id}`)),
5756
- action("stop_clip", text.actions.stop_clip)
5757
- ];
5758
- },
5759
- metrics: (locale) => {
5760
- const text = textOf(locale);
5761
- return [
5762
- { id: "camera", name: text.metrics.camera, read: () => options.camera?.mode() ?? text.none },
5763
- { id: "clip", name: text.metrics.clip, read: () => options.avatar.playing ?? text.none },
5764
- { id: "last_event", name: text.metrics.last_event, read: () => lastEvent || text.none },
5765
- { id: "state", name: text.metrics.state, read: () => `${state.crouch ? "crouch " : ""}${state.down ? "down " : ""}${state.armed === null ? "" : state.armed ? "armed" : "unarmed"}`.trim() || text.none },
5766
- { id: "joints", name: text.metrics.joints, read: joints }
5767
- ];
5768
- },
5769
- attach(lent) {
5770
- host = lent;
5771
- const stopChange = lent.bench?.onChange(onChange);
5772
- const stopEvents = options.avatar.on("event", (event) => {
5773
- lastEvent = `${event.clip} ${event.type} ${round(event.t)}s${event.data && "label" in event.data ? ` ${phaseLabel(event.data.label)}` : ""}`;
5774
- log("clip_event", { clip: event.clip, event: event.type, t: event.t, data: event.data ?? null });
5775
- if (event.type === "impact") log("impact", { clip: event.clip, t: event.t, kind: event.data?.kind ?? null });
5776
- });
5777
- return () => {
5778
- stopChange?.();
5779
- stopEvents();
5780
- host = null;
5781
- };
5782
- }
5783
- };
5784
- }
5785
5781
  export {
5786
5782
  AVATAR_AREA_ID,
5787
5783
  BAKE_NEUTRAL_POSE,