@adatechnology/game-characters 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +18 -2
- package/dist/index.d.ts +1103 -0
- package/dist/index.global.js +2448 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +2421 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,2421 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/data/body.json
|
|
8
|
+
var body_default = {
|
|
9
|
+
guide: {
|
|
10
|
+
heightMetres: 1.8,
|
|
11
|
+
shoulderWidthMetres: 0.61,
|
|
12
|
+
shoulderWidthWomanMetres: 0.57,
|
|
13
|
+
shoulderHeightMetres: 1.475,
|
|
14
|
+
headToHeight: 0.1667
|
|
15
|
+
},
|
|
16
|
+
teamGame: {
|
|
17
|
+
source: "G 65336 J (shoulders +-0.24 m)",
|
|
18
|
+
joints: {
|
|
19
|
+
quadril: [
|
|
20
|
+
0,
|
|
21
|
+
0.95,
|
|
22
|
+
0
|
|
23
|
+
],
|
|
24
|
+
pescoco: [
|
|
25
|
+
0,
|
|
26
|
+
1.52,
|
|
27
|
+
0
|
|
28
|
+
],
|
|
29
|
+
ombroE: [
|
|
30
|
+
-0.24,
|
|
31
|
+
1.44,
|
|
32
|
+
0
|
|
33
|
+
],
|
|
34
|
+
ombroD: [
|
|
35
|
+
0.24,
|
|
36
|
+
1.44,
|
|
37
|
+
0
|
|
38
|
+
],
|
|
39
|
+
cotoveloE: [
|
|
40
|
+
-0.27,
|
|
41
|
+
1.13,
|
|
42
|
+
0
|
|
43
|
+
],
|
|
44
|
+
cotoveloD: [
|
|
45
|
+
0.27,
|
|
46
|
+
1.13,
|
|
47
|
+
0
|
|
48
|
+
],
|
|
49
|
+
coxaE: [
|
|
50
|
+
-0.1,
|
|
51
|
+
0.92,
|
|
52
|
+
0
|
|
53
|
+
],
|
|
54
|
+
coxaD: [
|
|
55
|
+
0.1,
|
|
56
|
+
0.92,
|
|
57
|
+
0
|
|
58
|
+
],
|
|
59
|
+
joelhoE: [
|
|
60
|
+
-0.1,
|
|
61
|
+
0.5,
|
|
62
|
+
0
|
|
63
|
+
],
|
|
64
|
+
joelhoD: [
|
|
65
|
+
0.1,
|
|
66
|
+
0.5,
|
|
67
|
+
0
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
heightMetres: 1.8
|
|
71
|
+
},
|
|
72
|
+
lodDistancesMetres: [
|
|
73
|
+
18,
|
|
74
|
+
55
|
|
75
|
+
],
|
|
76
|
+
triangleBudget: {
|
|
77
|
+
lod0: 2500,
|
|
78
|
+
lod1: 800,
|
|
79
|
+
lod2: 250
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/body/body-spec.ts
|
|
84
|
+
var JOINT_IDS = ["hip", "spine", "neck", "head", "jaw", "shoulderL", "shoulderR", "elbowL", "elbowR", "wristL", "wristR", "hipL", "hipR", "kneeL", "kneeR", "ankleL", "ankleR"];
|
|
85
|
+
var SEGMENT_RADIUS = {
|
|
86
|
+
head: 0.1,
|
|
87
|
+
jaw: 0.05,
|
|
88
|
+
neck: 0.06,
|
|
89
|
+
torso: 0.17,
|
|
90
|
+
pelvis: 0.16,
|
|
91
|
+
upperArm: 0.085,
|
|
92
|
+
forearm: 0.065,
|
|
93
|
+
hand: 0.05,
|
|
94
|
+
thigh: 0.1,
|
|
95
|
+
shin: 0.08,
|
|
96
|
+
foot: 0.05
|
|
97
|
+
};
|
|
98
|
+
var { guide } = body_default;
|
|
99
|
+
var shoulderX = guide.shoulderWidthMetres / 2;
|
|
100
|
+
var shoulderY = guide.shoulderHeightMetres;
|
|
101
|
+
var GUIDE = {
|
|
102
|
+
id: "guide",
|
|
103
|
+
heightMetres: guide.heightMetres,
|
|
104
|
+
joints: {
|
|
105
|
+
hip: [0, 0.92, 0],
|
|
106
|
+
spine: [0, 1.05, 0],
|
|
107
|
+
neck: [0, 1.56, 0],
|
|
108
|
+
head: [0, 1.64, 0],
|
|
109
|
+
jaw: [0, 1.66, 0.06],
|
|
110
|
+
shoulderL: [shoulderX, shoulderY, 0],
|
|
111
|
+
shoulderR: [-shoulderX, shoulderY, 0],
|
|
112
|
+
elbowL: [shoulderX, shoulderY - 0.31, 0],
|
|
113
|
+
elbowR: [-shoulderX, shoulderY - 0.31, 0],
|
|
114
|
+
wristL: [shoulderX, shoulderY - 0.61, 0],
|
|
115
|
+
wristR: [-shoulderX, shoulderY - 0.61, 0],
|
|
116
|
+
hipL: [0.105, 0.9, 0],
|
|
117
|
+
hipR: [-0.105, 0.9, 0],
|
|
118
|
+
kneeL: [0.105, 0.48, 0],
|
|
119
|
+
kneeR: [-0.105, 0.48, 0],
|
|
120
|
+
ankleL: [0.105, 0.1, 0],
|
|
121
|
+
ankleR: [-0.105, 0.1, 0]
|
|
122
|
+
},
|
|
123
|
+
segmentRadius: SEGMENT_RADIUS
|
|
124
|
+
};
|
|
125
|
+
var J = body_default.teamGame.joints;
|
|
126
|
+
var below = (point, drop) => [point[0], point[1] - drop, point[2]];
|
|
127
|
+
var TEAM_GAME = {
|
|
128
|
+
id: "team-game",
|
|
129
|
+
heightMetres: body_default.teamGame.heightMetres,
|
|
130
|
+
joints: {
|
|
131
|
+
hip: J.quadril,
|
|
132
|
+
spine: J.quadril,
|
|
133
|
+
neck: J.pescoco,
|
|
134
|
+
head: J.pescoco,
|
|
135
|
+
jaw: [0, J.pescoco[1] + 0.08, 0.06],
|
|
136
|
+
shoulderL: J.ombroE,
|
|
137
|
+
shoulderR: J.ombroD,
|
|
138
|
+
elbowL: J.cotoveloE,
|
|
139
|
+
elbowR: J.cotoveloD,
|
|
140
|
+
wristL: below(J.cotoveloE, 0.3),
|
|
141
|
+
wristR: below(J.cotoveloD, 0.3),
|
|
142
|
+
hipL: J.coxaE,
|
|
143
|
+
hipR: J.coxaD,
|
|
144
|
+
kneeL: J.joelhoE,
|
|
145
|
+
kneeR: J.joelhoD,
|
|
146
|
+
ankleL: below(J.joelhoE, 0.42),
|
|
147
|
+
ankleR: below(J.joelhoD, 0.42)
|
|
148
|
+
},
|
|
149
|
+
segmentRadius: SEGMENT_RADIUS
|
|
150
|
+
};
|
|
151
|
+
var createBodySpec = (patch = {}, base = GUIDE) => ({
|
|
152
|
+
id: patch.id ?? base.id,
|
|
153
|
+
heightMetres: patch.heightMetres ?? base.heightMetres,
|
|
154
|
+
joints: { ...base.joints, ...patch.joints },
|
|
155
|
+
segmentRadius: { ...base.segmentRadius, ...patch.segmentRadius }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// src/corpses.ts
|
|
159
|
+
var createCorpseLedger = (options) => {
|
|
160
|
+
const entries = [];
|
|
161
|
+
return {
|
|
162
|
+
entries,
|
|
163
|
+
get count() {
|
|
164
|
+
return entries.length;
|
|
165
|
+
},
|
|
166
|
+
add(data) {
|
|
167
|
+
if (entries.length < options.max) {
|
|
168
|
+
entries.push({ age: 0, data });
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
let oldest = 0;
|
|
172
|
+
for (let index = 1; index < entries.length; index++) if (entries[index].age > entries[oldest].age) oldest = index;
|
|
173
|
+
const replaced = entries[oldest].data;
|
|
174
|
+
entries[oldest] = { age: 0, data };
|
|
175
|
+
return replaced;
|
|
176
|
+
},
|
|
177
|
+
update(dt) {
|
|
178
|
+
const removed = [];
|
|
179
|
+
for (let index = 0; index < entries.length; index++) {
|
|
180
|
+
const entry = entries[index];
|
|
181
|
+
entry.age += dt;
|
|
182
|
+
if (entry.age <= options.seconds) continue;
|
|
183
|
+
removed.push(entry.data);
|
|
184
|
+
entries[index] = entries[entries.length - 1];
|
|
185
|
+
entries.pop();
|
|
186
|
+
index--;
|
|
187
|
+
}
|
|
188
|
+
return removed;
|
|
189
|
+
},
|
|
190
|
+
clear() {
|
|
191
|
+
entries.length = 0;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/body/body-state.ts
|
|
197
|
+
import { MISSING_BITS } from "@adatechnology/game-contracts";
|
|
198
|
+
|
|
199
|
+
// src/presets/team-game.json
|
|
200
|
+
var team_game_default = {
|
|
201
|
+
id: "team-game",
|
|
202
|
+
segments: {
|
|
203
|
+
head: 40,
|
|
204
|
+
arm: 60,
|
|
205
|
+
leg: 80
|
|
206
|
+
},
|
|
207
|
+
calibers: {
|
|
208
|
+
"7_62x51": {
|
|
209
|
+
gameKey: "308",
|
|
210
|
+
name: "7,62\xD751 (.308)",
|
|
211
|
+
points: 75,
|
|
212
|
+
penetration: 4,
|
|
213
|
+
effectiveRangeMetres: 600,
|
|
214
|
+
noiseMetres: 130,
|
|
215
|
+
skullRange: 1,
|
|
216
|
+
class: "rifle"
|
|
217
|
+
},
|
|
218
|
+
"9x19": {
|
|
219
|
+
gameKey: "9x19",
|
|
220
|
+
name: "9\xD719 mm",
|
|
221
|
+
points: 35,
|
|
222
|
+
penetration: 1.2,
|
|
223
|
+
effectiveRangeMetres: 35,
|
|
224
|
+
noiseMetres: 60,
|
|
225
|
+
skullRange: 1,
|
|
226
|
+
class: "pistol"
|
|
227
|
+
},
|
|
228
|
+
"12ga_00buck": {
|
|
229
|
+
gameKey: "12ga",
|
|
230
|
+
name: "12 ga 00",
|
|
231
|
+
points: 12,
|
|
232
|
+
pellets: 9,
|
|
233
|
+
penetration: 0.5,
|
|
234
|
+
effectiveRangeMetres: 15,
|
|
235
|
+
noiseMetres: 90,
|
|
236
|
+
skullRange: 8,
|
|
237
|
+
class: "pistol"
|
|
238
|
+
},
|
|
239
|
+
"7_62x39": {
|
|
240
|
+
gameKey: "762x39",
|
|
241
|
+
name: "7,62\xD739 mm",
|
|
242
|
+
points: 55,
|
|
243
|
+
penetration: 3,
|
|
244
|
+
effectiveRangeMetres: 250,
|
|
245
|
+
noiseMetres: 110,
|
|
246
|
+
skullRange: 1,
|
|
247
|
+
class: "rifle"
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
regionCost: {
|
|
251
|
+
head: 0.7,
|
|
252
|
+
torso: 1,
|
|
253
|
+
bruteTorso: 2,
|
|
254
|
+
limb: 0.5
|
|
255
|
+
},
|
|
256
|
+
damageKeptPerBody: 0.75,
|
|
257
|
+
falloff: {
|
|
258
|
+
fullAtRange: 1,
|
|
259
|
+
atTwiceRange: 0.5
|
|
260
|
+
},
|
|
261
|
+
helmetAbsorbs: "pistol",
|
|
262
|
+
regions: [
|
|
263
|
+
"head",
|
|
264
|
+
"torso",
|
|
265
|
+
"armL",
|
|
266
|
+
"armR",
|
|
267
|
+
"legL",
|
|
268
|
+
"legR"
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/body/regions.ts
|
|
273
|
+
var LIMB_REGIONS = ["head", "armL", "armR", "legL", "legR"];
|
|
274
|
+
var REGION_HIT_POINTS = {
|
|
275
|
+
head: team_game_default.segments.head,
|
|
276
|
+
armL: team_game_default.segments.arm,
|
|
277
|
+
armR: team_game_default.segments.arm,
|
|
278
|
+
legL: team_game_default.segments.leg,
|
|
279
|
+
legR: team_game_default.segments.leg
|
|
280
|
+
};
|
|
281
|
+
var REGION_SEGMENTS = {
|
|
282
|
+
head: ["head", "jaw", "neck"],
|
|
283
|
+
torso: ["torso", "pelvis"],
|
|
284
|
+
armL: ["upperArmL", "forearmL", "handL"],
|
|
285
|
+
armR: ["upperArmR", "forearmR", "handR"],
|
|
286
|
+
legL: ["thighL", "shinL", "footL"],
|
|
287
|
+
legR: ["thighR", "shinR", "footR"]
|
|
288
|
+
};
|
|
289
|
+
var BODY_REGIONS = ["head", "torso", "armL", "armR", "legL", "legR"];
|
|
290
|
+
var isBodyRegion = (value) => BODY_REGIONS.includes(value);
|
|
291
|
+
|
|
292
|
+
// src/data/segments.json
|
|
293
|
+
var segments_default = {
|
|
294
|
+
segments: [
|
|
295
|
+
{
|
|
296
|
+
id: "head",
|
|
297
|
+
region: "head",
|
|
298
|
+
hitPoints: 40,
|
|
299
|
+
multiplier: 3,
|
|
300
|
+
bleed: 40
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "jaw",
|
|
304
|
+
region: "head",
|
|
305
|
+
hitPoints: 12,
|
|
306
|
+
multiplier: 2,
|
|
307
|
+
bleed: 8
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "neck",
|
|
311
|
+
region: "head",
|
|
312
|
+
hitPoints: 25,
|
|
313
|
+
multiplier: 2,
|
|
314
|
+
bleed: 46
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
id: "torso",
|
|
318
|
+
region: "torso",
|
|
319
|
+
hitPoints: 150,
|
|
320
|
+
multiplier: 1,
|
|
321
|
+
bleed: 10
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "pelvis",
|
|
325
|
+
region: "torso",
|
|
326
|
+
hitPoints: 90,
|
|
327
|
+
multiplier: 1,
|
|
328
|
+
bleed: 12
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "upperArm",
|
|
332
|
+
region: "arm",
|
|
333
|
+
hitPoints: 45,
|
|
334
|
+
multiplier: 0.6,
|
|
335
|
+
bleed: 15
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: "forearm",
|
|
339
|
+
region: "arm",
|
|
340
|
+
hitPoints: 30,
|
|
341
|
+
multiplier: 0.6,
|
|
342
|
+
bleed: 8
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "hand",
|
|
346
|
+
region: "arm",
|
|
347
|
+
hitPoints: 12,
|
|
348
|
+
multiplier: 0.6,
|
|
349
|
+
bleed: 3
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "thigh",
|
|
353
|
+
region: "leg",
|
|
354
|
+
hitPoints: 55,
|
|
355
|
+
multiplier: 0.7,
|
|
356
|
+
bleed: 19
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: "shin",
|
|
360
|
+
region: "leg",
|
|
361
|
+
hitPoints: 40,
|
|
362
|
+
multiplier: 0.7,
|
|
363
|
+
bleed: 9
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "foot",
|
|
367
|
+
region: "leg",
|
|
368
|
+
hitPoints: 15,
|
|
369
|
+
multiplier: 0.7,
|
|
370
|
+
bleed: 3
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
regions: {
|
|
374
|
+
head: {
|
|
375
|
+
hitPoints: 40,
|
|
376
|
+
segmentSum: 77,
|
|
377
|
+
scale: 0.5195
|
|
378
|
+
},
|
|
379
|
+
arm: {
|
|
380
|
+
hitPoints: 60,
|
|
381
|
+
segmentSum: 87,
|
|
382
|
+
scale: 0.6897
|
|
383
|
+
},
|
|
384
|
+
leg: {
|
|
385
|
+
hitPoints: 80,
|
|
386
|
+
segmentSum: 110,
|
|
387
|
+
scale: 0.7273
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
arsenalWeapons: {
|
|
391
|
+
pistola: {
|
|
392
|
+
n: "Pistola 9\xD719",
|
|
393
|
+
dmg: 35,
|
|
394
|
+
pel: 1
|
|
395
|
+
},
|
|
396
|
+
fuzil: {
|
|
397
|
+
n: "Fuzil 7,62\xD739",
|
|
398
|
+
dmg: 55,
|
|
399
|
+
pel: 1
|
|
400
|
+
},
|
|
401
|
+
escopeta: {
|
|
402
|
+
n: "Escopeta 12 ga",
|
|
403
|
+
dmg: 12,
|
|
404
|
+
pel: 9
|
|
405
|
+
},
|
|
406
|
+
cinquenta: {
|
|
407
|
+
n: ".50 BMG",
|
|
408
|
+
dmg: 180,
|
|
409
|
+
pel: 1
|
|
410
|
+
},
|
|
411
|
+
granada: {
|
|
412
|
+
n: "Granada",
|
|
413
|
+
dmg: 300,
|
|
414
|
+
pel: 0
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
elenco: {
|
|
418
|
+
segments: {
|
|
419
|
+
head: {
|
|
420
|
+
hp: 40,
|
|
421
|
+
radius: 0.07
|
|
422
|
+
},
|
|
423
|
+
torso: {
|
|
424
|
+
hp: 120
|
|
425
|
+
},
|
|
426
|
+
pelvis: {
|
|
427
|
+
hp: 90
|
|
428
|
+
},
|
|
429
|
+
upperArm: {
|
|
430
|
+
hp: 60,
|
|
431
|
+
radius: 0.085
|
|
432
|
+
},
|
|
433
|
+
forearm: {
|
|
434
|
+
hp: 45,
|
|
435
|
+
radius: 0.065
|
|
436
|
+
},
|
|
437
|
+
hand: {
|
|
438
|
+
hp: 25,
|
|
439
|
+
radius: 0.05
|
|
440
|
+
},
|
|
441
|
+
thigh: {
|
|
442
|
+
hp: 80,
|
|
443
|
+
radius: 0.1
|
|
444
|
+
},
|
|
445
|
+
shin: {
|
|
446
|
+
hp: 55,
|
|
447
|
+
radius: 0.08
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
below: {
|
|
451
|
+
upperArm: [
|
|
452
|
+
"forearm",
|
|
453
|
+
"hand"
|
|
454
|
+
],
|
|
455
|
+
forearm: [
|
|
456
|
+
"hand"
|
|
457
|
+
],
|
|
458
|
+
thigh: [
|
|
459
|
+
"shin"
|
|
460
|
+
]
|
|
461
|
+
},
|
|
462
|
+
falls: {
|
|
463
|
+
upperArm: "o bra\xE7o inteiro",
|
|
464
|
+
forearm: "o antebra\xE7o com a m\xE3o",
|
|
465
|
+
hand: "a m\xE3o",
|
|
466
|
+
thigh: "a perna inteira",
|
|
467
|
+
shin: "a canela com o p\xE9",
|
|
468
|
+
head: "a cabe\xE7a"
|
|
469
|
+
},
|
|
470
|
+
cutWeapons: [
|
|
471
|
+
{
|
|
472
|
+
id: "faca",
|
|
473
|
+
n: "Faca",
|
|
474
|
+
dmg: 22,
|
|
475
|
+
cut: 0.7
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "facao",
|
|
479
|
+
n: "Fac\xE3o",
|
|
480
|
+
dmg: 55,
|
|
481
|
+
cut: 1
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
id: "machado",
|
|
485
|
+
n: "Machado",
|
|
486
|
+
dmg: 85,
|
|
487
|
+
cut: 1
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
id: "p9",
|
|
491
|
+
n: "Pistola 9 mm",
|
|
492
|
+
dmg: 30,
|
|
493
|
+
cut: 0.15
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: "e12",
|
|
497
|
+
n: "Espingarda 12",
|
|
498
|
+
dmg: 95,
|
|
499
|
+
cut: 0.75
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: "f762",
|
|
503
|
+
n: "Fuzil 7,62",
|
|
504
|
+
dmg: 65,
|
|
505
|
+
cut: 0.45
|
|
506
|
+
}
|
|
507
|
+
]
|
|
508
|
+
},
|
|
509
|
+
severity: {
|
|
510
|
+
severAtCut: 0.5,
|
|
511
|
+
severAtDamageFraction: 0.9,
|
|
512
|
+
destroyAtDamageMultiple: 2,
|
|
513
|
+
limpAtHpFraction: 0.5,
|
|
514
|
+
splitAtDamage: 150,
|
|
515
|
+
headKillDamage: 20,
|
|
516
|
+
jawChance: 0.3,
|
|
517
|
+
jawDamage: 25,
|
|
518
|
+
headBitDamage: 60,
|
|
519
|
+
headBitShotgunMetres: 5
|
|
520
|
+
},
|
|
521
|
+
defaultCut: {
|
|
522
|
+
slash: 0.85,
|
|
523
|
+
blunt: 0,
|
|
524
|
+
explosive: 1,
|
|
525
|
+
ballistic: {
|
|
526
|
+
pistol: 0.15,
|
|
527
|
+
buckshot: 0.75,
|
|
528
|
+
rifle: 0.45
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
deathRule: {
|
|
532
|
+
default: "vitality-or-head",
|
|
533
|
+
options: [
|
|
534
|
+
"vitality-or-head",
|
|
535
|
+
"head-only"
|
|
536
|
+
]
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/body/segment-tree.ts
|
|
541
|
+
var limb = (kind, side, joint, parent, slots) => {
|
|
542
|
+
const isArm2 = kind === "upperArm" || kind === "forearm" || kind === "hand";
|
|
543
|
+
const region = isArm2 ? side === "L" ? "armL" : "armR" : side === "L" ? "legL" : "legR";
|
|
544
|
+
return { id: `${kind}${side}`, kind, side, joint, region, slots, ...parent ? { parent } : {} };
|
|
545
|
+
};
|
|
546
|
+
var sideOf = (side) => [
|
|
547
|
+
limb("upperArm", side, `shoulder${side}`, "torso", ["arms"]),
|
|
548
|
+
limb("forearm", side, `elbow${side}`, `upperArm${side}`, ["arms"]),
|
|
549
|
+
limb("hand", side, `wrist${side}`, `forearm${side}`, ["hands"]),
|
|
550
|
+
limb("thigh", side, `hip${side}`, "pelvis", ["legs"]),
|
|
551
|
+
limb("shin", side, `knee${side}`, `thigh${side}`, ["legs"]),
|
|
552
|
+
limb("foot", side, `ankle${side}`, `shin${side}`, ["feet"])
|
|
553
|
+
];
|
|
554
|
+
var SEGMENT_TREE = [
|
|
555
|
+
{ id: "pelvis", kind: "pelvis", joint: "hip", region: "torso", slots: ["torso"] },
|
|
556
|
+
{ id: "torso", kind: "torso", joint: "spine", parent: "pelvis", region: "torso", slots: ["torso"] },
|
|
557
|
+
{ id: "neck", kind: "neck", joint: "neck", parent: "torso", region: "head", slots: [] },
|
|
558
|
+
{ id: "head", kind: "head", joint: "head", parent: "neck", region: "head", slots: ["head", "eyes"] },
|
|
559
|
+
{ id: "jaw", kind: "jaw", joint: "jaw", parent: "head", region: "head", slots: [] },
|
|
560
|
+
...sideOf("L"),
|
|
561
|
+
...sideOf("R")
|
|
562
|
+
];
|
|
563
|
+
var SEGMENT_IDS = SEGMENT_TREE.map((node) => node.id);
|
|
564
|
+
var NODES = new Map(SEGMENT_TREE.map((node) => [node.id, node]));
|
|
565
|
+
var segmentNode = (id) => {
|
|
566
|
+
const node = NODES.get(id);
|
|
567
|
+
if (!node) throw new Error(`game-characters: unknown segment "${id}"`);
|
|
568
|
+
return node;
|
|
569
|
+
};
|
|
570
|
+
var isSegmentId = (value) => NODES.has(value);
|
|
571
|
+
var segmentsBelow = (id) => {
|
|
572
|
+
const out = [];
|
|
573
|
+
const visit = (parent) => {
|
|
574
|
+
for (const node of SEGMENT_TREE) {
|
|
575
|
+
if (node.parent !== parent) continue;
|
|
576
|
+
out.push(node.id);
|
|
577
|
+
visit(node.id);
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
visit(id);
|
|
581
|
+
return out;
|
|
582
|
+
};
|
|
583
|
+
var SEGMENT_NUMBERS = Object.fromEntries(
|
|
584
|
+
segments_default.segments.map((entry) => [entry.id, { hitPoints: entry.hitPoints, multiplier: entry.multiplier, bleed: entry.bleed }])
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
// src/body/body-state.ts
|
|
588
|
+
var createBodyState = ({ vitality, detail, crawling, hitPointsOf }) => ({
|
|
589
|
+
segments: SEGMENT_TREE.map((node) => {
|
|
590
|
+
const max = hitPointsOf(node.kind);
|
|
591
|
+
return { id: node.id, kind: node.kind, region: node.region, hp: max, max, status: "ok", attached: true, ...node.side ? { side: node.side } : {} };
|
|
592
|
+
}),
|
|
593
|
+
regions: { ...REGION_HIT_POINTS },
|
|
594
|
+
vitality,
|
|
595
|
+
maxVitality: vitality,
|
|
596
|
+
alive: true,
|
|
597
|
+
missing: 0,
|
|
598
|
+
limping: [],
|
|
599
|
+
crawl: crawling ?? false,
|
|
600
|
+
stunSeconds: 0,
|
|
601
|
+
pieces: [],
|
|
602
|
+
consumed: /* @__PURE__ */ new Set(),
|
|
603
|
+
detail
|
|
604
|
+
});
|
|
605
|
+
var segmentOf = (body, id) => {
|
|
606
|
+
const segment = body.segments.find((candidate) => candidate.id === id);
|
|
607
|
+
if (!segment) throw new Error(`game-characters: unknown segment "${id}"`);
|
|
608
|
+
return segment;
|
|
609
|
+
};
|
|
610
|
+
var LIMB_MASK = [
|
|
611
|
+
{ ids: ["upperArmL", "forearmL"], lost: MISSING_BITS.armL, broken: MISSING_BITS.armLBroken },
|
|
612
|
+
{ ids: ["upperArmR", "forearmR"], lost: MISSING_BITS.armR, broken: MISSING_BITS.armRBroken },
|
|
613
|
+
{ ids: ["thighL", "shinL"], lost: MISSING_BITS.legL, broken: MISSING_BITS.legLBroken },
|
|
614
|
+
{ ids: ["thighR", "shinR"], lost: MISSING_BITS.legR, broken: MISSING_BITS.legRBroken }
|
|
615
|
+
];
|
|
616
|
+
var missingFromSegments = (body) => {
|
|
617
|
+
let mask = 0;
|
|
618
|
+
if (!segmentOf(body, "head").attached) mask |= MISSING_BITS.head;
|
|
619
|
+
else if (!segmentOf(body, "jaw").attached) mask |= MISSING_BITS.jaw;
|
|
620
|
+
for (const limb2 of LIMB_MASK) {
|
|
621
|
+
const parts = limb2.ids.map((id) => segmentOf(body, id));
|
|
622
|
+
if (parts.some((part) => !part.attached)) mask |= limb2.lost;
|
|
623
|
+
else if (parts.some((part) => part.status === "broken")) mask |= limb2.broken;
|
|
624
|
+
}
|
|
625
|
+
return mask;
|
|
626
|
+
};
|
|
627
|
+
var regionsFromSegments = (body) => {
|
|
628
|
+
for (const region of LIMB_REGIONS) {
|
|
629
|
+
const parts = REGION_SEGMENTS[region].map((id) => segmentOf(body, id));
|
|
630
|
+
const left = parts.reduce((sum, part) => sum + (part.attached ? Math.max(0, part.hp) : 0), 0);
|
|
631
|
+
const total = parts.reduce((sum, part) => sum + part.max, 0);
|
|
632
|
+
body.regions[region] = REGION_HIT_POINTS[region] * left / total;
|
|
633
|
+
}
|
|
634
|
+
body.missing |= missingFromSegments(body);
|
|
635
|
+
};
|
|
636
|
+
var segmentsFromRegions = (body) => {
|
|
637
|
+
for (const region of LIMB_REGIONS) {
|
|
638
|
+
const share = Math.max(0, body.regions[region]) / REGION_HIT_POINTS[region];
|
|
639
|
+
for (const id of REGION_SEGMENTS[region]) {
|
|
640
|
+
const part = segmentOf(body, id);
|
|
641
|
+
if (part.attached) part.hp = Math.min(part.hp, part.max * share);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/body/describe.ts
|
|
647
|
+
var BODY_MATERIAL = "flesh";
|
|
648
|
+
var MASS_SHARE = {
|
|
649
|
+
head: 0.07,
|
|
650
|
+
jaw: 0.01,
|
|
651
|
+
neck: 0.02,
|
|
652
|
+
torso: 0.34,
|
|
653
|
+
pelvis: 0.14,
|
|
654
|
+
upperArm: 0.028,
|
|
655
|
+
forearm: 0.016,
|
|
656
|
+
hand: 6e-3,
|
|
657
|
+
thigh: 0.1,
|
|
658
|
+
shin: 0.045,
|
|
659
|
+
foot: 0.015
|
|
660
|
+
};
|
|
661
|
+
var ADULT_MASS_KG = 70;
|
|
662
|
+
var LEAF_KINDS = /* @__PURE__ */ new Set(["head", "jaw", "hand", "foot"]);
|
|
663
|
+
var firstChild = (node) => SEGMENT_TREE.find((candidate) => candidate.parent === node.id);
|
|
664
|
+
var cylinderBetween = (from, to, radius) => {
|
|
665
|
+
const axis = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
666
|
+
const length = Math.max(1e-3, Math.hypot(axis[0], axis[1], axis[2]));
|
|
667
|
+
const [x, y, z] = [axis[0] / length, axis[1] / length, axis[2] / length];
|
|
668
|
+
return {
|
|
669
|
+
shape: { kind: "cylinder", radius, height: length },
|
|
670
|
+
position: [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2, (from[2] + to[2]) / 2],
|
|
671
|
+
rotation: [Math.atan2(z, y), 0, -Math.asin(Math.max(-1, Math.min(1, x)))]
|
|
672
|
+
};
|
|
673
|
+
};
|
|
674
|
+
var partOf = (node, rig, body, massKg) => {
|
|
675
|
+
const joint = rig.joints[node.joint];
|
|
676
|
+
const radius = body.segmentRadius[node.kind] * (rig.height / body.heightMetres);
|
|
677
|
+
const mass = massKg * MASS_SHARE[node.kind];
|
|
678
|
+
const child = firstChild(node);
|
|
679
|
+
if (LEAF_KINDS.has(node.kind) || !child) {
|
|
680
|
+
const lift = node.kind === "head" ? radius : node.kind === "jaw" ? 0 : -radius;
|
|
681
|
+
return { id: node.id, material: BODY_MATERIAL, shape: { kind: "sphere", radius }, position: [joint[0], joint[1] + lift, joint[2]], mass, tags: ["body", node.kind] };
|
|
682
|
+
}
|
|
683
|
+
const placed = cylinderBetween(joint, rig.joints[child.joint], radius);
|
|
684
|
+
return { id: node.id, material: BODY_MATERIAL, ...placed, mass, tags: ["body", node.kind] };
|
|
685
|
+
};
|
|
686
|
+
var describeBody = ({ rig, body, build, name }) => {
|
|
687
|
+
const massKg = ADULT_MASS_KG * (rig.height / body.heightMetres) ** 3 * build;
|
|
688
|
+
const parts = SEGMENT_TREE.map((node) => partOf(node, rig, body, massKg));
|
|
689
|
+
const joints = SEGMENT_TREE.filter((node) => node.parent).map((node) => ({ id: `${node.parent}~${node.id}`, a: node.parent, b: node.id, kind: "fused", tools: [] }));
|
|
690
|
+
return { ...name ? { name } : {}, parts, joints, tags: ["character"] };
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/body/object-state.ts
|
|
694
|
+
var PART_STATUS = { ok: "intact", wounded: "damaged", broken: "damaged", severed: "collapsed", destroyed: "broken" };
|
|
695
|
+
var BODY_TEMPERATURE_C = 37;
|
|
696
|
+
var toObjectState = ({ id, name, position, rotation, body, spec }) => {
|
|
697
|
+
const groups = [body.segments.filter((segment) => segment.attached).map((segment) => segment.id)];
|
|
698
|
+
for (const piece of body.pieces) {
|
|
699
|
+
const left = piece.filter((part) => body.segments.find((segment) => segment.id === part)?.status !== "destroyed");
|
|
700
|
+
if (left.length > 0) groups.push(left);
|
|
701
|
+
}
|
|
702
|
+
const groupOf = (part) => groups.findIndex((group) => group.includes(part));
|
|
703
|
+
const parts = body.segments.map((segment) => {
|
|
704
|
+
const mass = spec.parts.find((part) => part.id === segment.id)?.mass ?? 0;
|
|
705
|
+
return {
|
|
706
|
+
id: segment.id,
|
|
707
|
+
material: "flesh",
|
|
708
|
+
status: PART_STATUS[segment.status],
|
|
709
|
+
hp: Math.max(0, segment.hp),
|
|
710
|
+
maxHp: segment.max,
|
|
711
|
+
mass,
|
|
712
|
+
temperature: BODY_TEMPERATURE_C,
|
|
713
|
+
burning: false,
|
|
714
|
+
fuel: 0,
|
|
715
|
+
char: 0,
|
|
716
|
+
melt: 0,
|
|
717
|
+
dents: [],
|
|
718
|
+
holes: [],
|
|
719
|
+
cracks: [],
|
|
720
|
+
group: groupOf(segment.id)
|
|
721
|
+
};
|
|
722
|
+
});
|
|
723
|
+
const joints = (spec.joints ?? []).map((joint) => {
|
|
724
|
+
const child = body.segments.find((segment) => segment.id === joint.b);
|
|
725
|
+
const parent = body.segments.find((segment) => segment.id === joint.a);
|
|
726
|
+
const open = child !== void 0 && parent !== void 0 && parent.attached !== child.attached;
|
|
727
|
+
return { id: joint.id ?? `${joint.a}~${joint.b}`, a: joint.a, b: joint.b, kind: joint.kind, hp: open ? 0 : 1, maxHp: 1, progress: 0, open, ...open ? { openedBy: "break" } : {} };
|
|
728
|
+
});
|
|
729
|
+
return {
|
|
730
|
+
id,
|
|
731
|
+
...name ? { name } : {},
|
|
732
|
+
position: [...position],
|
|
733
|
+
rotation: [...rotation],
|
|
734
|
+
parts,
|
|
735
|
+
joints,
|
|
736
|
+
groups,
|
|
737
|
+
destroyed: false,
|
|
738
|
+
body: { alive: body.alive, vitality: body.vitality, missing: body.detail === "segments" ? body.missing | missingFromSegments(body) : body.missing, limp: body.limping.length > 0, crawl: body.crawl }
|
|
739
|
+
};
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// src/body/rig.ts
|
|
743
|
+
var GAME_BONES = {
|
|
744
|
+
torso: 0,
|
|
745
|
+
head: 1,
|
|
746
|
+
upperArmL: 2,
|
|
747
|
+
upperArmR: 3,
|
|
748
|
+
thighL: 4,
|
|
749
|
+
thighR: 5,
|
|
750
|
+
eyes: 6,
|
|
751
|
+
forearmL: 7,
|
|
752
|
+
forearmR: 8,
|
|
753
|
+
shinL: 9,
|
|
754
|
+
shinR: 10,
|
|
755
|
+
jaw: 11
|
|
756
|
+
};
|
|
757
|
+
var JOINT_PARENTS = {
|
|
758
|
+
hip: void 0,
|
|
759
|
+
spine: "hip",
|
|
760
|
+
neck: "spine",
|
|
761
|
+
head: "neck",
|
|
762
|
+
jaw: "head",
|
|
763
|
+
shoulderL: "spine",
|
|
764
|
+
shoulderR: "spine",
|
|
765
|
+
elbowL: "shoulderL",
|
|
766
|
+
elbowR: "shoulderR",
|
|
767
|
+
wristL: "elbowL",
|
|
768
|
+
wristR: "elbowR",
|
|
769
|
+
hipL: "hip",
|
|
770
|
+
hipR: "hip",
|
|
771
|
+
kneeL: "hipL",
|
|
772
|
+
kneeR: "hipR",
|
|
773
|
+
ankleL: "kneeL",
|
|
774
|
+
ankleR: "kneeR"
|
|
775
|
+
};
|
|
776
|
+
var createRig = ({ body, height, build }) => {
|
|
777
|
+
const scale = height / body.heightMetres;
|
|
778
|
+
const joints = Object.fromEntries(
|
|
779
|
+
JOINT_IDS.map((id) => {
|
|
780
|
+
const [x, y, z] = body.joints[id];
|
|
781
|
+
return [id, [x * scale * build, y * scale, z * scale]];
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
return { joints, parents: JOINT_PARENTS, gameBones: GAME_BONES, height };
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
// src/character.ts
|
|
788
|
+
var DEFAULT_VITALITY = 100;
|
|
789
|
+
var DEFAULT_STUN_SECONDS = 0.45;
|
|
790
|
+
var NO_MULTIPLIERS = { head: 1, torso: 1, arms: 1, legs: 1 };
|
|
791
|
+
var resolveSpec = (spec, body, counter) => ({
|
|
792
|
+
...spec,
|
|
793
|
+
id: spec.id ?? `${spec.kind}_${counter}`,
|
|
794
|
+
height: spec.height ?? body.heightMetres,
|
|
795
|
+
build: spec.build ?? 1,
|
|
796
|
+
vitality: spec.vitality ?? DEFAULT_VITALITY,
|
|
797
|
+
multipliers: { ...NO_MULTIPLIERS, ...spec.multipliers },
|
|
798
|
+
limbFactor: spec.limbFactor ?? 1,
|
|
799
|
+
bloodFactor: spec.bloodFactor ?? 1,
|
|
800
|
+
bodyCount: spec.bodyCount ?? 1,
|
|
801
|
+
stunSeconds: spec.stunSeconds ?? DEFAULT_STUN_SECONDS,
|
|
802
|
+
protection: spec.protection ?? [],
|
|
803
|
+
detail: spec.detail ?? "segments"
|
|
804
|
+
});
|
|
805
|
+
var createCharacterCore = ({ spec, bodySpec, hitPointsOf }) => {
|
|
806
|
+
const rig = createRig({ body: bodySpec, height: spec.height, build: spec.build });
|
|
807
|
+
const parts = describeBody({ rig, body: bodySpec, build: spec.build, name: spec.kind });
|
|
808
|
+
const fresh = () => createBodyState({ vitality: spec.vitality, detail: spec.detail, crawling: spec.crawling ?? false, hitPointsOf: hitPointsOf ?? ((kind) => SEGMENT_NUMBERS[kind].hitPoints) });
|
|
809
|
+
const body = fresh();
|
|
810
|
+
return { spec, rig, body, position: [0, 0, 0], rotation: [0, 0, 0, 1], describe: () => parts, resetBody: () => void Object.assign(body, fresh()) };
|
|
811
|
+
};
|
|
812
|
+
var characterView = (core) => ({
|
|
813
|
+
id: core.spec.id,
|
|
814
|
+
spec: core.spec,
|
|
815
|
+
rig: core.rig,
|
|
816
|
+
get segments() {
|
|
817
|
+
return core.body.segments;
|
|
818
|
+
},
|
|
819
|
+
get detail() {
|
|
820
|
+
return core.body.detail;
|
|
821
|
+
},
|
|
822
|
+
regionHitPoints: () => ({ ...core.body.regions }),
|
|
823
|
+
setDetail(detail) {
|
|
824
|
+
if (detail === core.body.detail) return;
|
|
825
|
+
if (detail === "regions") regionsFromSegments(core.body);
|
|
826
|
+
else segmentsFromRegions(core.body);
|
|
827
|
+
core.body.detail = detail;
|
|
828
|
+
},
|
|
829
|
+
setTransform(position, rotation) {
|
|
830
|
+
core.position = [...position];
|
|
831
|
+
if (rotation) core.rotation = [...rotation];
|
|
832
|
+
},
|
|
833
|
+
describe: core.describe,
|
|
834
|
+
state: () => toObjectState({ id: core.spec.id, name: core.spec.kind, position: core.position, rotation: core.rotation, body: core.body, spec: core.describe() }),
|
|
835
|
+
reset: core.resetBody
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// src/body/transform.ts
|
|
839
|
+
var rotate = (q, v) => {
|
|
840
|
+
const [x, y, z, w] = q;
|
|
841
|
+
const tx = 2 * (y * v[2] - z * v[1]);
|
|
842
|
+
const ty = 2 * (z * v[0] - x * v[2]);
|
|
843
|
+
const tz = 2 * (x * v[1] - y * v[0]);
|
|
844
|
+
return [v[0] + w * tx + (y * tz - z * ty), v[1] + w * ty + (z * tx - x * tz), v[2] + w * tz + (x * ty - y * tx)];
|
|
845
|
+
};
|
|
846
|
+
var conjugate = (q) => [-q[0], -q[1], -q[2], q[3]];
|
|
847
|
+
var toWorld = ({ position, rotation }, local) => {
|
|
848
|
+
const turned = rotate(rotation, local);
|
|
849
|
+
return [turned[0] + position[0], turned[1] + position[1], turned[2] + position[2]];
|
|
850
|
+
};
|
|
851
|
+
var toLocal = ({ position, rotation }, world) => rotate(conjugate(rotation), [world[0] - position[0], world[1] - position[1], world[2] - position[2]]);
|
|
852
|
+
var distance = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
853
|
+
|
|
854
|
+
// src/damage/bleeding.ts
|
|
855
|
+
var BLOOD_LITERS_PER_DROP = 27e-5;
|
|
856
|
+
var bleedRateAt = (bleeder) => bleeder.age >= bleeder.duration ? 0 : bleeder.rate * (1 - bleeder.age / bleeder.duration);
|
|
857
|
+
var stepBleeding = ({ bleeders, dt, emit, pointOf }) => {
|
|
858
|
+
for (const bleeder of bleeders) {
|
|
859
|
+
if (bleeder.age >= bleeder.duration) continue;
|
|
860
|
+
bleeder.age += dt;
|
|
861
|
+
const second = Math.floor(bleeder.age);
|
|
862
|
+
if (second <= bleeder.reported) continue;
|
|
863
|
+
bleeder.reported = second;
|
|
864
|
+
const rate = bleedRateAt(bleeder);
|
|
865
|
+
if (rate > 0) emit({ type: "leak", part: bleeder.part, fluid: "blood", point: pointOf(bleeder.part), direction: [0, -1, 0], rate: rate * BLOOD_LITERS_PER_DROP, pressurized: bleeder.spurt });
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// src/damage/body-ops.ts
|
|
870
|
+
var killAllowed = (rule, cause) => rule === "vitality-or-head" || cause === "head" || cause === "explosion";
|
|
871
|
+
var createBodyOps = ({ body, spec, rules, random, time, source, pointOf, bleeders }) => {
|
|
872
|
+
const events = [];
|
|
873
|
+
const emit = (event) => void events.push({ ...event, object: spec.id, time, ...source !== void 0 ? { source } : {} });
|
|
874
|
+
const change = (kind, part, extra) => emit({ type: "body", change: kind, ...part ? { part } : {}, ...extra });
|
|
875
|
+
const detachBelow = (segment, below2) => {
|
|
876
|
+
const fallen = segmentsBelow(segment.id).filter((id) => segmentOf(body, id).attached);
|
|
877
|
+
segment.attached = false;
|
|
878
|
+
for (const id of fallen) {
|
|
879
|
+
const part = segmentOf(body, id);
|
|
880
|
+
part.attached = false;
|
|
881
|
+
if (below2 === "zero") Object.assign(part, { hp: 0, status: "severed" });
|
|
882
|
+
}
|
|
883
|
+
return fallen;
|
|
884
|
+
};
|
|
885
|
+
const ops = {
|
|
886
|
+
body,
|
|
887
|
+
spec,
|
|
888
|
+
rules,
|
|
889
|
+
random,
|
|
890
|
+
events,
|
|
891
|
+
emit,
|
|
892
|
+
pointOf,
|
|
893
|
+
change,
|
|
894
|
+
damage: (segment, amount, damageType) => emit({ type: "damage", part: segment.id, amount, hp: Math.max(0, segment.hp), damageType }),
|
|
895
|
+
wound(segment) {
|
|
896
|
+
if (segment.status === "ok") segment.status = "wounded";
|
|
897
|
+
change("wound", segment.id);
|
|
898
|
+
},
|
|
899
|
+
breakBone(segment) {
|
|
900
|
+
segment.status = "broken";
|
|
901
|
+
change("broken", segment.id);
|
|
902
|
+
},
|
|
903
|
+
sever(segment, below2 = "detach") {
|
|
904
|
+
if (!segment.attached) return;
|
|
905
|
+
const fallen = detachBelow(segment, below2);
|
|
906
|
+
segment.status = "severed";
|
|
907
|
+
body.pieces.push([segment.id, ...fallen]);
|
|
908
|
+
const parent = segmentNode(segment.id).parent;
|
|
909
|
+
change("severed", segment.id, { below: fallen });
|
|
910
|
+
if (parent) emit({ type: "detach", joint: `${parent}~${segment.id}`, parts: [parent, segment.id], by: "break", groups: [[segment.id, ...fallen]] });
|
|
911
|
+
},
|
|
912
|
+
destroy(segment) {
|
|
913
|
+
if (!segment.attached) return;
|
|
914
|
+
const fallen = detachBelow(segment, "detach");
|
|
915
|
+
segment.status = "destroyed";
|
|
916
|
+
if (fallen.length > 0) body.pieces.push(fallen);
|
|
917
|
+
change("destroyed", segment.id, { below: fallen });
|
|
918
|
+
emit({ type: "fracture", part: segment.id, style: "tear", fragments: [], point: pointOf(segment.id) });
|
|
919
|
+
},
|
|
920
|
+
kill(cause) {
|
|
921
|
+
if (!body.alive || !killAllowed(rules.deathRule, cause)) return;
|
|
922
|
+
body.alive = false;
|
|
923
|
+
change("killed", void 0, { cause });
|
|
924
|
+
},
|
|
925
|
+
limp(side) {
|
|
926
|
+
if (body.limping.includes(side)) return;
|
|
927
|
+
body.limping.push(side);
|
|
928
|
+
change("limp");
|
|
929
|
+
},
|
|
930
|
+
crawl() {
|
|
931
|
+
if (body.crawl) return;
|
|
932
|
+
body.crawl = true;
|
|
933
|
+
change("crawl");
|
|
934
|
+
},
|
|
935
|
+
stun() {
|
|
936
|
+
body.stunSeconds = spec.stunSeconds;
|
|
937
|
+
change("stunned");
|
|
938
|
+
},
|
|
939
|
+
bleed({ segment, rate, duration, spurt, point, direction }) {
|
|
940
|
+
const scaled = rate * spec.bloodFactor;
|
|
941
|
+
bleeders.push({ part: segment.id, rate: scaled, duration, spurt, age: 0, reported: 0 });
|
|
942
|
+
emit({ type: "leak", part: segment.id, fluid: "blood", point: point ?? pointOf(segment.id), direction: direction ?? [0, -1, 0], rate: scaled * BLOOD_LITERS_PER_DROP, pressurized: spurt });
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
return ops;
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
// src/presets/physical.json
|
|
949
|
+
var physical_default = {
|
|
950
|
+
id: "physical",
|
|
951
|
+
tissuePerCentimetre: {
|
|
952
|
+
flesh: 0.25,
|
|
953
|
+
bone: 1
|
|
954
|
+
},
|
|
955
|
+
torsoPath: {
|
|
956
|
+
fleshCentimetres: 22,
|
|
957
|
+
boneCentimetres: 2
|
|
958
|
+
},
|
|
959
|
+
regionFactor: {
|
|
960
|
+
head: 0.7,
|
|
961
|
+
limb: 0.5,
|
|
962
|
+
torso: 1
|
|
963
|
+
},
|
|
964
|
+
bruteTorsoFactor: 2,
|
|
965
|
+
energyKept: "P / (P + R)",
|
|
966
|
+
points: {
|
|
967
|
+
coefficient: 3.5,
|
|
968
|
+
exponent: 0.37
|
|
969
|
+
},
|
|
970
|
+
tolerance: {
|
|
971
|
+
bodies: 0,
|
|
972
|
+
energyKept: 0.05
|
|
973
|
+
},
|
|
974
|
+
calibration: [
|
|
975
|
+
{
|
|
976
|
+
caliber: "9x19",
|
|
977
|
+
force: 7.67,
|
|
978
|
+
bodies: 2,
|
|
979
|
+
energyKeptFirstBody: 0.51,
|
|
980
|
+
expectedBodies: 2,
|
|
981
|
+
bestiaryEnergyKept: 0.5
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
caliber: "5_56x45",
|
|
985
|
+
force: 16.56,
|
|
986
|
+
bodies: 3,
|
|
987
|
+
energyKeptFirstBody: 0.69,
|
|
988
|
+
expectedBodies: 3,
|
|
989
|
+
bestiaryEnergyKept: 0.65
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
caliber: "7_62x51",
|
|
993
|
+
force: 26.67,
|
|
994
|
+
bodies: 4,
|
|
995
|
+
energyKeptFirstBody: 0.78,
|
|
996
|
+
expectedBodies: 4,
|
|
997
|
+
bestiaryEnergyKept: 0.75
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
caliber: "7_62x39",
|
|
1001
|
+
force: 18.52,
|
|
1002
|
+
bodies: 3,
|
|
1003
|
+
energyKeptFirstBody: 0.71,
|
|
1004
|
+
expectedBodies: 3
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
caliber: "12ga_00buck",
|
|
1008
|
+
force: 4.35,
|
|
1009
|
+
bodies: 1,
|
|
1010
|
+
energyKeptFirstBody: 0.37,
|
|
1011
|
+
expectedBodies: 1
|
|
1012
|
+
}
|
|
1013
|
+
],
|
|
1014
|
+
torsoResistance: 7.5
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
// src/damage/defaults.ts
|
|
1018
|
+
var { severity } = segments_default;
|
|
1019
|
+
var DEFAULT_DAMAGE_RULES = {
|
|
1020
|
+
deathRule: "vitality-or-head",
|
|
1021
|
+
segmentRule: "kit",
|
|
1022
|
+
severity: { ...severity, stunAtPoints: 35, limbVitalityShare: 0.3, regionLimbVitalityShare: 0.5 },
|
|
1023
|
+
blast: { minimumPoints: 8, headDestroyPoints: 40, bigPoints: 120 },
|
|
1024
|
+
defaultCut: segments_default.defaultCut,
|
|
1025
|
+
points: physical_default.points,
|
|
1026
|
+
spread: 0,
|
|
1027
|
+
segments: SEGMENT_NUMBERS,
|
|
1028
|
+
burnPointsPerSecond: 3
|
|
1029
|
+
};
|
|
1030
|
+
var ARSENAL_RULES = { ...DEFAULT_DAMAGE_RULES, segmentRule: "arsenal" };
|
|
1031
|
+
var ELENCO_HIT_POINTS = segments_default.elenco.segments;
|
|
1032
|
+
var ELENCO_RULES = {
|
|
1033
|
+
...DEFAULT_DAMAGE_RULES,
|
|
1034
|
+
segmentRule: "elenco",
|
|
1035
|
+
spread: 0.15,
|
|
1036
|
+
segments: Object.fromEntries(
|
|
1037
|
+
Object.entries(SEGMENT_NUMBERS).map(([kind, numbers]) => [kind, { ...numbers, multiplier: 1, hitPoints: ELENCO_HIT_POINTS[kind]?.hp ?? numbers.hitPoints }])
|
|
1038
|
+
)
|
|
1039
|
+
};
|
|
1040
|
+
var DAMAGE_PRESETS = { kit: DEFAULT_DAMAGE_RULES, arsenal: ARSENAL_RULES, elenco: ELENCO_RULES };
|
|
1041
|
+
var PROTECTION_PRESETS = [
|
|
1042
|
+
{ id: "vest_police", slots: ["torso"], ballistic: { resistance: 14, blunt: 0.3, pass: 0.8 } },
|
|
1043
|
+
{ id: "vest_military", slots: ["torso"], ballistic: { resistance: 28, blunt: 0.5 } },
|
|
1044
|
+
{ id: "helmet_combat", slots: ["head"], ballistic: { resistance: 9, blunt: 0, pass: 1, consumable: true } },
|
|
1045
|
+
{ id: "turnout_gear", slots: ["torso", "arms", "legs"], fire: 0.25, limbs: 0.8 }
|
|
1046
|
+
];
|
|
1047
|
+
var CLASS_OF = { pistol: "pistol", rifle: "rifle" };
|
|
1048
|
+
var TEAM_GAME_CALIBERS = Object.entries(team_game_default.calibers).map(([id, entry]) => ({
|
|
1049
|
+
id,
|
|
1050
|
+
points: entry.points,
|
|
1051
|
+
penetration: entry.penetration,
|
|
1052
|
+
class: CLASS_OF[entry.class] ?? "pistol",
|
|
1053
|
+
skullRange: entry.skullRange,
|
|
1054
|
+
pellets: "pellets" in entry ? entry.pellets : 1,
|
|
1055
|
+
effectiveRangeMetres: entry.effectiveRangeMetres
|
|
1056
|
+
}));
|
|
1057
|
+
var TEAM_GAME_CALIBER_KEYS = Object.fromEntries(Object.entries(team_game_default.calibers).map(([id, entry]) => [entry.gameKey, id]));
|
|
1058
|
+
var TEAM_GAME_RULES = { regionCost: team_game_default.regionCost, damageKeptPerBody: team_game_default.damageKeptPerBody, falloff: team_game_default.falloff };
|
|
1059
|
+
var PHYSICAL_RULES = physical_default;
|
|
1060
|
+
|
|
1061
|
+
// src/damage/penetration.ts
|
|
1062
|
+
var PHYSICAL_PENETRATION = {
|
|
1063
|
+
id: "physical",
|
|
1064
|
+
stops: (layer, projectile) => projectile.force <= (layer.ballistic?.resistance ?? 0),
|
|
1065
|
+
piercesSkull: (projectile, headResistance) => projectile.force > headResistance
|
|
1066
|
+
};
|
|
1067
|
+
var TEAM_GAME_PENETRATION = {
|
|
1068
|
+
id: "team-game",
|
|
1069
|
+
stops: (_layer, projectile) => projectile.class === "pistol",
|
|
1070
|
+
piercesSkull: (projectile) => projectile.skullRange === 1 || projectile.distance <= projectile.skullRange
|
|
1071
|
+
};
|
|
1072
|
+
var PENETRATION_MODELS = { physical: PHYSICAL_PENETRATION, "team-game": TEAM_GAME_PENETRATION };
|
|
1073
|
+
var { tissuePerCentimetre, torsoPath, regionFactor } = PHYSICAL_RULES;
|
|
1074
|
+
var TORSO_RESISTANCE = torsoPath.fleshCentimetres * tissuePerCentimetre.flesh + torsoPath.boneCentimetres * tissuePerCentimetre.bone;
|
|
1075
|
+
var HEAD_KINDS = /* @__PURE__ */ new Set(["head", "jaw", "neck"]);
|
|
1076
|
+
var TORSO_KINDS = /* @__PURE__ */ new Set(["torso", "pelvis"]);
|
|
1077
|
+
var segmentResistance = ({ kind, bodyCount, protection }) => {
|
|
1078
|
+
if (TORSO_KINDS.has(kind)) return TORSO_RESISTANCE * regionFactor.torso * bodyCount + protection;
|
|
1079
|
+
if (HEAD_KINDS.has(kind)) return TORSO_RESISTANCE * regionFactor.head + protection;
|
|
1080
|
+
return TORSO_RESISTANCE * regionFactor.limb + protection;
|
|
1081
|
+
};
|
|
1082
|
+
var energyKept = (force, resistance) => force > 0 ? force / (force + resistance) : 0;
|
|
1083
|
+
var teamGameCost = (region, bodyCount) => {
|
|
1084
|
+
const cost = TEAM_GAME_RULES.regionCost;
|
|
1085
|
+
if (region === "head") return cost.head;
|
|
1086
|
+
if (region === "torso") return bodyCount > 1 ? cost.bruteTorso : cost.torso;
|
|
1087
|
+
return cost.limb;
|
|
1088
|
+
};
|
|
1089
|
+
var teamGameFalloff = (distance2, range) => {
|
|
1090
|
+
const floor = TEAM_GAME_RULES.falloff.atTwiceRange;
|
|
1091
|
+
return distance2 <= range * TEAM_GAME_RULES.falloff.fullAtRange ? 1 : Math.max(floor, 1 - (distance2 - range) / range * (1 - floor));
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
// src/damage/rules/blow.ts
|
|
1095
|
+
var NO_HEADSHOT = { headshot: false };
|
|
1096
|
+
var HEADSHOT = { headshot: true };
|
|
1097
|
+
var ARM_KINDS = /* @__PURE__ */ new Set(["upperArm", "forearm", "hand"]);
|
|
1098
|
+
var LEG_KINDS = /* @__PURE__ */ new Set(["thigh", "shin", "foot"]);
|
|
1099
|
+
var isArm = (kind) => ARM_KINDS.has(kind);
|
|
1100
|
+
var isLeg = (kind) => LEG_KINDS.has(kind);
|
|
1101
|
+
var isLimb = (kind) => isArm(kind) || isLeg(kind);
|
|
1102
|
+
var typeMultiplier = (spec, kind) => {
|
|
1103
|
+
if (isArm(kind)) return spec.multipliers.arms;
|
|
1104
|
+
if (isLeg(kind)) return spec.multipliers.legs;
|
|
1105
|
+
if (kind === "torso" || kind === "pelvis") return spec.multipliers.torso;
|
|
1106
|
+
return spec.multipliers.head;
|
|
1107
|
+
};
|
|
1108
|
+
var segmentDamage = (ops, blow, points) => {
|
|
1109
|
+
const { kind } = blow.segment;
|
|
1110
|
+
return points * ops.rules.segments[kind].multiplier * typeMultiplier(ops.spec, kind) * (isLimb(kind) ? blow.limbFactor : 1);
|
|
1111
|
+
};
|
|
1112
|
+
var bleedStump = (ops, segment) => {
|
|
1113
|
+
const bleed = ops.rules.segments[segment.kind].bleed;
|
|
1114
|
+
const parent = segmentNode(segment.id).parent;
|
|
1115
|
+
const stump = parent ? ops.body.segments.find((candidate) => candidate.id === parent) ?? segment : segment;
|
|
1116
|
+
ops.bleed({ segment: stump, rate: bleed * (segment.kind === "neck" || segment.kind === "head" ? 1.5 : 1), duration: 45, spurt: true });
|
|
1117
|
+
};
|
|
1118
|
+
var bleedPiece = (ops, segment) => ops.bleed({ segment, rate: ops.rules.segments[segment.kind].bleed * 0.35, duration: 15, spurt: true });
|
|
1119
|
+
var bleedWound = (ops, segment, point) => ops.bleed({ segment, rate: ops.rules.segments[segment.kind].bleed * 0.3, duration: 10, spurt: false, point });
|
|
1120
|
+
var severWithBleeding = (ops, segment) => {
|
|
1121
|
+
if (!segment.attached) return;
|
|
1122
|
+
bleedStump(ops, segment);
|
|
1123
|
+
ops.sever(segment);
|
|
1124
|
+
bleedPiece(ops, segment);
|
|
1125
|
+
};
|
|
1126
|
+
var destroyWithBleeding = (ops, segment) => {
|
|
1127
|
+
if (!segment.attached) return;
|
|
1128
|
+
bleedStump(ops, segment);
|
|
1129
|
+
ops.destroy(segment);
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
// src/damage/rules/regions.ts
|
|
1133
|
+
import { MISSING_BITS as MISSING_BITS2 } from "@adatechnology/game-contracts";
|
|
1134
|
+
|
|
1135
|
+
// src/damage/points.ts
|
|
1136
|
+
var createRng = (seed) => {
|
|
1137
|
+
let state = seed >>> 0;
|
|
1138
|
+
return () => {
|
|
1139
|
+
state = state + 1831565813 >>> 0;
|
|
1140
|
+
let mixed = Math.imul(state ^ state >>> 15, state | 1);
|
|
1141
|
+
mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
|
|
1142
|
+
return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
|
|
1143
|
+
};
|
|
1144
|
+
};
|
|
1145
|
+
var pointsFromEnergy = (energy, rules) => energy > 0 ? rules.points.coefficient * energy ** rules.points.exponent : 0;
|
|
1146
|
+
var resolvePoints = (hit, caliber, rules) => hit.points ?? caliber?.points ?? pointsFromEnergy(hit.energy ?? 0, rules);
|
|
1147
|
+
var spreadPoints = (points, spread, random) => spread > 0 ? points * (1 - spread + random() * 2 * spread) : points;
|
|
1148
|
+
var DEFAULT_PROJECTILE_GRAMS = 8;
|
|
1149
|
+
var RIFLE_JOULES = 1e3;
|
|
1150
|
+
var penetrationForce = (energy, grams, diameterMm) => {
|
|
1151
|
+
const area = Math.PI * diameterMm ** 2 / 4;
|
|
1152
|
+
return area > 0 && energy > 0 ? Math.sqrt(energy * grams / area) : 0;
|
|
1153
|
+
};
|
|
1154
|
+
var projectileOf = (hit, caliber) => {
|
|
1155
|
+
const pellets = hit.pellets ?? caliber?.pellets ?? 1;
|
|
1156
|
+
const energy = hit.energy ?? 0;
|
|
1157
|
+
const fallback = energy > RIFLE_JOULES ? "rifle" : "pistol";
|
|
1158
|
+
return {
|
|
1159
|
+
force: penetrationForce(energy, hit.mass ?? DEFAULT_PROJECTILE_GRAMS, hit.caliber ?? 9),
|
|
1160
|
+
class: caliber?.class ?? fallback,
|
|
1161
|
+
skullRange: caliber?.skullRange ?? 1,
|
|
1162
|
+
pellets,
|
|
1163
|
+
distance: hit.distance ?? 0
|
|
1164
|
+
};
|
|
1165
|
+
};
|
|
1166
|
+
var cutOf = (hit, projectile, rules) => {
|
|
1167
|
+
if (hit.cut !== void 0) return hit.cut;
|
|
1168
|
+
const type = hit.type;
|
|
1169
|
+
if (type === "ballistic" && projectile) return rules.defaultCut.ballistic[projectile.pellets > 1 ? "buckshot" : projectile.class];
|
|
1170
|
+
if (type === "slash" || type === "blunt" || type === "explosive") return rules.defaultCut[type];
|
|
1171
|
+
return 0;
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// src/damage/rules/regions.ts
|
|
1175
|
+
var LIMB_BITS = {
|
|
1176
|
+
armL: { severed: MISSING_BITS2.armL, broken: MISSING_BITS2.armLBroken },
|
|
1177
|
+
armR: { severed: MISSING_BITS2.armR, broken: MISSING_BITS2.armRBroken },
|
|
1178
|
+
legL: { severed: MISSING_BITS2.legL, broken: MISSING_BITS2.legLBroken },
|
|
1179
|
+
legR: { severed: MISSING_BITS2.legR, broken: MISSING_BITS2.legRBroken }
|
|
1180
|
+
};
|
|
1181
|
+
var rootOf = (ops, region) => segmentOf(ops.body, REGION_SEGMENTS[region][0]);
|
|
1182
|
+
var hitHead = (ops, blow, points) => {
|
|
1183
|
+
const { body, rules } = ops;
|
|
1184
|
+
const rule = rules.severity;
|
|
1185
|
+
const multiplier = rules.segments.head.multiplier;
|
|
1186
|
+
const { projectile } = blow;
|
|
1187
|
+
const lethal = projectile ? blow.model.piercesSkull(projectile, blow.headResistance) && points >= rule.headKillDamage : points * multiplier >= body.regions.head;
|
|
1188
|
+
if (lethal) {
|
|
1189
|
+
const shotgunClose = projectile !== void 0 && projectile.pellets > 1 && projectile.distance < rule.headBitShotgunMetres;
|
|
1190
|
+
if (points > rule.headBitDamage || shotgunClose) {
|
|
1191
|
+
body.missing |= MISSING_BITS2.head;
|
|
1192
|
+
ops.destroy(segmentOf(body, "head"));
|
|
1193
|
+
}
|
|
1194
|
+
return HEADSHOT;
|
|
1195
|
+
}
|
|
1196
|
+
body.vitality -= points * multiplier;
|
|
1197
|
+
body.regions.head -= points;
|
|
1198
|
+
ops.damage(segmentOf(body, "head"), points * multiplier, blow.damageType);
|
|
1199
|
+
if (points > rule.jawDamage && ops.random() < rule.jawChance && (body.missing & MISSING_BITS2.jaw) === 0) {
|
|
1200
|
+
body.missing |= MISSING_BITS2.jaw;
|
|
1201
|
+
ops.sever(segmentOf(body, "jaw"));
|
|
1202
|
+
}
|
|
1203
|
+
return NO_HEADSHOT;
|
|
1204
|
+
};
|
|
1205
|
+
var hitLimb = (ops, blow, region, points) => {
|
|
1206
|
+
const { body, spec, rules } = ops;
|
|
1207
|
+
const leg = region === "legL" || region === "legR";
|
|
1208
|
+
const factor = (leg ? spec.multipliers.legs : spec.multipliers.arms) * spec.limbFactor * blow.protection.limbs;
|
|
1209
|
+
const amount = points * factor;
|
|
1210
|
+
body.regions[region] -= amount;
|
|
1211
|
+
body.vitality -= amount * rules.severity.regionLimbVitalityShare;
|
|
1212
|
+
const root = rootOf(ops, region);
|
|
1213
|
+
ops.damage(root, amount, blow.damageType);
|
|
1214
|
+
const max = REGION_HIT_POINTS[region];
|
|
1215
|
+
if (leg && body.regions[region] <= max * rules.severity.limpAtHpFraction) ops.limp(region === "legL" ? "L" : "R");
|
|
1216
|
+
if (body.regions[region] > 0) return;
|
|
1217
|
+
const severs = blow.cut >= rules.severity.severAtCut || amount > max * rules.severity.severAtDamageFraction;
|
|
1218
|
+
const bit = severs ? LIMB_BITS[region].severed : LIMB_BITS[region].broken;
|
|
1219
|
+
const isNew = (body.missing & bit) === 0;
|
|
1220
|
+
body.missing |= bit;
|
|
1221
|
+
if (leg) ops.crawl();
|
|
1222
|
+
if (!isNew) return;
|
|
1223
|
+
if (severs) ops.sever(root);
|
|
1224
|
+
else ops.breakBone(root);
|
|
1225
|
+
};
|
|
1226
|
+
var regionRule = (ops, blow) => {
|
|
1227
|
+
const { body, rules } = ops;
|
|
1228
|
+
const points = spreadPoints(blow.points, rules.spread, ops.random);
|
|
1229
|
+
let result = NO_HEADSHOT;
|
|
1230
|
+
if (blow.region === "head") result = hitHead(ops, blow, points);
|
|
1231
|
+
else if (blow.region === "torso") {
|
|
1232
|
+
const amount = points * ops.spec.multipliers.torso * blow.protection.ballistic;
|
|
1233
|
+
body.vitality -= amount;
|
|
1234
|
+
ops.damage(segmentOf(body, "torso"), amount, blow.damageType);
|
|
1235
|
+
} else hitLimb(ops, blow, blow.region, points);
|
|
1236
|
+
if (result.headshot) ops.kill("head");
|
|
1237
|
+
else if (body.vitality <= 0) ops.kill("vitality");
|
|
1238
|
+
if (!body.alive || result.headshot || body.vitality <= 0) return result;
|
|
1239
|
+
if (points >= rules.severity.stunAtPoints) ops.stun();
|
|
1240
|
+
return result;
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
// src/damage/blast.ts
|
|
1244
|
+
var shares = (params, targets, pivot) => targets.map((target) => ({ target, points: params.points * Math.max(0, 1 - distance(params.jointOf(pivot(target)), params.center) / params.radius) })).filter((share) => share.points > params.ops.rules.blast.minimumPoints).sort((a, b) => b.points - a.points);
|
|
1245
|
+
var segmentBlast = (params) => {
|
|
1246
|
+
const { ops, rule, blow } = params;
|
|
1247
|
+
const { blast: blast2, segments } = ops.rules;
|
|
1248
|
+
let result = NO_HEADSHOT;
|
|
1249
|
+
for (const { target, points } of shares(params, ops.body.segments.filter((segment) => segment.attached), (segment) => segment.id)) {
|
|
1250
|
+
if (!target.attached) continue;
|
|
1251
|
+
if (target.kind === "head" && points > blast2.headDestroyPoints) {
|
|
1252
|
+
destroyWithBleeding(ops, target);
|
|
1253
|
+
ops.kill("explosion");
|
|
1254
|
+
result = HEADSHOT;
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
rule(ops, { ...blow, segment: target, points: points / segments[target.kind].multiplier, big: points > blast2.bigPoints, explosive: true });
|
|
1258
|
+
}
|
|
1259
|
+
return result;
|
|
1260
|
+
};
|
|
1261
|
+
var regionBlast = (params) => {
|
|
1262
|
+
let result = NO_HEADSHOT;
|
|
1263
|
+
const pivot = (region) => segmentNode(REGION_SEGMENTS[region][0]).id;
|
|
1264
|
+
for (const { target, points } of shares(params, BODY_REGIONS, pivot)) {
|
|
1265
|
+
if (regionRule(params.ops, { ...params.blow, region: target, points }).headshot) result = HEADSHOT;
|
|
1266
|
+
}
|
|
1267
|
+
return result;
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/damage/protection.ts
|
|
1271
|
+
var LAYERED_PROTECTION_CAP = 0.9;
|
|
1272
|
+
var layeredProtection = ({ layers, attack, conditions }) => {
|
|
1273
|
+
const through = layers.reduce((left, layer) => left * (1 - (layer[attack] ?? 0) * (conditions?.[layer.id] ?? 1)), 1);
|
|
1274
|
+
return Math.min(LAYERED_PROTECTION_CAP, 1 - through);
|
|
1275
|
+
};
|
|
1276
|
+
var layersOn = (worn, slots, consumed) => worn.filter((layer) => !consumed.has(layer.id) && layer.slots.some((slot) => slots.includes(slot)));
|
|
1277
|
+
var protect = ({ layers, projectile, model }) => {
|
|
1278
|
+
let ballistic = 1;
|
|
1279
|
+
let resistance = 0;
|
|
1280
|
+
for (const layer of layers) {
|
|
1281
|
+
const armour = layer.ballistic;
|
|
1282
|
+
if (!armour || !projectile) continue;
|
|
1283
|
+
const stops = model.stops(layer, projectile);
|
|
1284
|
+
if (stops && armour.consumable) return { absorbedBy: layer.id, ballistic: 0, resistance: armour.resistance, limbs: 1, fire: 1 };
|
|
1285
|
+
ballistic *= stops ? armour.blunt : armour.pass ?? armour.blunt;
|
|
1286
|
+
resistance += armour.resistance;
|
|
1287
|
+
}
|
|
1288
|
+
const limbs = layers.reduce((factor, layer) => factor * (layer.limbs ?? 1), 1);
|
|
1289
|
+
const fire = layers.reduce((factor, layer) => factor * (layer.fire ?? 1), 1);
|
|
1290
|
+
return { ballistic, resistance, limbs, fire };
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
// src/damage/resolve-part.ts
|
|
1294
|
+
var radiusOf = (part) => part.shape.kind === "box" ? Math.min(...part.shape.size) / 2 : part.shape.radius;
|
|
1295
|
+
var resolveSegment = (body, spec, part, localPoint) => {
|
|
1296
|
+
if (part !== void 0 && isSegmentId(part)) return part;
|
|
1297
|
+
if (part !== void 0 && isBodyRegion(part)) return REGION_SEGMENTS[part][0];
|
|
1298
|
+
let best = "torso";
|
|
1299
|
+
let bestGap = Infinity;
|
|
1300
|
+
for (const candidate of spec.parts) {
|
|
1301
|
+
const segment = body.segments.find((entry) => entry.id === candidate.id);
|
|
1302
|
+
if (!segment?.attached) continue;
|
|
1303
|
+
const gap = distance(candidate.position, localPoint) - radiusOf(candidate);
|
|
1304
|
+
if (gap < bestGap) [best, bestGap] = [segment.id, gap];
|
|
1305
|
+
}
|
|
1306
|
+
return best;
|
|
1307
|
+
};
|
|
1308
|
+
var regionOfSegment = (body, id) => body.segments.find((segment) => segment.id === id)?.region ?? "torso";
|
|
1309
|
+
|
|
1310
|
+
// src/damage/rules/arsenal.ts
|
|
1311
|
+
var HEAD_BLOW_POINTS = 30;
|
|
1312
|
+
var LIMB_VITALITY_SHARE = 0.3;
|
|
1313
|
+
var split = (ops) => {
|
|
1314
|
+
const torso = segmentOf(ops.body, "torso");
|
|
1315
|
+
if (!torso.attached) return;
|
|
1316
|
+
severWithBleeding(ops, torso);
|
|
1317
|
+
ops.change("split", "torso");
|
|
1318
|
+
ops.kill("vitality");
|
|
1319
|
+
};
|
|
1320
|
+
var loseLimb = (ops, segment, blow) => {
|
|
1321
|
+
if (blow.big || -segment.hp > segment.max) destroyWithBleeding(ops, segment);
|
|
1322
|
+
else severWithBleeding(ops, segment);
|
|
1323
|
+
const { body } = ops;
|
|
1324
|
+
if (segment.kind === "thigh" || segment.kind === "shin") {
|
|
1325
|
+
if (body.alive) ops.crawl();
|
|
1326
|
+
} else if (segment.kind === "foot" && body.alive && !body.crawl && body.limping.length === 0 && segment.side) ops.limp(segment.side);
|
|
1327
|
+
};
|
|
1328
|
+
var arsenalRule = (ops, blow) => {
|
|
1329
|
+
const { segment } = blow;
|
|
1330
|
+
if (!segment.attached) return NO_HEADSHOT;
|
|
1331
|
+
const amount = segmentDamage(ops, blow, blow.points);
|
|
1332
|
+
if (blow.point) bleedWound(ops, segment, blow.point);
|
|
1333
|
+
segment.hp -= amount;
|
|
1334
|
+
ops.damage(segment, amount, blow.damageType);
|
|
1335
|
+
let result = NO_HEADSHOT;
|
|
1336
|
+
if (segment.kind === "head") {
|
|
1337
|
+
if (blow.points >= HEAD_BLOW_POINTS || segment.hp <= 0) {
|
|
1338
|
+
destroyWithBleeding(ops, segment);
|
|
1339
|
+
ops.kill("head");
|
|
1340
|
+
result = HEADSHOT;
|
|
1341
|
+
} else ops.wound(segment);
|
|
1342
|
+
} else if (segment.kind === "jaw") {
|
|
1343
|
+
if (segment.hp <= 0) severWithBleeding(ops, segment);
|
|
1344
|
+
} else if (segment.kind === "neck") {
|
|
1345
|
+
if (segment.hp <= 0) {
|
|
1346
|
+
severWithBleeding(ops, segment);
|
|
1347
|
+
ops.kill("head");
|
|
1348
|
+
}
|
|
1349
|
+
} else if (segment.kind === "torso" || segment.kind === "pelvis") {
|
|
1350
|
+
ops.body.vitality -= amount;
|
|
1351
|
+
if (blow.points >= ops.rules.severity.splitAtDamage || segment.hp <= 0) split(ops);
|
|
1352
|
+
} else {
|
|
1353
|
+
ops.body.vitality -= amount * LIMB_VITALITY_SHARE;
|
|
1354
|
+
if (segment.hp <= 0) loseLimb(ops, segment, blow);
|
|
1355
|
+
}
|
|
1356
|
+
if (ops.body.alive && ops.body.vitality <= 0) ops.kill("vitality");
|
|
1357
|
+
return result;
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
// src/damage/rules/elenco.ts
|
|
1361
|
+
var HEAD_SEVER_CUT = 0.7;
|
|
1362
|
+
var HEAD_SEVER_POINTS = 90;
|
|
1363
|
+
var JOINTED = /* @__PURE__ */ new Set(["head", "upperArm", "forearm", "hand", "thigh", "shin", "foot"]);
|
|
1364
|
+
var elencoRule = (ops, blow) => {
|
|
1365
|
+
const { segment } = blow;
|
|
1366
|
+
const { body, rules } = ops;
|
|
1367
|
+
if (!segment.attached || !body.alive && segment.kind !== "head") return NO_HEADSHOT;
|
|
1368
|
+
const amount = segmentDamage(ops, blow, spreadPoints(blow.points, rules.spread, ops.random));
|
|
1369
|
+
segment.hp = Math.max(0, segment.hp - amount);
|
|
1370
|
+
ops.damage(segment, amount, blow.damageType);
|
|
1371
|
+
if (segment.hp > 0) {
|
|
1372
|
+
ops.wound(segment);
|
|
1373
|
+
return NO_HEADSHOT;
|
|
1374
|
+
}
|
|
1375
|
+
if (segment.kind === "head") {
|
|
1376
|
+
if (blow.cut >= HEAD_SEVER_CUT || blow.points >= HEAD_SEVER_POINTS) ops.sever(segment, "zero");
|
|
1377
|
+
else ops.breakBone(segment);
|
|
1378
|
+
ops.kill("head");
|
|
1379
|
+
return HEADSHOT;
|
|
1380
|
+
}
|
|
1381
|
+
if (JOINTED.has(segment.kind)) {
|
|
1382
|
+
if (blow.cut >= rules.severity.severAtCut || amount >= segment.max * rules.severity.severAtDamageFraction) {
|
|
1383
|
+
ops.sever(segment, "zero");
|
|
1384
|
+
if (isLeg(segment.kind)) ops.crawl();
|
|
1385
|
+
return NO_HEADSHOT;
|
|
1386
|
+
}
|
|
1387
|
+
ops.breakBone(segment);
|
|
1388
|
+
if (isLeg(segment.kind) && segment.side) {
|
|
1389
|
+
ops.limp(segment.side);
|
|
1390
|
+
if (body.limping.length === 2) ops.crawl();
|
|
1391
|
+
}
|
|
1392
|
+
return NO_HEADSHOT;
|
|
1393
|
+
}
|
|
1394
|
+
ops.breakBone(segment);
|
|
1395
|
+
if (segment.kind === "torso") ops.kill("vitality");
|
|
1396
|
+
else if (segment.kind === "pelvis") ops.crawl();
|
|
1397
|
+
else if (segment.kind === "neck") ops.kill("head");
|
|
1398
|
+
return NO_HEADSHOT;
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// src/damage/rules/kit.ts
|
|
1402
|
+
var severityOf = (ops, blow, amount, max) => {
|
|
1403
|
+
const rule = ops.rules.severity;
|
|
1404
|
+
if (blow.explosive || amount >= max * rule.destroyAtDamageMultiple) return "destroyed";
|
|
1405
|
+
if (blow.cut >= rule.severAtCut || amount > max * rule.severAtDamageFraction) return "severed";
|
|
1406
|
+
return "broken";
|
|
1407
|
+
};
|
|
1408
|
+
var lose = (ops, segment, outcome) => {
|
|
1409
|
+
if (outcome === "destroyed") destroyWithBleeding(ops, segment);
|
|
1410
|
+
else if (outcome === "severed") severWithBleeding(ops, segment);
|
|
1411
|
+
else ops.breakBone(segment);
|
|
1412
|
+
};
|
|
1413
|
+
var hitHead2 = (ops, blow, points, amount, hpBefore) => {
|
|
1414
|
+
const { segment, projectile } = blow;
|
|
1415
|
+
const rule = ops.rules.severity;
|
|
1416
|
+
const lethal = projectile ? blow.model.piercesSkull(projectile, blow.headResistance) && points >= rule.headKillDamage : amount >= hpBefore;
|
|
1417
|
+
if (lethal) {
|
|
1418
|
+
const shotgunClose = projectile !== void 0 && projectile.pellets > 1 && projectile.distance < rule.headBitShotgunMetres;
|
|
1419
|
+
if (blow.explosive || points > rule.headBitDamage || shotgunClose) destroyWithBleeding(ops, segment);
|
|
1420
|
+
else ops.breakBone(segment);
|
|
1421
|
+
ops.kill("head");
|
|
1422
|
+
return HEADSHOT;
|
|
1423
|
+
}
|
|
1424
|
+
ops.body.vitality -= amount;
|
|
1425
|
+
ops.wound(segment);
|
|
1426
|
+
if (points > rule.jawDamage && ops.random() < rule.jawChance) severWithBleeding(ops, segmentOf(ops.body, "jaw"));
|
|
1427
|
+
return NO_HEADSHOT;
|
|
1428
|
+
};
|
|
1429
|
+
var hitTorso = (ops, points, amount, segment) => {
|
|
1430
|
+
ops.body.vitality -= amount;
|
|
1431
|
+
if (points < ops.rules.severity.splitAtDamage && segment.hp > 0) return ops.wound(segment);
|
|
1432
|
+
const torso = segmentOf(ops.body, "torso");
|
|
1433
|
+
if (!torso.attached) return;
|
|
1434
|
+
severWithBleeding(ops, torso);
|
|
1435
|
+
ops.change("split", "torso");
|
|
1436
|
+
if (ops.rules.deathRule === "head-only") ops.crawl();
|
|
1437
|
+
else ops.kill("vitality");
|
|
1438
|
+
};
|
|
1439
|
+
var hitLimb2 = (ops, blow, amount, hpBefore) => {
|
|
1440
|
+
const { segment } = blow;
|
|
1441
|
+
const rule = ops.rules.severity;
|
|
1442
|
+
ops.body.vitality -= amount * rule.limbVitalityShare;
|
|
1443
|
+
if (isLeg(segment.kind) && segment.side && segment.hp <= segment.max * rule.limpAtHpFraction) ops.limp(segment.side);
|
|
1444
|
+
if (segment.hp > 0 || hpBefore <= 0) return ops.wound(segment);
|
|
1445
|
+
const outcome = severityOf(ops, blow, amount, segment.max);
|
|
1446
|
+
lose(ops, segment, outcome);
|
|
1447
|
+
if (segment.kind === "thigh" || segment.kind === "shin") ops.crawl();
|
|
1448
|
+
};
|
|
1449
|
+
var kitRule = (ops, blow) => {
|
|
1450
|
+
const { segment } = blow;
|
|
1451
|
+
if (!segment.attached) return NO_HEADSHOT;
|
|
1452
|
+
const points = spreadPoints(blow.points, ops.rules.spread, ops.random);
|
|
1453
|
+
const amount = segmentDamage(ops, blow, points);
|
|
1454
|
+
if (blow.point && !blow.explosive) bleedWound(ops, segment, blow.point);
|
|
1455
|
+
const hpBefore = segment.hp;
|
|
1456
|
+
segment.hp -= amount;
|
|
1457
|
+
ops.damage(segment, amount, blow.damageType);
|
|
1458
|
+
let result = NO_HEADSHOT;
|
|
1459
|
+
if (segment.kind === "head") result = hitHead2(ops, blow, points, amount, hpBefore);
|
|
1460
|
+
else if (segment.kind === "torso" || segment.kind === "pelvis") hitTorso(ops, points, amount, segment);
|
|
1461
|
+
else if (segment.kind === "jaw" || segment.kind === "neck") {
|
|
1462
|
+
if (segment.hp <= 0 && hpBefore > 0) {
|
|
1463
|
+
lose(ops, segment, severityOf(ops, blow, amount, segment.max));
|
|
1464
|
+
if (segment.kind === "neck" && !segment.attached) ops.kill("head");
|
|
1465
|
+
} else ops.wound(segment);
|
|
1466
|
+
} else hitLimb2(ops, blow, amount, hpBefore);
|
|
1467
|
+
if (ops.body.vitality <= 0) ops.kill("vitality");
|
|
1468
|
+
if (ops.body.alive && points >= ops.rules.severity.stunAtPoints) ops.stun();
|
|
1469
|
+
return result;
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
// src/damage/pipeline.ts
|
|
1473
|
+
var SEGMENT_RULES = { kit: kitRule, arsenal: arsenalRule, elenco: elencoRule };
|
|
1474
|
+
var HEAD_RESISTANCE = segmentResistance({ kind: "head", bodyCount: 1, protection: 0 });
|
|
1475
|
+
var NO_PROTECTION = { ballistic: 1, resistance: 0, limbs: 1, fire: 1 };
|
|
1476
|
+
var ABSORBED_SPARKS = 8;
|
|
1477
|
+
var METRES_PER_MM = 1e-3;
|
|
1478
|
+
var report = (ops, fields, result = { headshot: false }) => ({
|
|
1479
|
+
through: false,
|
|
1480
|
+
exitEnergy: 0,
|
|
1481
|
+
...fields,
|
|
1482
|
+
killed: !ops.body.alive,
|
|
1483
|
+
headshot: result.headshot
|
|
1484
|
+
});
|
|
1485
|
+
var burn = ({ ops, hit, setup, worn }) => {
|
|
1486
|
+
const torso = segmentOf(ops.body, "torso");
|
|
1487
|
+
const fire = protect({ layers: layersOn(worn, segmentNode("torso").slots, ops.body.consumed), model: setup.model }).fire;
|
|
1488
|
+
const points = setup.rules.burnPointsPerSecond * (hit.duration ?? 0) * fire;
|
|
1489
|
+
ops.body.vitality -= points;
|
|
1490
|
+
ops.damage(torso, points, hit.type);
|
|
1491
|
+
if (ops.body.vitality <= 0) ops.kill("vitality");
|
|
1492
|
+
return report(ops, { part: "torso", points });
|
|
1493
|
+
};
|
|
1494
|
+
var blast = ({ ops, hit, setup, localPoint, jointOf }) => {
|
|
1495
|
+
const points = hit.points ?? pointsFromEnergy(hit.energy ?? 0, setup.rules);
|
|
1496
|
+
const base = { ops, center: localPoint, points, radius: hit.radius ?? 1, jointOf };
|
|
1497
|
+
const common = { cut: hit.cut ?? setup.rules.defaultCut.explosive, damageType: hit.type, model: setup.model, headResistance: HEAD_RESISTANCE };
|
|
1498
|
+
const rule = SEGMENT_RULES[setup.rules.segmentRule] ?? kitRule;
|
|
1499
|
+
const result = ops.body.detail === "regions" ? regionBlast({ ...base, blow: { ...common, protection: NO_PROTECTION } }) : segmentBlast({ ...base, rule, blow: { ...common, limbFactor: ops.spec.limbFactor } });
|
|
1500
|
+
return report(ops, { points }, result);
|
|
1501
|
+
};
|
|
1502
|
+
var DEFAULT_PATH_METRES = 0.2;
|
|
1503
|
+
var pathThrough = (spec, id) => {
|
|
1504
|
+
const shape = spec.parts.find((part) => part.id === id)?.shape;
|
|
1505
|
+
return shape && shape.kind !== "box" ? 2 * shape.radius : DEFAULT_PATH_METRES;
|
|
1506
|
+
};
|
|
1507
|
+
var exitOf = (hit, depth) => {
|
|
1508
|
+
const [x, y, z] = hit.direction ?? [0, 0, -1];
|
|
1509
|
+
return [hit.point[0] + x * depth, hit.point[1] + y * depth, hit.point[2] + z * depth];
|
|
1510
|
+
};
|
|
1511
|
+
var direct = ({ ops, hit, setup, worn, localPoint, describe }) => {
|
|
1512
|
+
const { body, spec, rules } = ops;
|
|
1513
|
+
const caliber = hit.caliberId === void 0 ? void 0 : setup.calibers.get(hit.caliberId);
|
|
1514
|
+
const projectile = hit.type === "ballistic" ? projectileOf(hit, caliber) : void 0;
|
|
1515
|
+
const id = resolveSegment(body, describe(), hit.part, localPoint);
|
|
1516
|
+
const region = regionOfSegment(body, id);
|
|
1517
|
+
const slots = segmentNode(body.detail === "regions" ? REGION_SEGMENTS[region][0] : id).slots;
|
|
1518
|
+
const protection = protect({ layers: layersOn(worn, slots, body.consumed), model: setup.model, ...projectile ? { projectile } : {} });
|
|
1519
|
+
if (protection.absorbedBy) {
|
|
1520
|
+
body.consumed.add(protection.absorbedBy);
|
|
1521
|
+
ops.emit({ type: "debris", part: id, kind: "sparks", point: hit.point, normal: [0, 1, 0], count: ABSORBED_SPARKS, speed: 3 });
|
|
1522
|
+
return report(ops, { part: id, points: 0, absorbedBy: protection.absorbedBy });
|
|
1523
|
+
}
|
|
1524
|
+
const rawPoints = resolvePoints(hit, caliber, rules);
|
|
1525
|
+
const points = rawPoints * protection.ballistic;
|
|
1526
|
+
const cut = cutOf(hit, projectile, rules);
|
|
1527
|
+
const segment = segmentOf(body, id);
|
|
1528
|
+
const resistance = segmentResistance({ kind: segment.kind, bodyCount: spec.bodyCount, protection: protection.resistance });
|
|
1529
|
+
const through = setup.model.id === "physical" && projectile !== void 0 && projectile.force > resistance;
|
|
1530
|
+
const exitEnergy = through && projectile ? (hit.energy ?? 0) * energyKept(projectile.force, resistance) : 0;
|
|
1531
|
+
if (projectile) {
|
|
1532
|
+
const depth = pathThrough(describe(), id);
|
|
1533
|
+
ops.emit({ type: "hole", part: id, hole: { point: localPoint, normal: [0, 0, 1], diameter: (hit.caliber ?? 9) * METRES_PER_MM, through }, worldPoint: hit.point, ...through ? { exitPoint: exitOf(hit, depth) } : {} });
|
|
1534
|
+
}
|
|
1535
|
+
const common = { points, cut, damageType: hit.type, model: setup.model, headResistance: HEAD_RESISTANCE, ...projectile ? { projectile } : {} };
|
|
1536
|
+
const result = body.detail === "regions" ? regionRule(ops, { ...common, points: rawPoints, region, protection }) : (SEGMENT_RULES[rules.segmentRule] ?? kitRule)(ops, {
|
|
1537
|
+
...common,
|
|
1538
|
+
segment,
|
|
1539
|
+
explosive: false,
|
|
1540
|
+
big: points > rules.blast.bigPoints,
|
|
1541
|
+
limbFactor: spec.limbFactor * protection.limbs,
|
|
1542
|
+
...hit.woundAtPoint === false ? {} : { point: hit.point }
|
|
1543
|
+
});
|
|
1544
|
+
return report(ops, { part: id, points, through, exitEnergy }, result);
|
|
1545
|
+
};
|
|
1546
|
+
var applyBlow = (params) => {
|
|
1547
|
+
if (params.hit.type === "fire" || params.hit.type === "heat") return burn(params);
|
|
1548
|
+
if (params.hit.type === "explosive") return blast(params);
|
|
1549
|
+
return direct(params);
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// src/damage/damage-character.ts
|
|
1553
|
+
var createDamageCharacter = (core, setup) => {
|
|
1554
|
+
const view = characterView(core);
|
|
1555
|
+
const worn = core.spec.protection.map((id) => setup.protection.get(id)).filter((layer) => layer !== void 0);
|
|
1556
|
+
let bleeders = [];
|
|
1557
|
+
let hitCounter = 0;
|
|
1558
|
+
let clock = 0;
|
|
1559
|
+
let lastHit;
|
|
1560
|
+
const transform = () => ({ position: core.position, rotation: core.rotation });
|
|
1561
|
+
const jointOf = (id) => core.rig.joints[segmentNode(id).joint];
|
|
1562
|
+
const pointOf = (id) => toWorld(transform(), jointOf(id));
|
|
1563
|
+
const opsFor = (random, source) => createBodyOps({ body: core.body, spec: core.spec, rules: setup.rules, random, time: clock, pointOf, bleeders, ...source !== void 0 ? { source } : {} });
|
|
1564
|
+
const character = {
|
|
1565
|
+
...view,
|
|
1566
|
+
get segments() {
|
|
1567
|
+
return core.body.segments;
|
|
1568
|
+
},
|
|
1569
|
+
get detail() {
|
|
1570
|
+
return core.body.detail;
|
|
1571
|
+
},
|
|
1572
|
+
get alive() {
|
|
1573
|
+
return core.body.alive;
|
|
1574
|
+
},
|
|
1575
|
+
get limping() {
|
|
1576
|
+
return [...core.body.limping];
|
|
1577
|
+
},
|
|
1578
|
+
get stunSeconds() {
|
|
1579
|
+
return core.body.stunSeconds;
|
|
1580
|
+
},
|
|
1581
|
+
get lastHit() {
|
|
1582
|
+
return lastHit;
|
|
1583
|
+
},
|
|
1584
|
+
name: core.spec.kind,
|
|
1585
|
+
hit(raw) {
|
|
1586
|
+
const hit = raw;
|
|
1587
|
+
hitCounter += 1;
|
|
1588
|
+
const random = core.spec.random ?? createRng((core.spec.seed ^ hitCounter) >>> 0);
|
|
1589
|
+
const ops = opsFor(random, hit.source);
|
|
1590
|
+
lastHit = applyBlow({ ops, hit, setup, worn, localPoint: toLocal(transform(), hit.point), jointOf, describe: core.describe });
|
|
1591
|
+
return ops.events;
|
|
1592
|
+
},
|
|
1593
|
+
update(dt) {
|
|
1594
|
+
clock += dt;
|
|
1595
|
+
core.body.stunSeconds = Math.max(0, core.body.stunSeconds - dt);
|
|
1596
|
+
const ops = opsFor(() => 0);
|
|
1597
|
+
stepBleeding({ bleeders, dt, emit: ops.emit, pointOf });
|
|
1598
|
+
return ops.events;
|
|
1599
|
+
},
|
|
1600
|
+
bleeders: () => bleeders.map((bleeder) => ({ ...bleeder })),
|
|
1601
|
+
intactProtection: () => core.spec.protection.filter((id) => !core.body.consumed.has(id)),
|
|
1602
|
+
resistanceOf(part) {
|
|
1603
|
+
const id = resolveSegment(core.body, core.describe(), part, [0, 0, 0]);
|
|
1604
|
+
const layers = layersOn(worn, segmentNode(id).slots, core.body.consumed);
|
|
1605
|
+
const protection = layers.reduce((sum, layer) => sum + (layer.ballistic?.resistance ?? 0), 0);
|
|
1606
|
+
return segmentResistance({ kind: segmentNode(id).kind, bodyCount: core.spec.bodyCount, protection });
|
|
1607
|
+
},
|
|
1608
|
+
protectionAgainst: (attack, part) => layeredProtection({ layers: layersOn(worn, segmentNode(part).slots, core.body.consumed), attack }),
|
|
1609
|
+
reset() {
|
|
1610
|
+
core.resetBody();
|
|
1611
|
+
bleeders = [];
|
|
1612
|
+
hitCounter = 0;
|
|
1613
|
+
lastHit = void 0;
|
|
1614
|
+
},
|
|
1615
|
+
serialize() {
|
|
1616
|
+
const { consumed, ...rest } = core.body;
|
|
1617
|
+
return { body: { ...structuredClone(rest), consumed: [...consumed] }, bleeders: structuredClone(bleeders), hitCounter, clock };
|
|
1618
|
+
},
|
|
1619
|
+
load(data) {
|
|
1620
|
+
const saved = data;
|
|
1621
|
+
Object.assign(core.body, structuredClone(saved.body), { consumed: new Set(saved.body.consumed) });
|
|
1622
|
+
bleeders = structuredClone(saved.bleeders);
|
|
1623
|
+
hitCounter = saved.hitCounter;
|
|
1624
|
+
clock = saved.clock;
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
return character;
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
// src/damage/line.ts
|
|
1631
|
+
var RESTART_METRES = 0.3;
|
|
1632
|
+
var regionOfPart = (part) => isSegmentId(part) ? segmentNode(part).region : isBodyRegion(part) ? part : "torso";
|
|
1633
|
+
var lineHit = (index, target, fields) => ({
|
|
1634
|
+
index,
|
|
1635
|
+
...fields,
|
|
1636
|
+
killed: !target.character.alive,
|
|
1637
|
+
headshot: target.character.lastHit?.headshot ?? false
|
|
1638
|
+
});
|
|
1639
|
+
var teamGameLine = ({ shot, targets, calibers }) => {
|
|
1640
|
+
const caliber = shot.caliberId === void 0 ? void 0 : calibers.get(shot.caliberId);
|
|
1641
|
+
if (caliber?.penetration === void 0 || caliber.points === void 0) throw new Error(`game-characters: the team-game line needs a calibre with points and penetration ("${shot.caliberId}")`);
|
|
1642
|
+
let penetration = caliber.penetration;
|
|
1643
|
+
let points = caliber.points;
|
|
1644
|
+
let origin = 0;
|
|
1645
|
+
const hits = [];
|
|
1646
|
+
for (const [index, target] of targets.entries()) {
|
|
1647
|
+
if (!target.character.alive || target.distance - origin <= 0) continue;
|
|
1648
|
+
const distance2 = target.distance - origin;
|
|
1649
|
+
const dealt = points * teamGameFalloff(distance2, caliber.effectiveRangeMetres ?? Infinity);
|
|
1650
|
+
const events = target.character.hit({ type: "ballistic", point: target.point ?? [0, 0, 0], part: target.part, points: dealt, caliberId: caliber.id, distance: distance2, ...shot.source ? { source: shot.source } : {} });
|
|
1651
|
+
hits.push(lineHit(index, target, { points: dealt, energy: shot.energyAt(target.distance), events }));
|
|
1652
|
+
penetration -= teamGameCost(regionOfPart(target.part), target.character.spec.bodyCount);
|
|
1653
|
+
points *= TEAM_GAME_RULES.damageKeptPerBody;
|
|
1654
|
+
if (penetration <= 0) break;
|
|
1655
|
+
if (!target.character.alive) origin = target.distance + RESTART_METRES;
|
|
1656
|
+
}
|
|
1657
|
+
return { hits, stoppedAt: hits.length === 0 ? 0 : (hits[hits.length - 1]?.index ?? -1) + 1 };
|
|
1658
|
+
};
|
|
1659
|
+
var physicalLine = ({ shot, targets }) => {
|
|
1660
|
+
const first = targets[0];
|
|
1661
|
+
let force = first ? penetrationForce(shot.energyAt(first.distance), shot.mass, shot.caliber) : 0;
|
|
1662
|
+
let kept = 1;
|
|
1663
|
+
const hits = [];
|
|
1664
|
+
for (const [index, target] of targets.entries()) {
|
|
1665
|
+
if (force <= 0) break;
|
|
1666
|
+
if (!target.character.alive) continue;
|
|
1667
|
+
const energy = shot.energyAt(target.distance) * kept;
|
|
1668
|
+
const events = target.character.hit({ type: "ballistic", point: target.point ?? [0, 0, 0], part: target.part, energy, mass: shot.mass, caliber: shot.caliber, distance: target.distance, ...shot.caliberId ? { caliberId: shot.caliberId } : {} });
|
|
1669
|
+
hits.push(lineHit(index, target, { points: target.character.lastHit?.points ?? 0, energy, events }));
|
|
1670
|
+
const resistance = target.character.resistanceOf(target.part);
|
|
1671
|
+
kept *= energyKept(force, resistance);
|
|
1672
|
+
force -= resistance;
|
|
1673
|
+
}
|
|
1674
|
+
return { hits, stoppedAt: hits.length === 0 ? 0 : (hits[hits.length - 1]?.index ?? -1) + 1 };
|
|
1675
|
+
};
|
|
1676
|
+
var penetrateLine = (params) => params.model === "team-game" ? teamGameLine(params) : physicalLine(params);
|
|
1677
|
+
|
|
1678
|
+
// src/registry.ts
|
|
1679
|
+
var mergeById = (shipped, extra) => {
|
|
1680
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1681
|
+
for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
|
|
1682
|
+
return entries;
|
|
1683
|
+
};
|
|
1684
|
+
var applyPatches = (entries, patches) => {
|
|
1685
|
+
for (const [id, patch] of Object.entries(patches ?? {})) {
|
|
1686
|
+
const entry = entries.get(id);
|
|
1687
|
+
if (entry) entries.set(id, { ...entry, ...patch, id });
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
var removeIds = (entries, ids) => {
|
|
1691
|
+
for (const id of ids ?? []) entries.delete(id);
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
// src/kit.ts
|
|
1695
|
+
var DEFAULT_CORPSES = { max: 400, seconds: 900 };
|
|
1696
|
+
var PERSON_PRESET = { id: "person", kind: "person", age: "adult" };
|
|
1697
|
+
var applyPreset = (spec, presets) => {
|
|
1698
|
+
const preset = presets.get(spec.preset ?? spec.kind);
|
|
1699
|
+
if (!preset) return spec;
|
|
1700
|
+
const { id: _presetId, ...defaults } = preset;
|
|
1701
|
+
const multipliers = { ...defaults.multipliers, ...spec.multipliers };
|
|
1702
|
+
return { ...defaults, ...spec, multipliers };
|
|
1703
|
+
};
|
|
1704
|
+
var resolveDamageRules = (options = {}) => {
|
|
1705
|
+
const { preset, severity: severity2, blast: blast2, segments, ...rest } = options;
|
|
1706
|
+
const base = DAMAGE_PRESETS[preset ?? "kit"];
|
|
1707
|
+
return { ...base, ...rest, severity: { ...base.severity, ...severity2 }, blast: { ...base.blast, ...blast2 }, segments: { ...base.segments, ...segments } };
|
|
1708
|
+
};
|
|
1709
|
+
var modelOf = (option) => typeof option === "object" ? option : PENETRATION_MODELS[option ?? "physical"];
|
|
1710
|
+
var createCharacters = (options = {}) => {
|
|
1711
|
+
const body = createBodySpec(options.body);
|
|
1712
|
+
const packs = options.packs ?? [];
|
|
1713
|
+
const presets = mergeById([PERSON_PRESET], packs.flatMap((pack) => pack.presets ?? []));
|
|
1714
|
+
applyPatches(presets, options.patch);
|
|
1715
|
+
removeIds(presets, options.remove);
|
|
1716
|
+
const damage = resolveDamageRules(options.damage);
|
|
1717
|
+
const penetration = modelOf(options.penetration);
|
|
1718
|
+
const protection = mergeById(PROTECTION_PRESETS, [...packs.flatMap((pack) => pack.protection ?? []), ...options.protection ?? []]);
|
|
1719
|
+
const shippedCalibers = penetration.id === "team-game" ? TEAM_GAME_CALIBERS : [];
|
|
1720
|
+
const calibers = mergeById(shippedCalibers, [...packs.flatMap((pack) => pack.calibers ?? []), ...options.calibers ?? []]);
|
|
1721
|
+
const setup = { rules: damage, model: penetration, protection, calibers };
|
|
1722
|
+
let counter = 0;
|
|
1723
|
+
return {
|
|
1724
|
+
body,
|
|
1725
|
+
presets,
|
|
1726
|
+
corpses: { ...DEFAULT_CORPSES, ...options.corpses },
|
|
1727
|
+
damage,
|
|
1728
|
+
penetration,
|
|
1729
|
+
protection,
|
|
1730
|
+
calibers,
|
|
1731
|
+
create(spec) {
|
|
1732
|
+
counter += 1;
|
|
1733
|
+
const resolved = resolveSpec(applyPreset(spec, presets), body, counter);
|
|
1734
|
+
const core = createCharacterCore({ spec: resolved, bodySpec: body, hitPointsOf: (kind) => damage.segments[kind].hitPoints });
|
|
1735
|
+
return createDamageCharacter(core, setup);
|
|
1736
|
+
},
|
|
1737
|
+
createCorpses: () => createCorpseLedger({ ...DEFAULT_CORPSES, ...options.corpses }),
|
|
1738
|
+
penetrateLine: (shot, targets) => penetrateLine({ model: penetration.id === "team-game" ? "team-game" : "physical", shot, targets, calibers })
|
|
1739
|
+
};
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
// src/data/gait.json
|
|
1743
|
+
var gait_default = {
|
|
1744
|
+
cascade: {
|
|
1745
|
+
hip: 16,
|
|
1746
|
+
spine: 11,
|
|
1747
|
+
shoulder: 9,
|
|
1748
|
+
elbow: 7,
|
|
1749
|
+
leg: 14,
|
|
1750
|
+
knee: 11,
|
|
1751
|
+
head: 6
|
|
1752
|
+
},
|
|
1753
|
+
moves: {
|
|
1754
|
+
idle: {
|
|
1755
|
+
gait: 0,
|
|
1756
|
+
freq: 0,
|
|
1757
|
+
amp: 0,
|
|
1758
|
+
knee: 0,
|
|
1759
|
+
bob: 0,
|
|
1760
|
+
lean: 0,
|
|
1761
|
+
arm: 0,
|
|
1762
|
+
elb: 0.12,
|
|
1763
|
+
crouch: 0,
|
|
1764
|
+
aim: 0,
|
|
1765
|
+
turn: 0,
|
|
1766
|
+
grip: 0.35
|
|
1767
|
+
},
|
|
1768
|
+
walk: {
|
|
1769
|
+
gait: 1,
|
|
1770
|
+
freq: 5.6,
|
|
1771
|
+
amp: 0.42,
|
|
1772
|
+
knee: 0.85,
|
|
1773
|
+
bob: 0.035,
|
|
1774
|
+
lean: 0.05,
|
|
1775
|
+
arm: 0.42,
|
|
1776
|
+
elb: 0.3,
|
|
1777
|
+
crouch: 0,
|
|
1778
|
+
aim: 0,
|
|
1779
|
+
turn: 0,
|
|
1780
|
+
grip: 0.4
|
|
1781
|
+
},
|
|
1782
|
+
run: {
|
|
1783
|
+
gait: 1,
|
|
1784
|
+
freq: 9.2,
|
|
1785
|
+
amp: 0.72,
|
|
1786
|
+
knee: 1.5,
|
|
1787
|
+
bob: 0.06,
|
|
1788
|
+
lean: 0.3,
|
|
1789
|
+
arm: 0.85,
|
|
1790
|
+
elb: 1.35,
|
|
1791
|
+
crouch: 0,
|
|
1792
|
+
aim: 0,
|
|
1793
|
+
turn: 0,
|
|
1794
|
+
grip: 1.1
|
|
1795
|
+
},
|
|
1796
|
+
crouch: {
|
|
1797
|
+
gait: 1,
|
|
1798
|
+
freq: 3.8,
|
|
1799
|
+
amp: 0.3,
|
|
1800
|
+
knee: 0.6,
|
|
1801
|
+
bob: 0.02,
|
|
1802
|
+
lean: 0.42,
|
|
1803
|
+
arm: 0.15,
|
|
1804
|
+
elb: 0.7,
|
|
1805
|
+
crouch: 1,
|
|
1806
|
+
aim: 0,
|
|
1807
|
+
turn: 0,
|
|
1808
|
+
grip: 0.8
|
|
1809
|
+
},
|
|
1810
|
+
aim: {
|
|
1811
|
+
gait: 0,
|
|
1812
|
+
freq: 0,
|
|
1813
|
+
amp: 0,
|
|
1814
|
+
knee: 0,
|
|
1815
|
+
bob: 0,
|
|
1816
|
+
lean: 0.06,
|
|
1817
|
+
arm: 0,
|
|
1818
|
+
elb: 0.2,
|
|
1819
|
+
crouch: 0.25,
|
|
1820
|
+
aim: 1,
|
|
1821
|
+
turn: 0,
|
|
1822
|
+
grip: 1.2
|
|
1823
|
+
},
|
|
1824
|
+
turn: {
|
|
1825
|
+
gait: 0.5,
|
|
1826
|
+
freq: 4.2,
|
|
1827
|
+
amp: 0.14,
|
|
1828
|
+
knee: 0.7,
|
|
1829
|
+
bob: 0.012,
|
|
1830
|
+
lean: 0.03,
|
|
1831
|
+
arm: 0.12,
|
|
1832
|
+
elb: 0.25,
|
|
1833
|
+
crouch: 0,
|
|
1834
|
+
aim: 0,
|
|
1835
|
+
turn: 1,
|
|
1836
|
+
grip: 0.4
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
poses: [
|
|
1840
|
+
{
|
|
1841
|
+
index: 0,
|
|
1842
|
+
id: "walk",
|
|
1843
|
+
name: {
|
|
1844
|
+
"pt-BR": "anda"
|
|
1845
|
+
}
|
|
1846
|
+
},
|
|
1847
|
+
{
|
|
1848
|
+
index: 1,
|
|
1849
|
+
id: "crawl",
|
|
1850
|
+
name: {
|
|
1851
|
+
"pt-BR": "rasteja"
|
|
1852
|
+
}
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
index: 2,
|
|
1856
|
+
id: "fallen",
|
|
1857
|
+
name: {
|
|
1858
|
+
"pt-BR": "ca\xEDdo"
|
|
1859
|
+
}
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
index: 3,
|
|
1863
|
+
id: "attack",
|
|
1864
|
+
name: {
|
|
1865
|
+
"pt-BR": "ataca"
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
index: 4,
|
|
1870
|
+
id: "scream",
|
|
1871
|
+
name: {
|
|
1872
|
+
"pt-BR": "grito"
|
|
1873
|
+
}
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
index: 5,
|
|
1877
|
+
id: "crouch",
|
|
1878
|
+
name: {
|
|
1879
|
+
"pt-BR": "agachado"
|
|
1880
|
+
}
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
index: 6,
|
|
1884
|
+
id: "leap",
|
|
1885
|
+
name: {
|
|
1886
|
+
"pt-BR": "salto"
|
|
1887
|
+
}
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
index: 7,
|
|
1891
|
+
id: "eat",
|
|
1892
|
+
name: {
|
|
1893
|
+
"pt-BR": "comendo"
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
],
|
|
1897
|
+
walk: {
|
|
1898
|
+
cadenceBase: 0.5,
|
|
1899
|
+
cadencePerSpeed: 0.3,
|
|
1900
|
+
stopBelowSpeed: 0.05,
|
|
1901
|
+
maxSine: 0.9,
|
|
1902
|
+
strideFactor: 4,
|
|
1903
|
+
legLengthFactor: 0.9
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1907
|
+
// src/gait/stride.ts
|
|
1908
|
+
var WALK = gait_default.walk;
|
|
1909
|
+
var walkCadence = (speed) => WALK.cadenceBase + WALK.cadencePerSpeed * speed;
|
|
1910
|
+
var walkGait = (speed, height = 1) => speed < WALK.stopBelowSpeed ? 0 : 2 * Math.asin(Math.min(WALK.maxSine, speed / (WALK.strideFactor * WALK.legLengthFactor * height * walkCadence(speed))));
|
|
1911
|
+
var SWING_SHARE = 0.5;
|
|
1912
|
+
var KNEE_PER_GAIT = 0.85;
|
|
1913
|
+
var strideFor = ({ speed, height = 1, strideFromSpeed = true }, fallback) => {
|
|
1914
|
+
if (!strideFromSpeed) return fallback;
|
|
1915
|
+
const amplitude = walkGait(speed, height);
|
|
1916
|
+
return { frequency: walkCadence(speed), swing: amplitude * SWING_SHARE, kneeBend: amplitude * KNEE_PER_GAIT };
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
// src/gait/profile.ts
|
|
1920
|
+
var emptySample = () => ({ upper: {}, head: {}, arms: [{}, {}], legs: [{}, {}] });
|
|
1921
|
+
var CASCADE = gait_default.cascade;
|
|
1922
|
+
var INSTANCE_POSES = ["walk", "crawl", "fallen", "attack", "scream", "crouch", "leap", "eat"];
|
|
1923
|
+
var MIN_HEIGHT = -2;
|
|
1924
|
+
var MAX_HEIGHT = 8;
|
|
1925
|
+
var HEIGHT_STEPS = 1e3;
|
|
1926
|
+
var POSE_SLOTS = 16;
|
|
1927
|
+
var encodePoseHeight = (pose, height = 0) => INSTANCE_POSES.indexOf(pose) + POSE_SLOTS * Math.round((Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, height)) - MIN_HEIGHT) * HEIGHT_STEPS);
|
|
1928
|
+
var decodePoseHeight = (encoded) => {
|
|
1929
|
+
const height = Math.floor(encoded / POSE_SLOTS) / HEIGHT_STEPS + MIN_HEIGHT;
|
|
1930
|
+
return { pose: INSTANCE_POSES[encoded - Math.floor(encoded / POSE_SLOTS) * POSE_SLOTS], height };
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
// src/gait/animate.ts
|
|
1934
|
+
var TWO_PI = Math.PI * 2;
|
|
1935
|
+
var LEAP_AIR_SHARE = 0.38;
|
|
1936
|
+
var LEAP_PHASE_DIVISOR = 6.3;
|
|
1937
|
+
var DEFAULT_PROFILE = { f: 1.1, a: 0.28, k: 0.35, drag: true, arm: 0.08 };
|
|
1938
|
+
var leap = ({ rest, time, phase, profile }) => {
|
|
1939
|
+
const sample = emptySample();
|
|
1940
|
+
const cycle = (time * profile.f + phase / LEAP_PHASE_DIVISOR) % 1;
|
|
1941
|
+
const air = cycle < LEAP_AIR_SHARE ? Math.sin(cycle / LEAP_AIR_SHARE * Math.PI) : 0;
|
|
1942
|
+
sample.hipY = rest.hipY + air * 0.95;
|
|
1943
|
+
sample.upper.x = rest.lean - air * 0.45;
|
|
1944
|
+
rest.legs.forEach((leg, index) => sample.legs[index] = { hip: leg.hip * (1 - air * 0.7), knee: leg.knee * (1 - air * 0.75) });
|
|
1945
|
+
rest.arms.forEach((arm, index) => sample.arms[index] = { shoulder: arm.shoulder - air * 2.3, elbow: arm.elbow * (1 - air) });
|
|
1946
|
+
return sample;
|
|
1947
|
+
};
|
|
1948
|
+
var animateProfile = ({ profile = DEFAULT_PROFILE, rest, time, phase, stride }) => {
|
|
1949
|
+
const own = { frequency: profile.f, swing: profile.a ?? 0, kneeBend: profile.k ?? 0 };
|
|
1950
|
+
const { frequency, swing, kneeBend } = stride ? strideFor(stride, own) : own;
|
|
1951
|
+
const angle = time * frequency * TWO_PI + phase;
|
|
1952
|
+
const sine = Math.sin(angle);
|
|
1953
|
+
const cosine = Math.cos(angle);
|
|
1954
|
+
const sample = emptySample();
|
|
1955
|
+
if (profile.leap) return leap({ rest, time, phase, profile });
|
|
1956
|
+
if (profile.listen) {
|
|
1957
|
+
sample.head.y = Math.sin(time * 0.7 + phase) * 0.6;
|
|
1958
|
+
rest.arms.forEach((arm, index) => sample.arms[index] = { shoulder: arm.shoulder + Math.sin(time * 0.9 + index) * 0.08 });
|
|
1959
|
+
return sample;
|
|
1960
|
+
}
|
|
1961
|
+
if (profile.eat) {
|
|
1962
|
+
const wave = Math.sin(time * profile.f * TWO_PI + phase);
|
|
1963
|
+
sample.head.x = rest.headX + Math.max(0, wave) * 0.35;
|
|
1964
|
+
sample.upper.x = rest.lean + Math.max(0, wave) * 0.08;
|
|
1965
|
+
rest.arms.forEach((arm, index) => sample.arms[index] = { elbow: arm.elbow + wave * 0.2 });
|
|
1966
|
+
return sample;
|
|
1967
|
+
}
|
|
1968
|
+
if (profile.crawl) {
|
|
1969
|
+
rest.arms.forEach((arm, index) => {
|
|
1970
|
+
const wave = index ? sine : -sine;
|
|
1971
|
+
sample.arms[index] = { shoulder: arm.shoulder + wave * 0.35, elbow: arm.elbow - Math.max(0, wave) * 0.6 };
|
|
1972
|
+
});
|
|
1973
|
+
sample.upper.z = sine * 0.06;
|
|
1974
|
+
return sample;
|
|
1975
|
+
}
|
|
1976
|
+
if (profile.idle) {
|
|
1977
|
+
const scare = Math.max(0, Math.sin(time * 0.9 + phase));
|
|
1978
|
+
sample.upper.x = rest.lean - scare * 0.18;
|
|
1979
|
+
sample.head.x = rest.headX - scare * 0.25;
|
|
1980
|
+
rest.arms.forEach((arm, index) => sample.arms[index] = { shoulder: arm.shoulder + scare * 0.25 });
|
|
1981
|
+
return sample;
|
|
1982
|
+
}
|
|
1983
|
+
rest.legs.forEach((leg, index) => {
|
|
1984
|
+
if (leg.lost) return;
|
|
1985
|
+
const wave = index ? -sine : sine;
|
|
1986
|
+
if (profile.drag && index === 1) return void (sample.legs[index] = { hip: leg.hip + wave * swing * 0.35, knee: leg.knee });
|
|
1987
|
+
sample.legs[index] = { hip: leg.hip + wave * swing, knee: leg.knee + Math.max(0, index ? cosine : -cosine) * kneeBend };
|
|
1988
|
+
});
|
|
1989
|
+
rest.arms.forEach((arm, index) => sample.arms[index] = { shoulder: arm.shoulder + (index ? sine : -sine) * (profile.arm || 0.1) });
|
|
1990
|
+
sample.hipY = rest.hipY - Math.abs(cosine) * (profile.bob || 0.02);
|
|
1991
|
+
sample.upper.y = sine * 0.06;
|
|
1992
|
+
sample.upper.z = (profile.sway || 0) * sine + (profile.drag ? 0.04 : 0);
|
|
1993
|
+
if (profile.stalk) sample.upper.x = rest.lean + sine * 0.04;
|
|
1994
|
+
if (profile.sniff) sample.head.y = Math.sin(time * 3.1 + phase) * 0.35;
|
|
1995
|
+
return sample;
|
|
1996
|
+
};
|
|
1997
|
+
|
|
1998
|
+
// src/gait/cascade.ts
|
|
1999
|
+
var stepCascade = (current, target, dt, joint, rates = CASCADE) => current + (target - current) * Math.min(1, dt * rates[joint]);
|
|
2000
|
+
var lerpPose = (from, to, amount) => {
|
|
2001
|
+
const result = {};
|
|
2002
|
+
for (const name of Object.keys(to)) {
|
|
2003
|
+
const start = from[name];
|
|
2004
|
+
const end = to[name];
|
|
2005
|
+
result[name] = start === void 0 ? end : start + (end - start) * amount;
|
|
2006
|
+
}
|
|
2007
|
+
return result;
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
// src/gait/moves.ts
|
|
2011
|
+
var MOVES = gait_default.moves;
|
|
2012
|
+
var MOVE_BLEND_RATE = 4;
|
|
2013
|
+
var moveBlendOf = (move) => ({ ...MOVES[move] });
|
|
2014
|
+
var blendMoves = (current, target, dt) => {
|
|
2015
|
+
const goal = typeof target === "string" ? MOVES[target] : target;
|
|
2016
|
+
const amount = Math.min(1, dt * MOVE_BLEND_RATE);
|
|
2017
|
+
for (const key of Object.keys(goal)) current[key] += (goal[key] - current[key]) * amount;
|
|
2018
|
+
return current;
|
|
2019
|
+
};
|
|
2020
|
+
var REANIMATED_STAGE = 4;
|
|
2021
|
+
var TREMOR_STAGE = 3;
|
|
2022
|
+
var humanPose = (params) => {
|
|
2023
|
+
const { moves, time, phase, cycle, hunch, armRaise, stage, reduced = false, skirt = false } = params;
|
|
2024
|
+
const zombie = stage === REANIMATED_STAGE;
|
|
2025
|
+
const walking = reduced ? 0 : moves.gait;
|
|
2026
|
+
const phased = phase + time;
|
|
2027
|
+
const shift = reduced ? 0 : Math.sin(time * 0.45 + phase) * (1 - walking) * (1 - moves.aim);
|
|
2028
|
+
const upperX = hunch + moves.crouch * 0.15 + (stage >= TREMOR_STAGE && !reduced ? Math.sin(phased * 2.3) * 0.03 : 0);
|
|
2029
|
+
const upperY = walking * Math.sin(cycle - 0.35) * (0.08 + moves.arm * 0.12) - moves.aim * 0.35 + moves.turn * 0.25;
|
|
2030
|
+
const upperZ = -shift * 0.025 + walking * Math.sin(cycle - 0.35) * 0.02 + (zombie ? 0.05 + Math.sin(cycle * 0.5) * 0.06 * walking : 0);
|
|
2031
|
+
const legs = params.legSides.map((side, index) => {
|
|
2032
|
+
const legCycle = cycle + (index ? Math.PI : 0);
|
|
2033
|
+
const amplitude = moves.amp * (zombie && index === 0 ? 0.45 : 1) * (skirt ? 0.75 : 1);
|
|
2034
|
+
return {
|
|
2035
|
+
hip: -walking * Math.sin(legCycle) * amplitude - moves.crouch * 0.95 + (zombie && !walking ? index ? -0.1 : 0.08 : 0),
|
|
2036
|
+
knee: walking * (Math.max(0, Math.cos(legCycle)) * moves.knee * (zombie && index === 0 ? 0.3 : 1) + 0.06) + moves.crouch * 1.55 + (1 - walking) * (0.03 + Math.max(0, shift * (index ? 1 : -1)) * 0.18),
|
|
2037
|
+
hipZ: side * (0.02 + moves.aim * 0.1 + moves.crouch * 0.06)
|
|
2038
|
+
};
|
|
2039
|
+
});
|
|
2040
|
+
const arms = params.arms.map(({ side, hold = false }, index) => {
|
|
2041
|
+
const raise = armRaise * (hold && !zombie ? 0.2 : 1);
|
|
2042
|
+
const swing = cycle - 0.25;
|
|
2043
|
+
const shoulderX2 = (hold ? -0.2 : 0) - raise * (zombie ? 1.12 : 1.3) - side * Math.sin(swing) * moves.arm * walking * (zombie ? 0.3 : 1) + (reduced || walking ? 0 : Math.sin(phased * 1.6 + side) * 0.04) + (zombie && !reduced ? Math.sin(phased * 3 + side * 2) * 0.08 : 0);
|
|
2044
|
+
const aimX = index === 0 ? -1.45 : -1.32;
|
|
2045
|
+
return {
|
|
2046
|
+
shoulder: shoulderX2 * (1 - moves.aim) + aimX * moves.aim,
|
|
2047
|
+
shoulderZ: side * (0.07 + (zombie ? 0.1 : 0)) * (1 - moves.aim) - side * 0.32 * moves.aim,
|
|
2048
|
+
elbow: zombie ? -0.18 : -((moves.elb + (hold ? 0.25 : 0) + walking * Math.max(0, side * Math.sin(swing - 0.3)) * 0.3) * (1 - moves.aim) + (index === 0 ? 0.15 : 0.55) * moves.aim),
|
|
2049
|
+
wrist: reduced ? 0 : -walking * Math.sin(swing - 0.6) * 0.15 * side * (1 - moves.aim),
|
|
2050
|
+
grip: (hold ? 1.25 : moves.grip) * (zombie ? 0.5 : 1)
|
|
2051
|
+
};
|
|
2052
|
+
});
|
|
2053
|
+
const headY = (reduced ? 0 : Math.sin(phased * 0.5) * 0.25 * (1 - walking) * (1 - moves.aim) * (zombie ? 0.3 : 1)) - upperY * 0.75 * (1 - moves.turn) + moves.turn * 0.45 + moves.aim * 0.3;
|
|
2054
|
+
return {
|
|
2055
|
+
chestScaleY: 1 + (reduced ? 0 : Math.sin(phased * (1.6 + moves.freq * 0.12)) * (0.012 + moves.freq * 12e-4)),
|
|
2056
|
+
hip: { x: shift * 0.025 + walking * Math.sin(cycle) * 0.018, y: -walking * moves.bob * Math.abs(Math.sin(cycle)) - moves.crouch * 0.26, zRotation: shift * 0.02 - walking * Math.sin(cycle) * 0.022 },
|
|
2057
|
+
upper: { x: upperX, y: upperY, z: upperZ },
|
|
2058
|
+
head: { x: -hunch * 0.6 - moves.crouch * 0.12 + (zombie ? 0.15 : 0) + walking * Math.sin(cycle * 2 - 0.6) * 0.02, y: headY, z: (zombie ? 0.22 : 0) - upperZ * 0.6 },
|
|
2059
|
+
legs,
|
|
2060
|
+
arms
|
|
2061
|
+
};
|
|
2062
|
+
};
|
|
2063
|
+
var LIMP = { swing: 0.3, knee: 0.05, lean: 0.07 };
|
|
2064
|
+
|
|
2065
|
+
// src/gait/lying.ts
|
|
2066
|
+
var CRAWL_TILT_RATE = 4;
|
|
2067
|
+
var DEAD_TILT_RATE = 3;
|
|
2068
|
+
var crawlPose = (time, phase, sides) => {
|
|
2069
|
+
const cycle = time * 2.2 + phase;
|
|
2070
|
+
const wave = Math.sin(cycle);
|
|
2071
|
+
return {
|
|
2072
|
+
figTilt: 1.38,
|
|
2073
|
+
hipY: 0.2 + Math.abs(wave) * 0.02,
|
|
2074
|
+
hipZRotation: wave * 0.05,
|
|
2075
|
+
upper: { x: 0, y: wave * 0.12 },
|
|
2076
|
+
headX: -0.95,
|
|
2077
|
+
arms: sides.map((side) => ({ shoulder: -2.55 + side * wave * 0.45, shoulderZ: side * 0.25, elbow: -0.35 - Math.max(0, -side * wave) * 0.6 })),
|
|
2078
|
+
legs: [
|
|
2079
|
+
{ hip: 0.1, knee: 0 },
|
|
2080
|
+
{ hip: 0.12 + wave * 0.08, knee: 0.25 }
|
|
2081
|
+
]
|
|
2082
|
+
};
|
|
2083
|
+
};
|
|
2084
|
+
var deadPose = (armSides) => ({
|
|
2085
|
+
figTilt: -1.45,
|
|
2086
|
+
hipY: 0.22,
|
|
2087
|
+
headY: 0.5,
|
|
2088
|
+
arms: armSides.map((side) => ({ shoulder: -0.2, shoulderZ: side * 1.1, elbow: -0.3 })),
|
|
2089
|
+
legs: [-0.15, 0.15].map((hipZ) => ({ hip: 0, hipZ, knee: 0.1 }))
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
// src/condition/timeline.ts
|
|
2093
|
+
var phaseIndexAt = (phases, units) => {
|
|
2094
|
+
let found = 0;
|
|
2095
|
+
phases.forEach((phase, index) => {
|
|
2096
|
+
if (units >= phase.startsAt) found = index;
|
|
2097
|
+
});
|
|
2098
|
+
return found;
|
|
2099
|
+
};
|
|
2100
|
+
var mixEffects = (from, to, amount) => {
|
|
2101
|
+
const result = {};
|
|
2102
|
+
for (const name of /* @__PURE__ */ new Set([...Object.keys(from ?? {}), ...Object.keys(to ?? {})])) {
|
|
2103
|
+
const start = from?.[name] ?? 0;
|
|
2104
|
+
result[name] = start + ((to?.[name] ?? 0) - start) * amount;
|
|
2105
|
+
}
|
|
2106
|
+
return result;
|
|
2107
|
+
};
|
|
2108
|
+
var createConditionTimeline = (options) => {
|
|
2109
|
+
const { phases, unitsPerSecond = 1, blend = false } = options;
|
|
2110
|
+
if (phases.length === 0) throw new Error("createConditionTimeline: at least one phase is required");
|
|
2111
|
+
let units = 0;
|
|
2112
|
+
const sample = () => {
|
|
2113
|
+
const index = phaseIndexAt(phases, units);
|
|
2114
|
+
const phase = phases[index];
|
|
2115
|
+
const next = phases[index + 1];
|
|
2116
|
+
const progress = next ? Math.min(1, (units - phase.startsAt) / (next.startsAt - phase.startsAt)) : 1;
|
|
2117
|
+
const effects = blend && next ? mixEffects(phase.effects, next.effects, progress) : { ...phase.effects };
|
|
2118
|
+
return { index, phase, progress, effects, isFinal: !next };
|
|
2119
|
+
};
|
|
2120
|
+
return {
|
|
2121
|
+
get elapsed() {
|
|
2122
|
+
return units;
|
|
2123
|
+
},
|
|
2124
|
+
advance(seconds) {
|
|
2125
|
+
units += seconds * unitsPerSecond;
|
|
2126
|
+
return sample();
|
|
2127
|
+
},
|
|
2128
|
+
seek(target) {
|
|
2129
|
+
units = Math.max(0, target);
|
|
2130
|
+
return sample();
|
|
2131
|
+
},
|
|
2132
|
+
sample,
|
|
2133
|
+
reset() {
|
|
2134
|
+
units = 0;
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
// src/data/appearance.json
|
|
2140
|
+
var appearance_default = {
|
|
2141
|
+
variance: {
|
|
2142
|
+
height: {
|
|
2143
|
+
gaussianSpread: 0.09,
|
|
2144
|
+
min: 0.9,
|
|
2145
|
+
max: 1.1
|
|
2146
|
+
},
|
|
2147
|
+
buildBands: [
|
|
2148
|
+
{
|
|
2149
|
+
below: 0.3,
|
|
2150
|
+
base: 0.85,
|
|
2151
|
+
range: 0.1
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
below: 0.8,
|
|
2155
|
+
base: 0.95,
|
|
2156
|
+
range: 0.13
|
|
2157
|
+
},
|
|
2158
|
+
{
|
|
2159
|
+
base: 1.08,
|
|
2160
|
+
range: 0.17
|
|
2161
|
+
}
|
|
2162
|
+
],
|
|
2163
|
+
buildSpeed: {
|
|
2164
|
+
lean: 1.05,
|
|
2165
|
+
heavy: 0.9
|
|
2166
|
+
},
|
|
2167
|
+
buildHealth: {
|
|
2168
|
+
lean: 0.9,
|
|
2169
|
+
heavy: 1.15
|
|
2170
|
+
},
|
|
2171
|
+
speedJitter: {
|
|
2172
|
+
base: 0.92,
|
|
2173
|
+
range: 0.16
|
|
2174
|
+
},
|
|
2175
|
+
wanderJitter: {
|
|
2176
|
+
base: 0.8,
|
|
2177
|
+
range: 0.4
|
|
2178
|
+
},
|
|
2179
|
+
runnerAgeClamp: 1,
|
|
2180
|
+
variantCount: {
|
|
2181
|
+
walker: 4,
|
|
2182
|
+
runner: 3,
|
|
2183
|
+
crawler: 2,
|
|
2184
|
+
brute: 2
|
|
2185
|
+
},
|
|
2186
|
+
variantSeedBase: 7e3
|
|
2187
|
+
},
|
|
2188
|
+
civilianColors: [
|
|
2189
|
+
"6D5A4A",
|
|
2190
|
+
"4D5D6B",
|
|
2191
|
+
"7A3B33",
|
|
2192
|
+
"5B6B4A",
|
|
2193
|
+
"8A7A5A",
|
|
2194
|
+
"3E4A5C",
|
|
2195
|
+
"6A4A6A",
|
|
2196
|
+
"9A8A70",
|
|
2197
|
+
"40403A"
|
|
2198
|
+
],
|
|
2199
|
+
corpses: {
|
|
2200
|
+
max: 400,
|
|
2201
|
+
lifeSeconds: 900
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
// src/appearance/variance.ts
|
|
2206
|
+
var { variance } = appearance_default;
|
|
2207
|
+
var clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
2208
|
+
var drawBodyVariance = (random, scale = {}) => {
|
|
2209
|
+
const spread = (random() + random() + random() - 1.5) * variance.height.gaussianSpread;
|
|
2210
|
+
const height = clamp(1 + spread, variance.height.min, variance.height.max) * (scale.height ?? 1);
|
|
2211
|
+
const band = random();
|
|
2212
|
+
const bands = variance.buildBands;
|
|
2213
|
+
const found = bands.find((candidate) => candidate.below !== void 0 && band < candidate.below) ?? bands[bands.length - 1];
|
|
2214
|
+
const { base, range } = found;
|
|
2215
|
+
const build = (base + random() * range) * (scale.build ?? 1);
|
|
2216
|
+
const buildClass = band < 0.3 ? "lean" : band >= 0.8 ? "heavy" : "average";
|
|
2217
|
+
const speedBuild = buildClass === "lean" ? variance.buildSpeed.lean : buildClass === "heavy" ? variance.buildSpeed.heavy : 1;
|
|
2218
|
+
const healthBuild = buildClass === "lean" ? variance.buildHealth.lean : buildClass === "heavy" ? variance.buildHealth.heavy : 1;
|
|
2219
|
+
return {
|
|
2220
|
+
height,
|
|
2221
|
+
build,
|
|
2222
|
+
buildClass,
|
|
2223
|
+
speedFactor: speedBuild * (variance.speedJitter.base + random() * variance.speedJitter.range),
|
|
2224
|
+
wanderFactor: variance.wanderJitter.base + random() * variance.wanderJitter.range,
|
|
2225
|
+
healthFactor: healthBuild
|
|
2226
|
+
};
|
|
2227
|
+
};
|
|
2228
|
+
var variantCountFor = (typeId) => variance.variantCount[typeId] ?? 1;
|
|
2229
|
+
var variantSeed = (typeIndex, variant) => variance.variantSeedBase + typeIndex * 101 + variant * 13;
|
|
2230
|
+
var variantDecay = (variant) => variant % 2 === 0 ? 1 : 2;
|
|
2231
|
+
|
|
2232
|
+
// src/appearance/palette.ts
|
|
2233
|
+
var palette_exports = {};
|
|
2234
|
+
__export(palette_exports, {
|
|
2235
|
+
BAG_COLORS: () => BAG_COLORS,
|
|
2236
|
+
BLOOD_COLORS: () => BLOOD_COLORS,
|
|
2237
|
+
CAP_COLORS: () => CAP_COLORS,
|
|
2238
|
+
DECAY_SKIN: () => DECAY_SKIN,
|
|
2239
|
+
GRAY: () => GRAY,
|
|
2240
|
+
HAIR_COLORS: () => HAIR_COLORS,
|
|
2241
|
+
HAIR_STYLES_FEMALE: () => HAIR_STYLES_FEMALE,
|
|
2242
|
+
HAIR_STYLES_MALE: () => HAIR_STYLES_MALE,
|
|
2243
|
+
OLD_HAIR: () => OLD_HAIR,
|
|
2244
|
+
PANTS_COLORS: () => PANTS_COLORS,
|
|
2245
|
+
RED: () => RED,
|
|
2246
|
+
SHOE_COLORS: () => SHOE_COLORS,
|
|
2247
|
+
SKIN_DECAY_BLEND: () => SKIN_DECAY_BLEND,
|
|
2248
|
+
SKIN_TONES: () => SKIN_TONES,
|
|
2249
|
+
SLEEVES: () => SLEEVES,
|
|
2250
|
+
TOP_COLORS: () => TOP_COLORS,
|
|
2251
|
+
UNIFORM_PANTS: () => UNIFORM_PANTS,
|
|
2252
|
+
UNIFORM_SHIRTS: () => UNIFORM_SHIRTS,
|
|
2253
|
+
UNIFORM_SHOES: () => UNIFORM_SHOES
|
|
2254
|
+
});
|
|
2255
|
+
var SKIN_TONES = [15845797, 14724224, 13012066, 11039050, 9067066, 6175526];
|
|
2256
|
+
var TOP_COLORS = [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162, 12104354, 2894896, 10111530, 3104099, 13216074, 8007242, 14210248, 3820138];
|
|
2257
|
+
var PANTS_COLORS = [2896704, 2040360, 4866096, 5922662, 3820138, 6969920, 2767402];
|
|
2258
|
+
var SLEEVES = ["short", "short", "long", "tank"];
|
|
2259
|
+
var HAIR_STYLES_FEMALE = ["long", "long", "bun", "short", "afro", "bald"];
|
|
2260
|
+
var HAIR_STYLES_MALE = ["short", "short", "buzz", "bald", "afro", "long"];
|
|
2261
|
+
var CAP_COLORS = [9321275, 2894896, 3820138, 13216074];
|
|
2262
|
+
var BAG_COLORS = [3820090, 5909028, 2896704];
|
|
2263
|
+
var HAIR_COLORS = [1709072, 2760214, 3877920, 6965802, 11569744, 8006170];
|
|
2264
|
+
var SHOE_COLORS = [1908514, 3811866, 12104872, 4856346];
|
|
2265
|
+
var DECAY_SKIN = [13482920, 9740420, 8090698, 9206878];
|
|
2266
|
+
var UNIFORM_SHIRTS = [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162];
|
|
2267
|
+
var UNIFORM_PANTS = 2896704;
|
|
2268
|
+
var UNIFORM_SHOES = 1908514;
|
|
2269
|
+
var SKIN_DECAY_BLEND = [0.45, 0.65, 0.8, 0.88];
|
|
2270
|
+
var GRAY = 4868678;
|
|
2271
|
+
var RED = 8000012;
|
|
2272
|
+
var BLOOD_COLORS = [10361618, 7214096, 4860440, 1840658];
|
|
2273
|
+
var OLD_HAIR = 9078656;
|
|
2274
|
+
|
|
2275
|
+
// src/appearance/traits.ts
|
|
2276
|
+
var BLOOD_SEED_OFFSET = 99;
|
|
2277
|
+
var createBloodStream = (seed) => createRng(seed + BLOOD_SEED_OFFSET);
|
|
2278
|
+
var FEMALE_SHARE = 0.42;
|
|
2279
|
+
var BEARD_SHARE = 0.25;
|
|
2280
|
+
var GLASSES_SHARE = 0.12;
|
|
2281
|
+
var CAP_SHARE = 0.12;
|
|
2282
|
+
var BAG_SHARE = 0.15;
|
|
2283
|
+
var TORN_SHARE = 0.35;
|
|
2284
|
+
var APRON_SHARE = 0.05;
|
|
2285
|
+
var drawAppearance = (params) => {
|
|
2286
|
+
const { seed, outfit = {}, uniform = false, typeIndex = 0, human = false, life = 1 } = params;
|
|
2287
|
+
const random = createRng(seed);
|
|
2288
|
+
const pick = (options) => options[Math.floor(random() * options.length)];
|
|
2289
|
+
const female = !uniform && (params.allowFemale ?? true) && (params.female ?? random() < FEMALE_SHARE);
|
|
2290
|
+
const tone = params.tone ?? (uniform ? DECAY_SKIN[0] : pick(SKIN_TONES));
|
|
2291
|
+
const topColor = outfit.cloth ?? (uniform ? UNIFORM_SHIRTS[(params.clothIndex ?? typeIndex) % UNIFORM_SHIRTS.length] : pick(TOP_COLORS));
|
|
2292
|
+
const pantsColor = outfit.pants ?? (uniform ? UNIFORM_PANTS : pick(PANTS_COLORS));
|
|
2293
|
+
const look = uniform ? uniformLook() : drawLook({ random, pick, female, outfit, human, params });
|
|
2294
|
+
const hairColor = params.hairColor ?? (life === 2 ? OLD_HAIR : pick(HAIR_COLORS));
|
|
2295
|
+
const shoeColor = uniform ? UNIFORM_SHOES : pick(SHOE_COLORS);
|
|
2296
|
+
return { traits: { female, tone, topColor, pantsColor, ...look, hairColor, shoeColor }, random };
|
|
2297
|
+
};
|
|
2298
|
+
var uniformLook = () => ({
|
|
2299
|
+
sleeve: "short",
|
|
2300
|
+
hair: "short",
|
|
2301
|
+
headWidth: 1,
|
|
2302
|
+
headHeight: 1,
|
|
2303
|
+
jaw: 1,
|
|
2304
|
+
nose: 1,
|
|
2305
|
+
beard: false,
|
|
2306
|
+
glasses: false,
|
|
2307
|
+
cap: false,
|
|
2308
|
+
capColor: 0,
|
|
2309
|
+
bag: false,
|
|
2310
|
+
bagColor: 0,
|
|
2311
|
+
torn: false,
|
|
2312
|
+
apron: false
|
|
2313
|
+
});
|
|
2314
|
+
var drawLook = ({ random, pick, female, outfit, human, params }) => {
|
|
2315
|
+
const sleeve = outfit.cloth ? "long" : pick(SLEEVES);
|
|
2316
|
+
const hair = params.hairStyle ?? (params.wet ? "long" : params.charred ? "bald" : pick(female ? HAIR_STYLES_FEMALE : HAIR_STYLES_MALE));
|
|
2317
|
+
const headWidth = 1 + (random() - 0.5) * 0.16;
|
|
2318
|
+
const headHeight = 1 + (random() - 0.5) * 0.14;
|
|
2319
|
+
const jaw = 1 + (random() - 0.5) * 0.3;
|
|
2320
|
+
const nose = 1 + (random() - 0.5) * 0.5;
|
|
2321
|
+
const beard = !female && random() < BEARD_SHARE;
|
|
2322
|
+
const glasses = random() < GLASSES_SHARE;
|
|
2323
|
+
const cap = !outfit.hat && random() < CAP_SHARE;
|
|
2324
|
+
const capColor = pick(CAP_COLORS);
|
|
2325
|
+
const bag = random() < BAG_SHARE && !human;
|
|
2326
|
+
const bagColor = pick(BAG_COLORS);
|
|
2327
|
+
const torn = random() < TORN_SHARE && !human;
|
|
2328
|
+
const apron = random() < APRON_SHARE;
|
|
2329
|
+
return { sleeve, hair, headWidth, headHeight, jaw, nose, beard, glasses, cap, capColor, bag, bagColor, torn, apron };
|
|
2330
|
+
};
|
|
2331
|
+
export {
|
|
2332
|
+
ARSENAL_RULES,
|
|
2333
|
+
palette_exports as AppearancePalette,
|
|
2334
|
+
BLOOD_LITERS_PER_DROP,
|
|
2335
|
+
BLOOD_SEED_OFFSET,
|
|
2336
|
+
BODY_MATERIAL,
|
|
2337
|
+
BODY_REGIONS,
|
|
2338
|
+
CASCADE,
|
|
2339
|
+
CRAWL_TILT_RATE,
|
|
2340
|
+
DAMAGE_PRESETS,
|
|
2341
|
+
DEAD_TILT_RATE,
|
|
2342
|
+
DEFAULT_CORPSES,
|
|
2343
|
+
DEFAULT_DAMAGE_RULES,
|
|
2344
|
+
ELENCO_RULES,
|
|
2345
|
+
GAME_BONES,
|
|
2346
|
+
GUIDE,
|
|
2347
|
+
INSTANCE_POSES,
|
|
2348
|
+
JOINT_IDS,
|
|
2349
|
+
JOINT_PARENTS,
|
|
2350
|
+
LAYERED_PROTECTION_CAP,
|
|
2351
|
+
LIMB_REGIONS,
|
|
2352
|
+
LIMP,
|
|
2353
|
+
MOVES,
|
|
2354
|
+
MOVE_BLEND_RATE,
|
|
2355
|
+
PENETRATION_MODELS,
|
|
2356
|
+
PERSON_PRESET,
|
|
2357
|
+
PHYSICAL_PENETRATION,
|
|
2358
|
+
PROTECTION_PRESETS,
|
|
2359
|
+
REGION_HIT_POINTS,
|
|
2360
|
+
REGION_SEGMENTS,
|
|
2361
|
+
SEGMENT_IDS,
|
|
2362
|
+
SEGMENT_NUMBERS,
|
|
2363
|
+
SEGMENT_TREE,
|
|
2364
|
+
TEAM_GAME,
|
|
2365
|
+
TEAM_GAME_CALIBERS,
|
|
2366
|
+
TEAM_GAME_CALIBER_KEYS,
|
|
2367
|
+
TEAM_GAME_PENETRATION,
|
|
2368
|
+
TORSO_RESISTANCE,
|
|
2369
|
+
animateProfile,
|
|
2370
|
+
applyPatches,
|
|
2371
|
+
applyPreset,
|
|
2372
|
+
bleedRateAt,
|
|
2373
|
+
blendMoves,
|
|
2374
|
+
crawlPose,
|
|
2375
|
+
createBloodStream,
|
|
2376
|
+
createBodySpec,
|
|
2377
|
+
createCharacters,
|
|
2378
|
+
createConditionTimeline,
|
|
2379
|
+
createCorpseLedger,
|
|
2380
|
+
createRig,
|
|
2381
|
+
createRng,
|
|
2382
|
+
cutOf,
|
|
2383
|
+
deadPose,
|
|
2384
|
+
decodePoseHeight,
|
|
2385
|
+
describeBody,
|
|
2386
|
+
drawAppearance,
|
|
2387
|
+
drawBodyVariance,
|
|
2388
|
+
emptySample,
|
|
2389
|
+
encodePoseHeight,
|
|
2390
|
+
energyKept,
|
|
2391
|
+
humanPose,
|
|
2392
|
+
isBodyRegion,
|
|
2393
|
+
isSegmentId,
|
|
2394
|
+
layeredProtection,
|
|
2395
|
+
layersOn,
|
|
2396
|
+
lerpPose,
|
|
2397
|
+
mergeById,
|
|
2398
|
+
moveBlendOf,
|
|
2399
|
+
penetrateLine,
|
|
2400
|
+
penetrationForce,
|
|
2401
|
+
pointsFromEnergy,
|
|
2402
|
+
projectileOf,
|
|
2403
|
+
protect,
|
|
2404
|
+
removeIds,
|
|
2405
|
+
resolveDamageRules,
|
|
2406
|
+
resolvePoints,
|
|
2407
|
+
segmentNode,
|
|
2408
|
+
segmentResistance,
|
|
2409
|
+
segmentsBelow,
|
|
2410
|
+
spreadPoints,
|
|
2411
|
+
stepCascade,
|
|
2412
|
+
strideFor,
|
|
2413
|
+
teamGameCost,
|
|
2414
|
+
teamGameFalloff,
|
|
2415
|
+
variantCountFor,
|
|
2416
|
+
variantDecay,
|
|
2417
|
+
variantSeed,
|
|
2418
|
+
walkCadence,
|
|
2419
|
+
walkGait
|
|
2420
|
+
};
|
|
2421
|
+
//# sourceMappingURL=index.js.map
|