@adatechnology/game-test-room 0.28.2 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11226,7 +11226,8 @@ var GameTestRoom = (() => {
11226
11226
  var EVENT_BUDGET = { particles: 400, debris: 40, blastLights: 4, fireLights: 2 };
11227
11227
  var explosion_aliases_default = {
11228
11228
  carl_gustaf_84mm: "at4_84mm",
11229
- vog_25: "m79_40mm"
11229
+ vog_25: "m79_40mm",
11230
+ semtex: "c4"
11230
11231
  };
11231
11232
  var explosions_default = {
11232
11233
  m67: {
@@ -26417,6 +26418,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
26417
26418
  kick: "kick",
26418
26419
  shove: "shove",
26419
26420
  knife: "blade",
26421
+ kabar: "blade",
26422
+ katana: "blade",
26423
+ brass_knuckles: "punch",
26420
26424
  machete: "blade",
26421
26425
  utility_knife: "blade",
26422
26426
  axe: "chop",
@@ -27468,6 +27472,60 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
27468
27472
  sources: [
27469
27473
  "Estimated from muzzle energy, bullet construction and the published anchors of this file (see _notes); refine with a datasheet."
27470
27474
  ]
27475
+ },
27476
+ {
27477
+ id: "7_62x25:fmj",
27478
+ caliber: "7_62x25",
27479
+ kind: "fmj",
27480
+ label: "7.62x25 Tokarev FMJ",
27481
+ core: "lead",
27482
+ jacket: "steel",
27483
+ penetration: {
27484
+ steel: 3.5,
27485
+ concrete: 30,
27486
+ brick: 60,
27487
+ wood: 100
27488
+ },
27489
+ basis: "estimated",
27490
+ sources: [
27491
+ "Estimated from muzzle energy, bullet construction and the published anchors of this file (see _notes); refine with a datasheet."
27492
+ ]
27493
+ },
27494
+ {
27495
+ id: "7_92x57:fmj",
27496
+ caliber: "7_92x57",
27497
+ kind: "fmj",
27498
+ label: "7.92x57 s.S. ball",
27499
+ core: "lead",
27500
+ jacket: "gilding_metal",
27501
+ penetration: {
27502
+ steel: 5.5,
27503
+ concrete: 125,
27504
+ brick: 250,
27505
+ wood: 330
27506
+ },
27507
+ basis: "estimated",
27508
+ sources: [
27509
+ "Estimated from muzzle energy, bullet construction and the published anchors of this file (see _notes); refine with a datasheet."
27510
+ ]
27511
+ },
27512
+ {
27513
+ id: "9x39:fmj",
27514
+ caliber: "9x39",
27515
+ kind: "fmj",
27516
+ label: "9x39 SP-5",
27517
+ core: "lead",
27518
+ jacket: "gilding_metal",
27519
+ penetration: {
27520
+ steel: 3,
27521
+ concrete: 30,
27522
+ brick: 70,
27523
+ wood: 130
27524
+ },
27525
+ basis: "estimated",
27526
+ sources: [
27527
+ "Estimated from muzzle energy, bullet construction and the published anchors of this file (see _notes); refine with a datasheet."
27528
+ ]
27471
27529
  }
27472
27530
  ]
27473
27531
  };
@@ -30140,6 +30198,72 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
30140
30198
  sources: [
30141
30199
  "sdk"
30142
30200
  ]
30201
+ },
30202
+ {
30203
+ id: "7_62x25",
30204
+ label: "7.62\xD725 mm Tokarev",
30205
+ diameter: 7.85,
30206
+ bulletGrams: 5.5,
30207
+ muzzleSpeed: 488,
30208
+ dragPer100m: 0.12,
30209
+ kind: "fmj",
30210
+ noise: 300,
30211
+ caseType: "pistol",
30212
+ rim: "rimless",
30213
+ cartridge: {
30214
+ caseDiameter: 9.95,
30215
+ overallLength: 35.2,
30216
+ caseLength: 25,
30217
+ projectileDiameter: 7.85
30218
+ },
30219
+ sources: [
30220
+ "reference",
30221
+ "derived"
30222
+ ]
30223
+ },
30224
+ {
30225
+ id: "7_92x57",
30226
+ label: "7.92\xD757 mm Mauser",
30227
+ diameter: 8.22,
30228
+ bulletGrams: 11.7,
30229
+ muzzleSpeed: 760,
30230
+ dragPer100m: 0.08,
30231
+ kind: "fmj",
30232
+ noise: 420,
30233
+ caseType: "rifle",
30234
+ rim: "rimless",
30235
+ cartridge: {
30236
+ caseDiameter: 11.95,
30237
+ overallLength: 82,
30238
+ caseLength: 57,
30239
+ projectileDiameter: 8.22
30240
+ },
30241
+ sources: [
30242
+ "reference",
30243
+ "derived"
30244
+ ]
30245
+ },
30246
+ {
30247
+ id: "9x39",
30248
+ label: "9\xD739 mm SP-5",
30249
+ diameter: 9.27,
30250
+ bulletGrams: 16.1,
30251
+ muzzleSpeed: 290,
30252
+ dragPer100m: 0.2,
30253
+ kind: "fmj",
30254
+ noise: 300,
30255
+ caseType: "rifle",
30256
+ rim: "rimless",
30257
+ cartridge: {
30258
+ caseDiameter: 11.33,
30259
+ overallLength: 56,
30260
+ caseLength: 38.7,
30261
+ projectileDiameter: 9.27
30262
+ },
30263
+ sources: [
30264
+ "reference",
30265
+ "derived"
30266
+ ]
30143
30267
  }
30144
30268
  ];
30145
30269
  var explosives_default = [
@@ -30594,6 +30718,59 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
30594
30718
  "lab",
30595
30719
  "derived"
30596
30720
  ]
30721
+ },
30722
+ {
30723
+ id: "semtex",
30724
+ name: "Semtex charge",
30725
+ kind: "blast",
30726
+ lethalRadius: 6,
30727
+ injuryRadius: 18,
30728
+ energy: 377400,
30729
+ tnt: 0.83,
30730
+ destructionLevel: 3,
30731
+ noise: 700,
30732
+ fuseLength: 0.23,
30733
+ burnRate: 131,
30734
+ burnTolerance: 0.1,
30735
+ triggers: [
30736
+ "remote",
30737
+ "timer"
30738
+ ],
30739
+ mass: 0.5,
30740
+ sources: [
30741
+ "lab",
30742
+ "reference",
30743
+ "derived"
30744
+ ]
30745
+ },
30746
+ {
30747
+ id: "smoke_grenade",
30748
+ name: "M18 colored smoke grenade",
30749
+ kind: "smoke",
30750
+ lethalRadius: 0,
30751
+ injuryRadius: 0,
30752
+ energy: 0,
30753
+ destructionLevel: 0,
30754
+ noise: 30,
30755
+ fuseRange: [
30756
+ 1,
30757
+ 2.3
30758
+ ],
30759
+ smoke: {
30760
+ color: 4177738,
30761
+ seconds: [
30762
+ 50,
30763
+ 90
30764
+ ]
30765
+ },
30766
+ triggers: [
30767
+ "timer"
30768
+ ],
30769
+ mass: 0.54,
30770
+ sources: [
30771
+ "reference",
30772
+ "derived"
30773
+ ]
30597
30774
  }
30598
30775
  ];
30599
30776
  var explosive_weapons_default = [
@@ -30695,6 +30872,54 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
30695
30872
  "lab",
30696
30873
  "reference"
30697
30874
  ]
30875
+ },
30876
+ {
30877
+ id: "semtex",
30878
+ name: "Semtex charge",
30879
+ category: "throwable",
30880
+ action: {
30881
+ kind: "throwable",
30882
+ explosive: "semtex",
30883
+ energy: 0,
30884
+ radius: 0,
30885
+ fuse: 0,
30886
+ throwSpeed: 0,
30887
+ noise: 0
30888
+ },
30889
+ mass: 0.5,
30890
+ tags: [
30891
+ "explosive",
30892
+ "placed"
30893
+ ],
30894
+ archetype: "frag_grenade",
30895
+ sources: [
30896
+ "reference",
30897
+ "derived"
30898
+ ]
30899
+ },
30900
+ {
30901
+ id: "smoke_grenade",
30902
+ name: "M18 colored smoke grenade",
30903
+ category: "throwable",
30904
+ action: {
30905
+ kind: "throwable",
30906
+ explosive: "smoke_grenade",
30907
+ energy: 0,
30908
+ radius: 0,
30909
+ fuse: 0,
30910
+ throwSpeed: 15,
30911
+ noise: 0
30912
+ },
30913
+ mass: 0.54,
30914
+ tags: [
30915
+ "explosive",
30916
+ "smoke"
30917
+ ],
30918
+ archetype: "frag_grenade",
30919
+ sources: [
30920
+ "reference",
30921
+ "derived"
30922
+ ]
30698
30923
  }
30699
30924
  ];
30700
30925
  var firearms_default = [
@@ -37758,8 +37983,1509 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37758
37983
  barrel: 0.59,
37759
37984
  emptyMass: 4.8
37760
37985
  },
37761
- archetype: "rifle_7_62x39",
37762
- variantOf: "ak_47",
37986
+ archetype: "rifle_7_62x39",
37987
+ variantOf: "ak_47",
37988
+ sources: [
37989
+ "reference",
37990
+ "derived",
37991
+ "sdk"
37992
+ ],
37993
+ mounts: [
37994
+ {
37995
+ kind: "muzzle",
37996
+ interfaces: [
37997
+ "thread_14x1lh"
37998
+ ],
37999
+ sources: [
38000
+ "derived"
38001
+ ]
38002
+ },
38003
+ {
38004
+ kind: "rail_bottom",
38005
+ interfaces: [
38006
+ "bipod_stud"
38007
+ ],
38008
+ sources: [
38009
+ "gallery"
38010
+ ]
38011
+ },
38012
+ {
38013
+ kind: "magazine",
38014
+ interfaces: [
38015
+ "mag_curved"
38016
+ ],
38017
+ sources: [
38018
+ "gallery"
38019
+ ]
38020
+ },
38021
+ {
38022
+ kind: "sling",
38023
+ interfaces: [
38024
+ "swivel"
38025
+ ],
38026
+ sources: [
38027
+ "derived"
38028
+ ]
38029
+ }
38030
+ ],
38031
+ fitted: [
38032
+ {
38033
+ id: "bipod",
38034
+ mount: "rail_bottom"
38035
+ }
38036
+ ]
38037
+ },
38038
+ {
38039
+ id: "sig_mpx",
38040
+ name: "SIG MPX",
38041
+ category: "smg",
38042
+ action: {
38043
+ kind: "firearm",
38044
+ caliber: "9x19",
38045
+ rpm: 850,
38046
+ modes: [
38047
+ "auto",
38048
+ "semi"
38049
+ ],
38050
+ magazine: 30,
38051
+ magazineOptions: [
38052
+ 20,
38053
+ 35
38054
+ ],
38055
+ reload: 2.6,
38056
+ spreadDeg: 1.5,
38057
+ bloomDeg: 0.35,
38058
+ recoveryDegPerS: 6,
38059
+ recoil: [
38060
+ 0.72,
38061
+ 0.4
38062
+ ],
38063
+ range: 80,
38064
+ family: "magazine",
38065
+ timings: {
38066
+ draw: 0.35,
38067
+ reload: 2.6,
38068
+ clear: 1.2
38069
+ }
38070
+ },
38071
+ mass: 2.7,
38072
+ dimensions: {
38073
+ length: 0.61,
38074
+ barrel: 0.203,
38075
+ emptyMass: 2.7,
38076
+ lengthCollapsed: 0.425
38077
+ },
38078
+ archetype: "smg_9mm",
38079
+ sources: [
38080
+ "reference",
38081
+ "derived",
38082
+ "sdk"
38083
+ ],
38084
+ mounts: [
38085
+ {
38086
+ kind: "rail_top",
38087
+ interfaces: [
38088
+ "picatinny"
38089
+ ],
38090
+ sources: [
38091
+ "gallery"
38092
+ ]
38093
+ },
38094
+ {
38095
+ kind: "rail_left",
38096
+ interfaces: [
38097
+ "picatinny"
38098
+ ],
38099
+ sources: [
38100
+ "gallery"
38101
+ ]
38102
+ },
38103
+ {
38104
+ kind: "rail_right",
38105
+ interfaces: [
38106
+ "picatinny"
38107
+ ],
38108
+ sources: [
38109
+ "gallery"
38110
+ ]
38111
+ },
38112
+ {
38113
+ kind: "rail_bottom",
38114
+ interfaces: [
38115
+ "picatinny"
38116
+ ],
38117
+ sources: [
38118
+ "gallery"
38119
+ ]
38120
+ },
38121
+ {
38122
+ kind: "magazine",
38123
+ interfaces: [
38124
+ "mag_box"
38125
+ ],
38126
+ sources: [
38127
+ "gallery"
38128
+ ]
38129
+ },
38130
+ {
38131
+ kind: "sling",
38132
+ interfaces: [
38133
+ "swivel"
38134
+ ],
38135
+ sources: [
38136
+ "derived"
38137
+ ]
38138
+ }
38139
+ ],
38140
+ fitted: [],
38141
+ stock: {
38142
+ kind: "retract"
38143
+ }
38144
+ },
38145
+ {
38146
+ id: "mg3",
38147
+ name: "MG3",
38148
+ category: "machine_gun",
38149
+ action: {
38150
+ kind: "firearm",
38151
+ caliber: "7_62x51",
38152
+ rpm: 1100,
38153
+ modes: [
38154
+ "auto"
38155
+ ],
38156
+ magazine: 100,
38157
+ reload: 8.5,
38158
+ muzzleSpeed: 820,
38159
+ spreadDeg: 0.8,
38160
+ bloomDeg: 0.35,
38161
+ recoveryDegPerS: 4.5,
38162
+ recoil: [
38163
+ 0.77,
38164
+ 0.41
38165
+ ],
38166
+ range: 350,
38167
+ family: "belt",
38168
+ timings: {
38169
+ draw: 0.35,
38170
+ reload: 8.5,
38171
+ clear: 1.2
38172
+ }
38173
+ },
38174
+ mass: 11.5,
38175
+ dimensions: {
38176
+ length: 1.225,
38177
+ barrel: 0.565,
38178
+ emptyMass: 11.5
38179
+ },
38180
+ archetype: "rifle_7_62x39",
38181
+ sources: [
38182
+ "reference",
38183
+ "derived",
38184
+ "sdk"
38185
+ ],
38186
+ mounts: [
38187
+ {
38188
+ kind: "rail_bottom",
38189
+ interfaces: [
38190
+ "bipod_stud"
38191
+ ],
38192
+ sources: [
38193
+ "gallery"
38194
+ ]
38195
+ },
38196
+ {
38197
+ kind: "sling",
38198
+ interfaces: [
38199
+ "swivel"
38200
+ ],
38201
+ sources: [
38202
+ "derived"
38203
+ ]
38204
+ },
38205
+ {
38206
+ kind: "feed",
38207
+ interfaces: [
38208
+ "belt"
38209
+ ],
38210
+ sources: [
38211
+ "derived"
38212
+ ]
38213
+ }
38214
+ ],
38215
+ fitted: [
38216
+ {
38217
+ id: "bipod",
38218
+ mount: "rail_bottom"
38219
+ }
38220
+ ]
38221
+ },
38222
+ {
38223
+ id: "pp19_bizon",
38224
+ name: "PP-19 Bizon",
38225
+ category: "smg",
38226
+ action: {
38227
+ kind: "firearm",
38228
+ caliber: "9x18",
38229
+ rpm: 680,
38230
+ modes: [
38231
+ "auto",
38232
+ "semi"
38233
+ ],
38234
+ magazine: 64,
38235
+ reload: 2.5,
38236
+ muzzleSpeed: 320,
38237
+ spreadDeg: 1.5,
38238
+ bloomDeg: 0.35,
38239
+ recoveryDegPerS: 6,
38240
+ recoil: [
38241
+ 0.58,
38242
+ 0.32
38243
+ ],
38244
+ range: 80,
38245
+ family: "magazine",
38246
+ timings: {
38247
+ draw: 0.35,
38248
+ reload: 2.5,
38249
+ clear: 1.2
38250
+ }
38251
+ },
38252
+ mass: 2.1,
38253
+ dimensions: {
38254
+ length: 0.66,
38255
+ barrel: 0.195,
38256
+ emptyMass: 2.1,
38257
+ lengthCollapsed: 0.425
38258
+ },
38259
+ archetype: "smg_9mm",
38260
+ sources: [
38261
+ "reference",
38262
+ "derived",
38263
+ "sdk"
38264
+ ],
38265
+ mounts: [
38266
+ {
38267
+ kind: "magazine",
38268
+ interfaces: [
38269
+ "mag_helical"
38270
+ ],
38271
+ sources: [
38272
+ "gallery"
38273
+ ]
38274
+ },
38275
+ {
38276
+ kind: "sling",
38277
+ interfaces: [
38278
+ "swivel"
38279
+ ],
38280
+ sources: [
38281
+ "derived"
38282
+ ]
38283
+ }
38284
+ ],
38285
+ fitted: [],
38286
+ stock: {
38287
+ kind: "fold",
38288
+ foldAxis: "left"
38289
+ }
38290
+ },
38291
+ {
38292
+ id: "vz61",
38293
+ name: "vz. 61 \u0160korpion",
38294
+ category: "smg",
38295
+ action: {
38296
+ kind: "firearm",
38297
+ caliber: "32acp",
38298
+ rpm: 850,
38299
+ modes: [
38300
+ "auto",
38301
+ "semi"
38302
+ ],
38303
+ magazine: 20,
38304
+ magazineOptions: [
38305
+ 10
38306
+ ],
38307
+ reload: 2.4,
38308
+ muzzleSpeed: 320,
38309
+ spreadDeg: 1.5,
38310
+ bloomDeg: 0.35,
38311
+ recoveryDegPerS: 6,
38312
+ recoil: [
38313
+ 0.7,
38314
+ 0.39
38315
+ ],
38316
+ range: 80,
38317
+ family: "magazine",
38318
+ timings: {
38319
+ draw: 0.35,
38320
+ reload: 2.4,
38321
+ clear: 1.2
38322
+ }
38323
+ },
38324
+ mass: 1.3,
38325
+ dimensions: {
38326
+ length: 0.517,
38327
+ barrel: 0.115,
38328
+ emptyMass: 1.3,
38329
+ lengthCollapsed: 0.27
38330
+ },
38331
+ archetype: "smg_9mm",
38332
+ sources: [
38333
+ "reference",
38334
+ "derived",
38335
+ "sdk"
38336
+ ],
38337
+ mounts: [
38338
+ {
38339
+ kind: "magazine",
38340
+ interfaces: [
38341
+ "mag_curved"
38342
+ ],
38343
+ sources: [
38344
+ "gallery"
38345
+ ]
38346
+ },
38347
+ {
38348
+ kind: "sling",
38349
+ interfaces: [
38350
+ "swivel"
38351
+ ],
38352
+ sources: [
38353
+ "derived"
38354
+ ]
38355
+ }
38356
+ ],
38357
+ fitted: [],
38358
+ stock: {
38359
+ kind: "fold",
38360
+ foldAxis: "over"
38361
+ }
38362
+ },
38363
+ {
38364
+ id: "m134",
38365
+ name: "M134 Minigun",
38366
+ category: "machine_gun",
38367
+ action: {
38368
+ kind: "firearm",
38369
+ caliber: "7_62x51",
38370
+ rpm: 3e3,
38371
+ modes: [
38372
+ "auto"
38373
+ ],
38374
+ magazine: 200,
38375
+ reload: 8.5,
38376
+ muzzleSpeed: 853,
38377
+ spin: {
38378
+ upSeconds: 0.5,
38379
+ holdSeconds: 1
38380
+ },
38381
+ rpmRange: [
38382
+ 2e3,
38383
+ 6e3
38384
+ ],
38385
+ spreadDeg: 0.8,
38386
+ bloomDeg: 0.35,
38387
+ recoveryDegPerS: 4.5,
38388
+ recoil: [
38389
+ 0.25,
38390
+ 0.13
38391
+ ],
38392
+ range: 350,
38393
+ family: "belt",
38394
+ timings: {
38395
+ draw: 0.35,
38396
+ reload: 8.5,
38397
+ clear: 1.2
38398
+ }
38399
+ },
38400
+ mass: 39,
38401
+ dimensions: {
38402
+ length: 0.801,
38403
+ barrel: 0.559,
38404
+ emptyMass: 39,
38405
+ barrels: 6,
38406
+ barrelMass: 2
38407
+ },
38408
+ archetype: "rifle_7_62x39",
38409
+ sources: [
38410
+ "reference",
38411
+ "derived",
38412
+ "sdk"
38413
+ ],
38414
+ mounts: [
38415
+ {
38416
+ kind: "underbarrel",
38417
+ interfaces: [
38418
+ "tripod"
38419
+ ],
38420
+ sources: [
38421
+ "reference"
38422
+ ]
38423
+ },
38424
+ {
38425
+ kind: "sling",
38426
+ interfaces: [
38427
+ "swivel"
38428
+ ],
38429
+ sources: [
38430
+ "derived"
38431
+ ]
38432
+ },
38433
+ {
38434
+ kind: "feed",
38435
+ interfaces: [
38436
+ "belt"
38437
+ ],
38438
+ sources: [
38439
+ "derived"
38440
+ ]
38441
+ }
38442
+ ],
38443
+ fitted: []
38444
+ },
38445
+ {
38446
+ id: "aks_74u",
38447
+ name: "AKS-74U",
38448
+ category: "rifle",
38449
+ action: {
38450
+ kind: "firearm",
38451
+ caliber: "5_45x39",
38452
+ rpm: 700,
38453
+ modes: [
38454
+ "auto",
38455
+ "semi"
38456
+ ],
38457
+ magazine: 30,
38458
+ reload: 2.6,
38459
+ muzzleSpeed: 735,
38460
+ spreadDeg: 0.6,
38461
+ bloomDeg: 0.25,
38462
+ recoveryDegPerS: 5,
38463
+ recoil: [
38464
+ 0.66,
38465
+ 0.4
38466
+ ],
38467
+ range: 400,
38468
+ family: "magazine",
38469
+ timings: {
38470
+ draw: 0.35,
38471
+ reload: 2.6,
38472
+ reloadEmpty: 3.4,
38473
+ clear: 1.2
38474
+ }
38475
+ },
38476
+ mass: 2.7,
38477
+ dimensions: {
38478
+ length: 0.73,
38479
+ barrel: 0.206,
38480
+ emptyMass: 2.7,
38481
+ lengthCollapsed: 0.49
38482
+ },
38483
+ archetype: "rifle_5_56",
38484
+ variantOf: "ak_74",
38485
+ sources: [
38486
+ "reference",
38487
+ "derived",
38488
+ "sdk"
38489
+ ],
38490
+ mounts: [
38491
+ {
38492
+ kind: "muzzle",
38493
+ interfaces: [
38494
+ "fixed_muzzle_device"
38495
+ ],
38496
+ sources: [
38497
+ "derived"
38498
+ ]
38499
+ },
38500
+ {
38501
+ kind: "magazine",
38502
+ interfaces: [
38503
+ "mag_curved"
38504
+ ],
38505
+ sources: [
38506
+ "gallery"
38507
+ ]
38508
+ },
38509
+ {
38510
+ kind: "sling",
38511
+ interfaces: [
38512
+ "swivel"
38513
+ ],
38514
+ sources: [
38515
+ "derived"
38516
+ ]
38517
+ }
38518
+ ],
38519
+ fitted: [
38520
+ {
38521
+ id: "muzzle_brake",
38522
+ mount: "muzzle"
38523
+ }
38524
+ ],
38525
+ stock: {
38526
+ kind: "fold",
38527
+ foldAxis: "left"
38528
+ }
38529
+ },
38530
+ {
38531
+ id: "fn_scar_l",
38532
+ name: "FN SCAR-L",
38533
+ maker: "FN",
38534
+ category: "dmr",
38535
+ action: {
38536
+ kind: "firearm",
38537
+ caliber: "5_56x45",
38538
+ rpm: 625,
38539
+ modes: [
38540
+ "auto",
38541
+ "semi"
38542
+ ],
38543
+ magazine: 30,
38544
+ reload: 2.6,
38545
+ muzzleSpeed: 870,
38546
+ spreadDeg: 0.25,
38547
+ bloomDeg: 0.3,
38548
+ recoveryDegPerS: 3,
38549
+ recoil: [
38550
+ 1.43,
38551
+ 0.24
38552
+ ],
38553
+ range: 800,
38554
+ family: "magazine",
38555
+ timings: {
38556
+ draw: 0.35,
38557
+ reload: 2.6,
38558
+ clear: 1.2
38559
+ }
38560
+ },
38561
+ mass: 3.29,
38562
+ dimensions: {
38563
+ length: 0.889,
38564
+ barrel: 0.351,
38565
+ emptyMass: 3.29,
38566
+ lengthCollapsed: 0.635
38567
+ },
38568
+ archetype: "rifle_bolt_308",
38569
+ variantOf: "fn_scar_h",
38570
+ sources: [
38571
+ "reference",
38572
+ "derived",
38573
+ "sdk"
38574
+ ],
38575
+ mounts: [
38576
+ {
38577
+ kind: "rail_top",
38578
+ interfaces: [
38579
+ "picatinny"
38580
+ ],
38581
+ sources: [
38582
+ "gallery"
38583
+ ]
38584
+ },
38585
+ {
38586
+ kind: "rail_left",
38587
+ interfaces: [
38588
+ "picatinny"
38589
+ ],
38590
+ sources: [
38591
+ "gallery"
38592
+ ]
38593
+ },
38594
+ {
38595
+ kind: "rail_right",
38596
+ interfaces: [
38597
+ "picatinny"
38598
+ ],
38599
+ sources: [
38600
+ "gallery"
38601
+ ]
38602
+ },
38603
+ {
38604
+ kind: "rail_bottom",
38605
+ interfaces: [
38606
+ "picatinny"
38607
+ ],
38608
+ sources: [
38609
+ "gallery"
38610
+ ]
38611
+ },
38612
+ {
38613
+ kind: "magazine",
38614
+ interfaces: [
38615
+ "mag_box"
38616
+ ],
38617
+ sources: [
38618
+ "gallery"
38619
+ ]
38620
+ },
38621
+ {
38622
+ kind: "sling",
38623
+ interfaces: [
38624
+ "swivel"
38625
+ ],
38626
+ sources: [
38627
+ "derived"
38628
+ ]
38629
+ }
38630
+ ],
38631
+ fitted: [
38632
+ {
38633
+ id: "acog_4x",
38634
+ mount: "rail_top"
38635
+ }
38636
+ ],
38637
+ stock: {
38638
+ kind: "fold",
38639
+ foldAxis: "right"
38640
+ }
38641
+ },
38642
+ {
38643
+ id: "m1_garand",
38644
+ name: "M1 Garand",
38645
+ category: "rifle",
38646
+ action: {
38647
+ kind: "firearm",
38648
+ caliber: "30_06",
38649
+ rpm: 120,
38650
+ modes: [
38651
+ "semi"
38652
+ ],
38653
+ magazine: 8,
38654
+ reload: 2.2,
38655
+ muzzleSpeed: 865,
38656
+ spreadDeg: 0.8,
38657
+ bloomDeg: 0.35,
38658
+ recoveryDegPerS: 4.5,
38659
+ recoil: [
38660
+ 2.28,
38661
+ 1.22
38662
+ ],
38663
+ range: 350,
38664
+ family: "top_load_semi",
38665
+ timings: {
38666
+ draw: 0.35,
38667
+ reload: 2.2,
38668
+ clear: 1.2
38669
+ }
38670
+ },
38671
+ mass: 4.31,
38672
+ dimensions: {
38673
+ length: 1.1,
38674
+ barrel: 0.61,
38675
+ emptyMass: 4.31
38676
+ },
38677
+ archetype: "rifle_7_62x39",
38678
+ variantOf: "sks",
38679
+ sources: [
38680
+ "reference",
38681
+ "derived",
38682
+ "sdk"
38683
+ ],
38684
+ mounts: [
38685
+ {
38686
+ kind: "sling",
38687
+ interfaces: [
38688
+ "swivel"
38689
+ ],
38690
+ sources: [
38691
+ "derived"
38692
+ ]
38693
+ }
38694
+ ],
38695
+ fitted: []
38696
+ },
38697
+ {
38698
+ id: "accuracy_l96a1",
38699
+ name: "Accuracy L96A1",
38700
+ maker: "Accuracy",
38701
+ category: "sniper",
38702
+ action: {
38703
+ kind: "firearm",
38704
+ caliber: "7_62x51",
38705
+ rpm: 73.17,
38706
+ modes: [
38707
+ "bolt"
38708
+ ],
38709
+ magazine: 10,
38710
+ reload: 3.2,
38711
+ muzzleSpeed: 840,
38712
+ spreadDeg: 0.25,
38713
+ bloomDeg: 0.3,
38714
+ recoveryDegPerS: 3,
38715
+ recoil: [
38716
+ 1.64,
38717
+ 0.27
38718
+ ],
38719
+ range: 800,
38720
+ family: "bolt_magazine",
38721
+ timings: {
38722
+ draw: 0.35,
38723
+ reload: 3.2,
38724
+ cycle: 0.82,
38725
+ clear: 1.2
38726
+ }
38727
+ },
38728
+ mass: 6.5,
38729
+ dimensions: {
38730
+ length: 1.18,
38731
+ barrel: 0.66,
38732
+ emptyMass: 6.5
38733
+ },
38734
+ archetype: "rifle_bolt_308",
38735
+ variantOf: "accuracy_awm",
38736
+ sources: [
38737
+ "reference",
38738
+ "derived",
38739
+ "sdk"
38740
+ ],
38741
+ mounts: [
38742
+ {
38743
+ kind: "muzzle",
38744
+ interfaces: [
38745
+ "fixed_muzzle_device"
38746
+ ],
38747
+ sources: [
38748
+ "derived"
38749
+ ]
38750
+ },
38751
+ {
38752
+ kind: "rail_top",
38753
+ interfaces: [
38754
+ "dovetail"
38755
+ ],
38756
+ sources: [
38757
+ "derived"
38758
+ ]
38759
+ },
38760
+ {
38761
+ kind: "rail_bottom",
38762
+ interfaces: [
38763
+ "bipod_stud"
38764
+ ],
38765
+ sources: [
38766
+ "gallery"
38767
+ ]
38768
+ },
38769
+ {
38770
+ kind: "magazine",
38771
+ interfaces: [
38772
+ "mag_box"
38773
+ ],
38774
+ sources: [
38775
+ "gallery"
38776
+ ]
38777
+ },
38778
+ {
38779
+ kind: "sling",
38780
+ interfaces: [
38781
+ "swivel"
38782
+ ],
38783
+ sources: [
38784
+ "derived"
38785
+ ]
38786
+ }
38787
+ ],
38788
+ fitted: [
38789
+ {
38790
+ id: "muzzle_brake",
38791
+ mount: "muzzle"
38792
+ },
38793
+ {
38794
+ id: "scope_3_9x",
38795
+ mount: "rail_top"
38796
+ },
38797
+ {
38798
+ id: "bipod",
38799
+ mount: "rail_bottom"
38800
+ }
38801
+ ]
38802
+ },
38803
+ {
38804
+ id: "mossberg_590",
38805
+ name: "Mossberg 590",
38806
+ maker: "Mossberg",
38807
+ category: "shotgun",
38808
+ action: {
38809
+ kind: "firearm",
38810
+ caliber: "12ga_00buck",
38811
+ rpm: 120,
38812
+ modes: [
38813
+ "pump"
38814
+ ],
38815
+ magazine: 8,
38816
+ chamber: 1,
38817
+ reload: 0.55,
38818
+ perRound: true,
38819
+ muzzleSpeed: 400,
38820
+ spreadDeg: 1,
38821
+ bloomDeg: 0.5,
38822
+ recoveryDegPerS: 3,
38823
+ pelletSpreadDeg: 3,
38824
+ recoil: [
38825
+ 3.62,
38826
+ 0.91
38827
+ ],
38828
+ range: 40,
38829
+ family: "pump",
38830
+ timings: {
38831
+ draw: 0.35,
38832
+ reload: 0.55,
38833
+ perRound: 0.55,
38834
+ cycle: 0.5,
38835
+ clear: 1.2
38836
+ }
38837
+ },
38838
+ mass: 3.3,
38839
+ dimensions: {
38840
+ length: 1.003,
38841
+ barrel: 0.508,
38842
+ emptyMass: 3.3
38843
+ },
38844
+ archetype: "shotgun_pump_12",
38845
+ variantOf: "mossberg_500",
38846
+ sources: [
38847
+ "reference",
38848
+ "derived",
38849
+ "sdk"
38850
+ ],
38851
+ mounts: [
38852
+ {
38853
+ kind: "barrel",
38854
+ interfaces: [
38855
+ "barrel_swap"
38856
+ ],
38857
+ sources: [
38858
+ "derived"
38859
+ ]
38860
+ },
38861
+ {
38862
+ kind: "sling",
38863
+ interfaces: [
38864
+ "swivel"
38865
+ ],
38866
+ sources: [
38867
+ "derived"
38868
+ ]
38869
+ }
38870
+ ],
38871
+ fitted: []
38872
+ },
38873
+ {
38874
+ id: "fn_m240b",
38875
+ name: "FN M240B",
38876
+ maker: "FN",
38877
+ category: "machine_gun",
38878
+ action: {
38879
+ kind: "firearm",
38880
+ caliber: "7_62x51",
38881
+ rpm: 750,
38882
+ modes: [
38883
+ "auto"
38884
+ ],
38885
+ magazine: 100,
38886
+ reload: 8.5,
38887
+ muzzleSpeed: 853,
38888
+ spreadDeg: 0.8,
38889
+ bloomDeg: 0.35,
38890
+ recoveryDegPerS: 4.5,
38891
+ recoil: [
38892
+ 0.77,
38893
+ 0.41
38894
+ ],
38895
+ range: 350,
38896
+ family: "belt",
38897
+ timings: {
38898
+ draw: 0.35,
38899
+ reload: 8.5,
38900
+ clear: 1.2
38901
+ }
38902
+ },
38903
+ mass: 12.5,
38904
+ dimensions: {
38905
+ length: 1.245,
38906
+ barrel: 0.63,
38907
+ emptyMass: 12.5
38908
+ },
38909
+ archetype: "rifle_7_62x39",
38910
+ sources: [
38911
+ "reference",
38912
+ "derived",
38913
+ "sdk"
38914
+ ],
38915
+ mounts: [
38916
+ {
38917
+ kind: "rail_top",
38918
+ interfaces: [
38919
+ "picatinny"
38920
+ ],
38921
+ sources: [
38922
+ "gallery"
38923
+ ]
38924
+ },
38925
+ {
38926
+ kind: "rail_bottom",
38927
+ interfaces: [
38928
+ "bipod_stud"
38929
+ ],
38930
+ sources: [
38931
+ "gallery"
38932
+ ]
38933
+ },
38934
+ {
38935
+ kind: "sling",
38936
+ interfaces: [
38937
+ "swivel"
38938
+ ],
38939
+ sources: [
38940
+ "derived"
38941
+ ]
38942
+ },
38943
+ {
38944
+ kind: "feed",
38945
+ interfaces: [
38946
+ "belt"
38947
+ ],
38948
+ sources: [
38949
+ "derived"
38950
+ ]
38951
+ }
38952
+ ],
38953
+ fitted: [
38954
+ {
38955
+ id: "bipod",
38956
+ mount: "rail_bottom"
38957
+ }
38958
+ ]
38959
+ },
38960
+ {
38961
+ id: "sig_sauer_p320",
38962
+ name: "SIG Sauer P320",
38963
+ maker: "SIG Sauer",
38964
+ category: "pistol",
38965
+ action: {
38966
+ kind: "firearm",
38967
+ caliber: "9x19",
38968
+ rpm: 300,
38969
+ modes: [
38970
+ "semi"
38971
+ ],
38972
+ magazine: 17,
38973
+ reload: 1.9,
38974
+ muzzleSpeed: 350,
38975
+ spreadDeg: 1,
38976
+ bloomDeg: 0.6,
38977
+ recoveryDegPerS: 4,
38978
+ recoil: [
38979
+ 1.2,
38980
+ 0.4
38981
+ ],
38982
+ range: 50,
38983
+ family: "pistol",
38984
+ timings: {
38985
+ draw: 0.35,
38986
+ reload: 1.9,
38987
+ clear: 1.2
38988
+ },
38989
+ faultFactor: 0.8
38990
+ },
38991
+ mass: 0.83,
38992
+ dimensions: {
38993
+ length: 0.203,
38994
+ barrel: 0.12,
38995
+ emptyMass: 0.83
38996
+ },
38997
+ archetype: "pistol_9mm",
38998
+ variantOf: "sig_sauer_p226",
38999
+ sources: [
39000
+ "reference",
39001
+ "derived",
39002
+ "sdk"
39003
+ ],
39004
+ mounts: [
39005
+ {
39006
+ kind: "magazine",
39007
+ interfaces: [
39008
+ "mag_pistol"
39009
+ ],
39010
+ sources: [
39011
+ "derived"
39012
+ ]
39013
+ }
39014
+ ],
39015
+ fitted: []
39016
+ },
39017
+ {
39018
+ id: "hk_usp",
39019
+ name: "HK USP",
39020
+ maker: "HK",
39021
+ category: "pistol",
39022
+ action: {
39023
+ kind: "firearm",
39024
+ caliber: "9x19",
39025
+ rpm: 300,
39026
+ modes: [
39027
+ "semi"
39028
+ ],
39029
+ magazine: 15,
39030
+ reload: 1.9,
39031
+ muzzleSpeed: 350,
39032
+ spreadDeg: 1,
39033
+ bloomDeg: 0.6,
39034
+ recoveryDegPerS: 4,
39035
+ recoil: [
39036
+ 1.29,
39037
+ 0.43
39038
+ ],
39039
+ range: 50,
39040
+ family: "pistol",
39041
+ timings: {
39042
+ draw: 0.35,
39043
+ reload: 1.9,
39044
+ clear: 1.2
39045
+ },
39046
+ faultFactor: 0.8
39047
+ },
39048
+ mass: 0.77,
39049
+ dimensions: {
39050
+ length: 0.194,
39051
+ barrel: 0.108,
39052
+ emptyMass: 0.77
39053
+ },
39054
+ archetype: "pistol_9mm",
39055
+ variantOf: "sig_sauer_p226",
39056
+ sources: [
39057
+ "reference",
39058
+ "derived",
39059
+ "sdk"
39060
+ ],
39061
+ mounts: [
39062
+ {
39063
+ kind: "magazine",
39064
+ interfaces: [
39065
+ "mag_pistol"
39066
+ ],
39067
+ sources: [
39068
+ "derived"
39069
+ ]
39070
+ }
39071
+ ],
39072
+ fitted: []
39073
+ },
39074
+ {
39075
+ id: "walther_p99",
39076
+ name: "Walther P99",
39077
+ maker: "Walther",
39078
+ category: "pistol",
39079
+ action: {
39080
+ kind: "firearm",
39081
+ caliber: "9x19",
39082
+ rpm: 300,
39083
+ modes: [
39084
+ "semi"
39085
+ ],
39086
+ magazine: 15,
39087
+ reload: 1.8,
39088
+ muzzleSpeed: 375,
39089
+ spreadDeg: 1,
39090
+ bloomDeg: 0.6,
39091
+ recoveryDegPerS: 4,
39092
+ recoil: [
39093
+ 1.81,
39094
+ 0.6
39095
+ ],
39096
+ range: 50,
39097
+ family: "pistol",
39098
+ timings: {
39099
+ draw: 0.35,
39100
+ reload: 1.8,
39101
+ clear: 1.2
39102
+ },
39103
+ faultFactor: 0.7
39104
+ },
39105
+ mass: 0.63,
39106
+ dimensions: {
39107
+ length: 0.18,
39108
+ barrel: 0.102,
39109
+ emptyMass: 0.63
39110
+ },
39111
+ archetype: "pistol_9mm",
39112
+ variantOf: "glock_17",
39113
+ sources: [
39114
+ "reference",
39115
+ "derived",
39116
+ "sdk"
39117
+ ],
39118
+ mounts: [
39119
+ {
39120
+ kind: "magazine",
39121
+ interfaces: [
39122
+ "mag_pistol"
39123
+ ],
39124
+ sources: [
39125
+ "derived"
39126
+ ]
39127
+ }
39128
+ ],
39129
+ fitted: []
39130
+ },
39131
+ {
39132
+ id: "colt_python",
39133
+ name: "Colt Python",
39134
+ maker: "Colt",
39135
+ category: "revolver",
39136
+ action: {
39137
+ kind: "firearm",
39138
+ caliber: "357mag",
39139
+ rpm: 150,
39140
+ modes: [
39141
+ "single"
39142
+ ],
39143
+ magazine: 6,
39144
+ reload: 2.4,
39145
+ muzzleSpeed: 430,
39146
+ spreadDeg: 0.8,
39147
+ bloomDeg: 1,
39148
+ recoveryDegPerS: 3,
39149
+ recoil: [
39150
+ 3.13,
39151
+ 0.94
39152
+ ],
39153
+ range: 60,
39154
+ family: "revolver",
39155
+ timings: {
39156
+ draw: 0.35,
39157
+ reload: 2.4,
39158
+ perRound: 1,
39159
+ clear: 1.2
39160
+ }
39161
+ },
39162
+ mass: 1.23,
39163
+ dimensions: {
39164
+ length: 0.292,
39165
+ barrel: 0.152,
39166
+ emptyMass: 1.23
39167
+ },
39168
+ archetype: "revolver_357",
39169
+ variantOf: "sw_686",
39170
+ sources: [
39171
+ "reference",
39172
+ "derived",
39173
+ "sdk"
39174
+ ],
39175
+ mounts: [],
39176
+ fitted: []
39177
+ },
39178
+ {
39179
+ id: "fn_ps90",
39180
+ name: "FN PS90",
39181
+ maker: "FN",
39182
+ category: "smg",
39183
+ action: {
39184
+ kind: "firearm",
39185
+ caliber: "5_7x28",
39186
+ rpm: 120,
39187
+ modes: [
39188
+ "semi"
39189
+ ],
39190
+ magazine: 50,
39191
+ reload: 3,
39192
+ muzzleSpeed: 715,
39193
+ spreadDeg: 1.5,
39194
+ bloomDeg: 0.35,
39195
+ recoveryDegPerS: 6,
39196
+ recoil: [
39197
+ 0.69,
39198
+ 0.38
39199
+ ],
39200
+ range: 80,
39201
+ family: "top_magazine",
39202
+ timings: {
39203
+ draw: 0.35,
39204
+ reload: 3,
39205
+ clear: 1.2
39206
+ }
39207
+ },
39208
+ mass: 2.86,
39209
+ dimensions: {
39210
+ length: 0.668,
39211
+ barrel: 0.407,
39212
+ emptyMass: 2.86
39213
+ },
39214
+ archetype: "smg_9mm",
39215
+ variantOf: "fn_p90",
39216
+ sources: [
39217
+ "reference",
39218
+ "derived",
39219
+ "sdk"
39220
+ ],
39221
+ mounts: [
39222
+ {
39223
+ kind: "sling",
39224
+ interfaces: [
39225
+ "swivel"
39226
+ ],
39227
+ sources: [
39228
+ "derived"
39229
+ ]
39230
+ }
39231
+ ],
39232
+ fitted: []
39233
+ },
39234
+ {
39235
+ id: "bt_mp9",
39236
+ name: "B&T MP9",
39237
+ maker: "B&T",
39238
+ category: "smg",
39239
+ action: {
39240
+ kind: "firearm",
39241
+ caliber: "9x19",
39242
+ rpm: 1100,
39243
+ modes: [
39244
+ "auto",
39245
+ "semi"
39246
+ ],
39247
+ magazine: 30,
39248
+ reload: 2.4,
39249
+ muzzleSpeed: 400,
39250
+ spreadDeg: 1.5,
39251
+ bloomDeg: 0.35,
39252
+ recoveryDegPerS: 6,
39253
+ recoil: [
39254
+ 1.66,
39255
+ 0.92
39256
+ ],
39257
+ range: 80,
39258
+ family: "magazine",
39259
+ timings: {
39260
+ draw: 0.35,
39261
+ reload: 2.4,
39262
+ clear: 1.2
39263
+ }
39264
+ },
39265
+ mass: 1.4,
39266
+ dimensions: {
39267
+ length: 0.523,
39268
+ barrel: 0.13,
39269
+ emptyMass: 1.4,
39270
+ lengthCollapsed: 0.303
39271
+ },
39272
+ archetype: "smg_9mm",
39273
+ variantOf: "uzi",
39274
+ sources: [
39275
+ "reference",
39276
+ "derived",
39277
+ "sdk"
39278
+ ],
39279
+ mounts: [
39280
+ {
39281
+ kind: "rail_top",
39282
+ interfaces: [
39283
+ "picatinny"
39284
+ ],
39285
+ sources: [
39286
+ "gallery"
39287
+ ]
39288
+ },
39289
+ {
39290
+ kind: "magazine",
39291
+ interfaces: [
39292
+ "mag_box"
39293
+ ],
39294
+ sources: [
39295
+ "gallery"
39296
+ ]
39297
+ },
39298
+ {
39299
+ kind: "sling",
39300
+ interfaces: [
39301
+ "swivel"
39302
+ ],
39303
+ sources: [
39304
+ "derived"
39305
+ ]
39306
+ }
39307
+ ],
39308
+ fitted: [],
39309
+ stock: {
39310
+ kind: "fold",
39311
+ foldAxis: "right"
39312
+ }
39313
+ },
39314
+ {
39315
+ id: "mp40",
39316
+ name: "MP40",
39317
+ category: "smg",
39318
+ action: {
39319
+ kind: "firearm",
39320
+ caliber: "9x19",
39321
+ rpm: 550,
39322
+ modes: [
39323
+ "auto",
39324
+ "semi"
39325
+ ],
39326
+ magazine: 32,
39327
+ reload: 2.9,
39328
+ spreadDeg: 1.5,
39329
+ bloomDeg: 0.35,
39330
+ recoveryDegPerS: 6,
39331
+ recoil: [
39332
+ 0.48,
39333
+ 0.27
39334
+ ],
39335
+ range: 80,
39336
+ family: "magazine",
39337
+ timings: {
39338
+ draw: 0.35,
39339
+ reload: 2.9,
39340
+ clear: 1.2
39341
+ }
39342
+ },
39343
+ mass: 4,
39344
+ dimensions: {
39345
+ length: 0.833,
39346
+ barrel: 0.251,
39347
+ emptyMass: 4,
39348
+ lengthCollapsed: 0.63
39349
+ },
39350
+ archetype: "smg_9mm",
39351
+ variantOf: "thompson_m1a1",
39352
+ sources: [
39353
+ "reference",
39354
+ "derived",
39355
+ "sdk"
39356
+ ],
39357
+ mounts: [
39358
+ {
39359
+ kind: "magazine",
39360
+ interfaces: [
39361
+ "mag_box"
39362
+ ],
39363
+ sources: [
39364
+ "gallery"
39365
+ ]
39366
+ },
39367
+ {
39368
+ kind: "sling",
39369
+ interfaces: [
39370
+ "swivel"
39371
+ ],
39372
+ sources: [
39373
+ "derived"
39374
+ ]
39375
+ }
39376
+ ],
39377
+ fitted: [],
39378
+ stock: {
39379
+ kind: "fold",
39380
+ foldAxis: "under"
39381
+ }
39382
+ },
39383
+ {
39384
+ id: "famas",
39385
+ name: "FAMAS",
39386
+ category: "rifle",
39387
+ action: {
39388
+ kind: "firearm",
39389
+ caliber: "5_56x45",
39390
+ rpm: 1e3,
39391
+ modes: [
39392
+ "auto",
39393
+ "semi"
39394
+ ],
39395
+ magazine: 25,
39396
+ reload: 2.8,
39397
+ muzzleSpeed: 960,
39398
+ spreadDeg: 0.6,
39399
+ bloomDeg: 0.25,
39400
+ recoveryDegPerS: 5,
39401
+ recoil: [
39402
+ 0.99,
39403
+ 0.6
39404
+ ],
39405
+ range: 400,
39406
+ family: "magazine",
39407
+ timings: {
39408
+ draw: 0.35,
39409
+ reload: 2.8,
39410
+ reloadEmpty: 3.4,
39411
+ clear: 1.2
39412
+ }
39413
+ },
39414
+ mass: 3.61,
39415
+ dimensions: {
39416
+ length: 0.757,
39417
+ barrel: 0.488,
39418
+ emptyMass: 3.61
39419
+ },
39420
+ archetype: "rifle_5_56",
39421
+ variantOf: "steyr_aug_a1",
39422
+ sources: [
39423
+ "reference",
39424
+ "derived",
39425
+ "sdk"
39426
+ ],
39427
+ mounts: [
39428
+ {
39429
+ kind: "magazine",
39430
+ interfaces: [
39431
+ "mag_box"
39432
+ ],
39433
+ sources: [
39434
+ "derived"
39435
+ ]
39436
+ },
39437
+ {
39438
+ kind: "sling",
39439
+ interfaces: [
39440
+ "swivel"
39441
+ ],
39442
+ sources: [
39443
+ "derived"
39444
+ ]
39445
+ }
39446
+ ],
39447
+ fitted: []
39448
+ },
39449
+ {
39450
+ id: "iwi_tavor",
39451
+ name: "IWI Tavor",
39452
+ maker: "IWI",
39453
+ category: "rifle",
39454
+ action: {
39455
+ kind: "firearm",
39456
+ caliber: "5_56x45",
39457
+ rpm: 850,
39458
+ modes: [
39459
+ "auto",
39460
+ "semi"
39461
+ ],
39462
+ magazine: 30,
39463
+ reload: 2.8,
39464
+ muzzleSpeed: 910,
39465
+ spreadDeg: 0.6,
39466
+ bloomDeg: 0.25,
39467
+ recoveryDegPerS: 5,
39468
+ recoil: [
39469
+ 0.98,
39470
+ 0.59
39471
+ ],
39472
+ range: 400,
39473
+ family: "magazine",
39474
+ timings: {
39475
+ draw: 0.35,
39476
+ reload: 2.8,
39477
+ reloadEmpty: 3.4,
39478
+ clear: 1.2
39479
+ }
39480
+ },
39481
+ mass: 3.27,
39482
+ dimensions: {
39483
+ length: 0.72,
39484
+ barrel: 0.46,
39485
+ emptyMass: 3.27
39486
+ },
39487
+ archetype: "rifle_5_56",
39488
+ variantOf: "steyr_aug_a1",
37763
39489
  sources: [
37764
39490
  "reference",
37765
39491
  "derived",
@@ -37767,18 +39493,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37767
39493
  ],
37768
39494
  mounts: [
37769
39495
  {
37770
- kind: "muzzle",
37771
- interfaces: [
37772
- "thread_14x1lh"
37773
- ],
37774
- sources: [
37775
- "derived"
37776
- ]
37777
- },
37778
- {
37779
- kind: "rail_bottom",
39496
+ kind: "rail_top",
37780
39497
  interfaces: [
37781
- "bipod_stud"
39498
+ "picatinny"
37782
39499
  ],
37783
39500
  sources: [
37784
39501
  "gallery"
@@ -37787,10 +39504,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37787
39504
  {
37788
39505
  kind: "magazine",
37789
39506
  interfaces: [
37790
- "mag_curved"
39507
+ "mag_box"
37791
39508
  ],
37792
39509
  sources: [
37793
- "gallery"
39510
+ "derived"
37794
39511
  ]
37795
39512
  },
37796
39513
  {
@@ -37803,54 +39520,47 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37803
39520
  ]
37804
39521
  }
37805
39522
  ],
37806
- fitted: [
37807
- {
37808
- id: "bipod",
37809
- mount: "rail_bottom"
37810
- }
37811
- ]
39523
+ fitted: []
37812
39524
  },
37813
39525
  {
37814
- id: "sig_mpx",
37815
- name: "SIG MPX",
37816
- category: "smg",
39526
+ id: "l85a2",
39527
+ name: "L85A2",
39528
+ category: "rifle",
37817
39529
  action: {
37818
39530
  kind: "firearm",
37819
- caliber: "9x19",
37820
- rpm: 850,
39531
+ caliber: "5_56x45",
39532
+ rpm: 700,
37821
39533
  modes: [
37822
39534
  "auto",
37823
39535
  "semi"
37824
39536
  ],
37825
39537
  magazine: 30,
37826
- magazineOptions: [
37827
- 20,
37828
- 35
37829
- ],
37830
- reload: 2.6,
37831
- spreadDeg: 1.5,
37832
- bloomDeg: 0.35,
37833
- recoveryDegPerS: 6,
39538
+ reload: 2.8,
39539
+ muzzleSpeed: 940,
39540
+ spreadDeg: 0.6,
39541
+ bloomDeg: 0.25,
39542
+ recoveryDegPerS: 5,
37834
39543
  recoil: [
37835
- 0.72,
37836
- 0.4
39544
+ 0.9,
39545
+ 0.54
37837
39546
  ],
37838
- range: 80,
39547
+ range: 400,
37839
39548
  family: "magazine",
37840
39549
  timings: {
37841
39550
  draw: 0.35,
37842
- reload: 2.6,
39551
+ reload: 2.8,
39552
+ reloadEmpty: 3.4,
37843
39553
  clear: 1.2
37844
39554
  }
37845
39555
  },
37846
- mass: 2.7,
39556
+ mass: 3.82,
37847
39557
  dimensions: {
37848
- length: 0.61,
37849
- barrel: 0.203,
37850
- emptyMass: 2.7,
37851
- lengthCollapsed: 0.425
39558
+ length: 0.785,
39559
+ barrel: 0.518,
39560
+ emptyMass: 3.82
37852
39561
  },
37853
- archetype: "smg_9mm",
39562
+ archetype: "rifle_5_56",
39563
+ variantOf: "steyr_aug_a1",
37854
39564
  sources: [
37855
39565
  "reference",
37856
39566
  "derived",
@@ -37866,40 +39576,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37866
39576
  "gallery"
37867
39577
  ]
37868
39578
  },
37869
- {
37870
- kind: "rail_left",
37871
- interfaces: [
37872
- "picatinny"
37873
- ],
37874
- sources: [
37875
- "gallery"
37876
- ]
37877
- },
37878
- {
37879
- kind: "rail_right",
37880
- interfaces: [
37881
- "picatinny"
37882
- ],
37883
- sources: [
37884
- "gallery"
37885
- ]
37886
- },
37887
- {
37888
- kind: "rail_bottom",
37889
- interfaces: [
37890
- "picatinny"
37891
- ],
37892
- sources: [
37893
- "gallery"
37894
- ]
37895
- },
37896
39579
  {
37897
39580
  kind: "magazine",
37898
39581
  interfaces: [
37899
39582
  "mag_box"
37900
39583
  ],
37901
39584
  sources: [
37902
- "gallery"
39585
+ "derived"
37903
39586
  ]
37904
39587
  },
37905
39588
  {
@@ -37912,62 +39595,54 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37912
39595
  ]
37913
39596
  }
37914
39597
  ],
37915
- fitted: [],
37916
- stock: {
37917
- kind: "retract"
37918
- }
39598
+ fitted: []
37919
39599
  },
37920
39600
  {
37921
- id: "mg3",
37922
- name: "MG3",
37923
- category: "machine_gun",
39601
+ id: "winchester_1887",
39602
+ name: "Winchester 1887",
39603
+ maker: "Winchester",
39604
+ category: "hunting",
37924
39605
  action: {
37925
39606
  kind: "firearm",
37926
- caliber: "7_62x51",
37927
- rpm: 1100,
39607
+ caliber: "12ga_00buck",
39608
+ rpm: 109.09,
37928
39609
  modes: [
37929
- "auto"
39610
+ "lever"
37930
39611
  ],
37931
- magazine: 100,
37932
- reload: 8.5,
37933
- muzzleSpeed: 820,
37934
- spreadDeg: 0.8,
37935
- bloomDeg: 0.35,
37936
- recoveryDegPerS: 4.5,
39612
+ magazine: 5,
39613
+ reload: 0.6,
39614
+ perRound: true,
39615
+ spreadDeg: 0.25,
39616
+ bloomDeg: 0.3,
39617
+ recoveryDegPerS: 3,
37937
39618
  recoil: [
37938
- 0.77,
37939
- 0.41
39619
+ 2.33,
39620
+ 0.39
37940
39621
  ],
37941
- range: 350,
37942
- family: "belt",
39622
+ range: 800,
39623
+ family: "lever",
37943
39624
  timings: {
37944
39625
  draw: 0.35,
37945
- reload: 8.5,
39626
+ reload: 0.6,
39627
+ perRound: 0.6,
39628
+ cycle: 0.55,
37946
39629
  clear: 1.2
37947
39630
  }
37948
39631
  },
37949
- mass: 11.5,
39632
+ mass: 3.6,
37950
39633
  dimensions: {
37951
- length: 1.225,
37952
- barrel: 0.565,
37953
- emptyMass: 11.5
39634
+ length: 1.016,
39635
+ barrel: 0.508,
39636
+ emptyMass: 3.6
37954
39637
  },
37955
- archetype: "rifle_7_62x39",
39638
+ archetype: "rifle_bolt_308",
39639
+ variantOf: "winchester_1894",
37956
39640
  sources: [
37957
39641
  "reference",
37958
39642
  "derived",
37959
39643
  "sdk"
37960
39644
  ],
37961
39645
  mounts: [
37962
- {
37963
- kind: "rail_bottom",
37964
- interfaces: [
37965
- "bipod_stud"
37966
- ],
37967
- sources: [
37968
- "gallery"
37969
- ]
37970
- },
37971
39646
  {
37972
39647
  kind: "sling",
37973
39648
  interfaces: [
@@ -37976,62 +39651,51 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37976
39651
  sources: [
37977
39652
  "derived"
37978
39653
  ]
37979
- },
37980
- {
37981
- kind: "feed",
37982
- interfaces: [
37983
- "belt"
37984
- ],
37985
- sources: [
37986
- "derived"
37987
- ]
37988
39654
  }
37989
39655
  ],
37990
- fitted: [
37991
- {
37992
- id: "bipod",
37993
- mount: "rail_bottom"
37994
- }
37995
- ]
39656
+ fitted: []
37996
39657
  },
37997
39658
  {
37998
- id: "pp19_bizon",
37999
- name: "PP-19 Bizon",
39659
+ id: "ppsh_41",
39660
+ name: "PPSh-41",
38000
39661
  category: "smg",
38001
39662
  action: {
38002
39663
  kind: "firearm",
38003
- caliber: "9x18",
38004
- rpm: 680,
39664
+ caliber: "7_62x25",
39665
+ rpm: 1250,
38005
39666
  modes: [
38006
39667
  "auto",
38007
39668
  "semi"
38008
39669
  ],
38009
- magazine: 64,
38010
- reload: 2.5,
38011
- muzzleSpeed: 320,
39670
+ magazine: 71,
39671
+ magazineOptions: [
39672
+ 35
39673
+ ],
39674
+ reload: 2.9,
39675
+ muzzleSpeed: 488,
38012
39676
  spreadDeg: 1.5,
38013
39677
  bloomDeg: 0.35,
38014
39678
  recoveryDegPerS: 6,
38015
39679
  recoil: [
38016
- 0.58,
38017
- 0.32
39680
+ 0.7,
39681
+ 0.39
38018
39682
  ],
38019
39683
  range: 80,
38020
39684
  family: "magazine",
38021
39685
  timings: {
38022
39686
  draw: 0.35,
38023
- reload: 2.5,
39687
+ reload: 2.9,
38024
39688
  clear: 1.2
38025
39689
  }
38026
39690
  },
38027
- mass: 2.1,
39691
+ mass: 3.63,
38028
39692
  dimensions: {
38029
- length: 0.66,
38030
- barrel: 0.195,
38031
- emptyMass: 2.1,
38032
- lengthCollapsed: 0.425
39693
+ length: 0.843,
39694
+ barrel: 0.269,
39695
+ emptyMass: 3.63
38033
39696
  },
38034
39697
  archetype: "smg_9mm",
39698
+ variantOf: "thompson_m1a1",
38035
39699
  sources: [
38036
39700
  "reference",
38037
39701
  "derived",
@@ -38041,7 +39705,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38041
39705
  {
38042
39706
  kind: "magazine",
38043
39707
  interfaces: [
38044
- "mag_helical"
39708
+ "mag_drum"
38045
39709
  ],
38046
39710
  sources: [
38047
39711
  "gallery"
@@ -38057,53 +39721,47 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38057
39721
  ]
38058
39722
  }
38059
39723
  ],
38060
- fitted: [],
38061
- stock: {
38062
- kind: "fold",
38063
- foldAxis: "left"
38064
- }
39724
+ fitted: []
38065
39725
  },
38066
39726
  {
38067
- id: "vz61",
38068
- name: "vz. 61 \u0160korpion",
38069
- category: "smg",
39727
+ id: "mauser_kar98k",
39728
+ name: "Mauser Kar98k",
39729
+ maker: "Mauser",
39730
+ category: "sniper",
38070
39731
  action: {
38071
39732
  kind: "firearm",
38072
- caliber: "32acp",
38073
- rpm: 850,
39733
+ caliber: "7_92x57",
39734
+ rpm: 73.17,
38074
39735
  modes: [
38075
- "auto",
38076
- "semi"
38077
- ],
38078
- magazine: 20,
38079
- magazineOptions: [
38080
- 10
39736
+ "bolt"
38081
39737
  ],
39738
+ magazine: 5,
38082
39739
  reload: 2.4,
38083
- muzzleSpeed: 320,
38084
- spreadDeg: 1.5,
38085
- bloomDeg: 0.35,
38086
- recoveryDegPerS: 6,
39740
+ muzzleSpeed: 760,
39741
+ spreadDeg: 0.25,
39742
+ bloomDeg: 0.3,
39743
+ recoveryDegPerS: 3,
38087
39744
  recoil: [
38088
- 0.7,
38089
- 0.39
39745
+ 2.84,
39746
+ 0.47
38090
39747
  ],
38091
- range: 80,
38092
- family: "magazine",
39748
+ range: 800,
39749
+ family: "bolt",
38093
39750
  timings: {
38094
39751
  draw: 0.35,
38095
39752
  reload: 2.4,
39753
+ cycle: 0.82,
38096
39754
  clear: 1.2
38097
39755
  }
38098
39756
  },
38099
- mass: 1.3,
39757
+ mass: 3.7,
38100
39758
  dimensions: {
38101
- length: 0.517,
38102
- barrel: 0.115,
38103
- emptyMass: 1.3,
38104
- lengthCollapsed: 0.27
39759
+ length: 1.11,
39760
+ barrel: 0.6,
39761
+ emptyMass: 3.7
38105
39762
  },
38106
- archetype: "smg_9mm",
39763
+ archetype: "rifle_bolt_308",
39764
+ variantOf: "mosin_nagant_91_30",
38107
39765
  sources: [
38108
39766
  "reference",
38109
39767
  "derived",
@@ -38111,14 +39769,61 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38111
39769
  ],
38112
39770
  mounts: [
38113
39771
  {
38114
- kind: "magazine",
39772
+ kind: "sling",
38115
39773
  interfaces: [
38116
- "mag_curved"
39774
+ "swivel"
38117
39775
  ],
38118
39776
  sources: [
38119
- "gallery"
39777
+ "derived"
38120
39778
  ]
39779
+ }
39780
+ ],
39781
+ fitted: []
39782
+ },
39783
+ {
39784
+ id: "mg42",
39785
+ name: "MG42",
39786
+ category: "machine_gun",
39787
+ action: {
39788
+ kind: "firearm",
39789
+ caliber: "7_92x57",
39790
+ rpm: 1200,
39791
+ modes: [
39792
+ "auto"
39793
+ ],
39794
+ magazine: 50,
39795
+ reload: 8.5,
39796
+ muzzleSpeed: 740,
39797
+ spreadDeg: 0.8,
39798
+ bloomDeg: 0.35,
39799
+ recoveryDegPerS: 4.5,
39800
+ recoil: [
39801
+ 0.75,
39802
+ 0.4
39803
+ ],
39804
+ range: 350,
39805
+ family: "belt",
39806
+ timings: {
39807
+ draw: 0.35,
39808
+ reload: 8.5,
39809
+ clear: 1.2
38121
39810
  },
39811
+ faultFactor: 1.3
39812
+ },
39813
+ mass: 11.6,
39814
+ dimensions: {
39815
+ length: 1.22,
39816
+ barrel: 0.53,
39817
+ emptyMass: 11.6
39818
+ },
39819
+ archetype: "rifle_7_62x39",
39820
+ variantOf: "m60",
39821
+ sources: [
39822
+ "reference",
39823
+ "derived",
39824
+ "sdk"
39825
+ ],
39826
+ mounts: [
38122
39827
  {
38123
39828
  kind: "sling",
38124
39829
  interfaces: [
@@ -38129,58 +39834,52 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38129
39834
  ]
38130
39835
  }
38131
39836
  ],
38132
- fitted: [],
38133
- stock: {
38134
- kind: "fold",
38135
- foldAxis: "over"
38136
- }
39837
+ fitted: []
38137
39838
  },
38138
39839
  {
38139
- id: "m134",
38140
- name: "M134 Minigun",
38141
- category: "machine_gun",
39840
+ id: "vss_vintorez",
39841
+ name: "VSS Vintorez",
39842
+ category: "dmr",
38142
39843
  action: {
38143
39844
  kind: "firearm",
38144
- caliber: "7_62x51",
38145
- rpm: 3e3,
39845
+ caliber: "9x39",
39846
+ rpm: 850,
38146
39847
  modes: [
38147
- "auto"
39848
+ "auto",
39849
+ "semi"
38148
39850
  ],
38149
- magazine: 200,
38150
- reload: 8.5,
38151
- muzzleSpeed: 853,
38152
- spin: {
38153
- upSeconds: 0.5,
38154
- holdSeconds: 1
38155
- },
38156
- rpmRange: [
38157
- 2e3,
38158
- 6e3
39851
+ magazine: 10,
39852
+ magazineOptions: [
39853
+ 20,
39854
+ 30
38159
39855
  ],
39856
+ reload: 2.6,
39857
+ muzzleSpeed: 295,
39858
+ noise: 75,
38160
39859
  spreadDeg: 0.8,
38161
39860
  bloomDeg: 0.35,
38162
39861
  recoveryDegPerS: 4.5,
38163
39862
  recoil: [
38164
- 0.25,
38165
- 0.13
39863
+ 0.73,
39864
+ 0.39
38166
39865
  ],
38167
39866
  range: 350,
38168
- family: "belt",
39867
+ family: "magazine",
38169
39868
  timings: {
38170
39869
  draw: 0.35,
38171
- reload: 8.5,
39870
+ reload: 2.6,
39871
+ reloadEmpty: 3.4,
38172
39872
  clear: 1.2
38173
39873
  }
38174
39874
  },
38175
- mass: 39,
39875
+ mass: 2.6,
38176
39876
  dimensions: {
38177
- length: 0.801,
38178
- barrel: 0.559,
38179
- emptyMass: 39,
38180
- barrels: 6,
38181
- barrelMass: 2
39877
+ length: 0.894,
39878
+ barrel: 0.2,
39879
+ emptyMass: 2.6
38182
39880
  },
38183
39881
  archetype: "rifle_7_62x39",
39882
+ variantOf: "svd_dragunov",
38184
39883
  sources: [
38185
39884
  "reference",
38186
39885
  "derived",
@@ -38188,9 +39887,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38188
39887
  ],
38189
39888
  mounts: [
38190
39889
  {
38191
- kind: "underbarrel",
39890
+ kind: "muzzle",
38192
39891
  interfaces: [
38193
- "tripod"
39892
+ "integral"
38194
39893
  ],
38195
39894
  sources: [
38196
39895
  "reference"
@@ -38204,18 +39903,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38204
39903
  sources: [
38205
39904
  "derived"
38206
39905
  ]
38207
- },
38208
- {
38209
- kind: "feed",
38210
- interfaces: [
38211
- "belt"
38212
- ],
38213
- sources: [
38214
- "derived"
38215
- ]
38216
39906
  }
38217
39907
  ],
38218
- fitted: []
39908
+ fitted: [
39909
+ {
39910
+ id: "integral_suppressor",
39911
+ mount: "muzzle",
39912
+ integral: true
39913
+ }
39914
+ ]
38219
39915
  }
38220
39916
  ];
38221
39917
  var launchers_default = [
@@ -38552,6 +40248,46 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38552
40248
  "reference",
38553
40249
  "derived"
38554
40250
  ]
40251
+ },
40252
+ {
40253
+ id: "m32_mgl",
40254
+ name: "M32 multiple grenade launcher (Milkor MGL)",
40255
+ category: "launcher",
40256
+ action: {
40257
+ kind: "launcher",
40258
+ projectile: "m79_40mm",
40259
+ magazine: 6,
40260
+ reload: 9.2,
40261
+ timings: {
40262
+ draw: 0.35,
40263
+ reload: 9.2,
40264
+ clear: 1.2
40265
+ },
40266
+ family: "revolver",
40267
+ speed: 75,
40268
+ minArmDistance: 10,
40269
+ backblast: 0,
40270
+ recoil: [
40271
+ 2.25,
40272
+ 0.6
40273
+ ]
40274
+ },
40275
+ mass: 6.68,
40276
+ dimensions: {
40277
+ length: 0.778,
40278
+ lengthCollapsed: 0.565,
40279
+ barrel: 0.3,
40280
+ emptyMass: 5.3
40281
+ },
40282
+ archetype: "shotgun_double_12",
40283
+ stock: {
40284
+ kind: "fold",
40285
+ foldAxis: "over"
40286
+ },
40287
+ sources: [
40288
+ "reference",
40289
+ "derived"
40290
+ ]
38555
40291
  }
38556
40292
  ];
38557
40293
  var INSTALL_RANK = { field: 0, tool: 1, bench: 2 };
@@ -38787,6 +40523,69 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38787
40523
  tags: [
38788
40524
  "silent"
38789
40525
  ]
40526
+ },
40527
+ {
40528
+ id: "kabar",
40529
+ name: "KA-BAR combat knife",
40530
+ category: "melee_blade",
40531
+ action: {
40532
+ kind: "melee",
40533
+ damage: "slash",
40534
+ energy: 48,
40535
+ radius: 0.03,
40536
+ reach: 0.55,
40537
+ rpm: 100,
40538
+ noise: 1
40539
+ },
40540
+ mass: 0.32,
40541
+ sources: [
40542
+ "reference",
40543
+ "derived"
40544
+ ]
40545
+ },
40546
+ {
40547
+ id: "katana",
40548
+ name: "Katana",
40549
+ category: "melee_blade",
40550
+ action: {
40551
+ kind: "melee",
40552
+ damage: "slash",
40553
+ energy: 230,
40554
+ radius: 0.04,
40555
+ reach: 1.1,
40556
+ rpm: 70,
40557
+ noise: 5
40558
+ },
40559
+ mass: 1.1,
40560
+ tags: [
40561
+ "two_handed"
40562
+ ],
40563
+ sources: [
40564
+ "reference",
40565
+ "derived"
40566
+ ]
40567
+ },
40568
+ {
40569
+ id: "brass_knuckles",
40570
+ name: "Brass knuckles",
40571
+ category: "melee_blunt",
40572
+ action: {
40573
+ kind: "melee",
40574
+ damage: "blunt",
40575
+ energy: 32,
40576
+ radius: 0.04,
40577
+ reach: 0.6,
40578
+ rpm: 120,
40579
+ noise: 5
40580
+ },
40581
+ mass: 0.15,
40582
+ tags: [
40583
+ "fists"
40584
+ ],
40585
+ sources: [
40586
+ "reference",
40587
+ "derived"
40588
+ ]
38790
40589
  }
38791
40590
  ];
38792
40591
  var findDuplicateIds = (kind, entries2) => {
@@ -38987,7 +40786,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38987
40786
  improvised_spear: "Improvised spear",
38988
40787
  chainsaw: "Chainsaw",
38989
40788
  recurve_bow: "Recurve bow",
38990
- m134: "M134 Minigun"
40789
+ m134: "M134 Minigun",
40790
+ semtex: "Semtex charge",
40791
+ smoke_grenade: "M18 smoke grenade",
40792
+ kabar: "KA-BAR combat knife",
40793
+ katana: "Katana",
40794
+ brass_knuckles: "Brass knuckles"
38991
40795
  },
38992
40796
  modes: modesEn,
38993
40797
  categories: categoriesEn,
@@ -39000,6 +40804,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39000
40804
  c4: "C4 charge",
39001
40805
  claymore: "Claymore mine",
39002
40806
  flashbang: "Flashbang",
40807
+ semtex: "Semtex charge",
40808
+ smoke_grenade: "M18 colored smoke grenade",
39003
40809
  m79_40mm: "40 mm grenade",
39004
40810
  m72_66mm: "66 mm HEAT rocket",
39005
40811
  at4_84mm: "84 mm HEAT rocket",
@@ -39081,7 +40887,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39081
40887
  improvised_spear: "lan\xE7a improvisada",
39082
40888
  chainsaw: "motosserra",
39083
40889
  recurve_bow: "arco recurvo",
39084
- m134: "Minigun M134"
40890
+ m134: "Minigun M134",
40891
+ semtex: "Semtex com detonador",
40892
+ smoke_grenade: "granada de fuma\xE7a M18",
40893
+ kabar: "faca KA-BAR",
40894
+ katana: "espada katana",
40895
+ brass_knuckles: "soco ingl\xEAs"
39085
40896
  },
39086
40897
  modes: { single: "tiro \xFAnico", semi: "semiautom\xE1tico", burst: "rajada", auto: "autom\xE1tico", pump: "repeti\xE7\xE3o", bolt: "ferrolho", lever: "alavanca", break: "basculante", volley: "salva" },
39087
40898
  categories: {
@@ -39117,6 +40928,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39117
40928
  c4: "C4 com detonador",
39118
40929
  claymore: "Mina Claymore",
39119
40930
  flashbang: "Granada de luz",
40931
+ semtex: "Semtex com detonador",
40932
+ smoke_grenade: "Granada de fuma\xE7a M18",
39120
40933
  m79_40mm: "Granada de 40 mm",
39121
40934
  m72_66mm: "Foguete HEAT de 66 mm",
39122
40935
  at4_84mm: "Foguete HEAT de 84 mm",
@@ -39228,6 +41041,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39228
41041
  // the iron sights close the view in a little: more for a long gun, whose sights sit farther from the eye, than for a handgun
39229
41042
  ironSightZoom: { pistol: 1.1, revolver: 1.1, smg: 1.2, carbine: 1.25, shotgun: 1.15, rifle: 1.3, hunting: 1.35, dmr: 1.4, sniper: 1.4, machine_gun: 1.2, launcher: 1.15, bow: 1.15, crossbow: 1.25 }
39230
41043
  },
41044
+ // a weapon in each hand: the handguns, the machine pistols short and light enough for one hand, and the one-hand blades; one hand and no sight picture open the cone and lift the muzzle more
41045
+ dualWield: {
41046
+ categories: ["pistol", "revolver"],
41047
+ weapons: ["mini_uzi", "uzi", "mac_10", "vz61", "hk_mp7a1", "knife", "tactical_knife", "machete", "hatchet"],
41048
+ exclude: [],
41049
+ spread: 1.8,
41050
+ recoil: 1.35,
41051
+ // tucking a pistol in the belt and drawing it back is a short motion of one hand
41052
+ stowSeconds: 0.45
41053
+ },
39231
41054
  // Arsenal §08/§11: coronha retrátil saca mais rápido, mais recuo; os números são padrões aprovados pelo dono (2026-10-24)
39232
41055
  stock: {
39233
41056
  seconds: 0.6,
@@ -39390,7 +41213,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39390
41213
  destructionLevel: def.destructionLevel,
39391
41214
  ...def.cone ? { cone: def.cone } : {},
39392
41215
  ...def.stunSeconds !== void 0 ? { stunSeconds: def.stunSeconds } : {},
39393
- ...def.fire ? { fire: def.fire } : {}
41216
+ ...def.fire ? { fire: def.fire } : {},
41217
+ ...def.smoke ? { smoke: def.smoke } : {}
39394
41218
  };
39395
41219
  return { type: "detonate", at: at3, detonation };
39396
41220
  }
@@ -39650,6 +41474,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39650
41474
  return roll.ammoQuality.faultChance > 0 && roll.random() < roll.ammoQuality.faultChance ? "misfire" : null;
39651
41475
  };
39652
41476
  var isDudWithoutJam = (action, fault) => fault === "misfire" && action.kind === "firearm" && MISFIRE_ONLY.has(action.family);
41477
+ var ONE_HAND = { spread: 1, recoil: 1 };
39653
41478
  var activeParts = (fitted, catalogue) => fitted.flatMap((part) => {
39654
41479
  const def = catalogue.get(part.id);
39655
41480
  const isOn = def?.kind !== "light" && def?.kind !== "laser" ? true : part.on === true;
@@ -39716,12 +41541,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39716
41541
  const read = (name, base) => stat(name, base, sources, active);
39717
41542
  const mass = read("mass", def.mass + parts.reduce((total, part) => total + part.mass, 0) - factory.reduce((total, part) => total + part.mass, 0));
39718
41543
  const hipFactor = context.aiming ? 1 : 1 + (context.rules.aim.hipSpread - 1) * read("hipSpreadExcess", 1);
41544
+ const dual = context.dualWield && context.rules.dualWield ? context.rules.dualWield : ONE_HAND;
39719
41545
  let action = def.action;
39720
41546
  if (firearm) {
39721
41547
  const baseNoise = firearm.noise ?? context.caliber?.noise;
39722
41548
  const effects = {
39723
- spread: read("spread", firearm.spreadDeg) * hipFactor,
39724
- recoil: [read("recoilVertical", firearm.recoil[0]), read("recoilHorizontal", firearm.recoil[1])],
41549
+ spread: read("spread", firearm.spreadDeg) * hipFactor * dual.spread,
41550
+ recoil: [read("recoilVertical", firearm.recoil[0]) * dual.recoil, read("recoilHorizontal", firearm.recoil[1]) * dual.recoil],
39725
41551
  range: read("range", firearm.range),
39726
41552
  noise: baseNoise === void 0 ? void 0 : read("noise", baseNoise),
39727
41553
  speed,
@@ -40090,6 +41916,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
40090
41916
  __publicField4(this, "stockState", "extended");
40091
41917
  __publicField4(this, "aiming", false);
40092
41918
  __publicField4(this, "supportedStance", false);
41919
+ __publicField4(this, "dualStance", false);
40093
41920
  __publicField4(this, "reloadCount", 0);
40094
41921
  __publicField4(this, "catalogue");
40095
41922
  __publicField4(this, "isCompatible");
@@ -40137,7 +41964,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
40137
41964
  }
40138
41965
  resolve() {
40139
41966
  const supported = this.supportedStance;
40140
- return resolveLoadout(this.def, this.fitted, this.catalogue, { aiming: this.aiming, supported, stock: this.stockState, caliber: this.caliber, rules: this.rules });
41967
+ return resolveLoadout(this.def, this.fitted, this.catalogue, { aiming: this.aiming, supported, stock: this.stockState, caliber: this.caliber, rules: this.rules, dualWield: this.dualStance });
40141
41968
  }
40142
41969
  /** Takes the numbers of the accessories on the weapon: the action, the capacity (what no longer fits goes back to the reserve) and the draw time. */
40143
41970
  applyLoad() {
@@ -40310,9 +42137,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
40310
42137
  setStance(stance) {
40311
42138
  const aiming = stance.aiming ?? this.aiming;
40312
42139
  const supported = stance.supported ?? this.supportedStance;
40313
- if (aiming === this.aiming && supported === this.supportedStance) return;
40314
- this.aiming = aiming;
42140
+ const dual = stance.dualWield ?? this.dualStance;
42141
+ const aimed = dual ? false : aiming;
42142
+ if (aimed === this.aiming && supported === this.supportedStance && dual === this.dualStance) return;
42143
+ this.aiming = aimed;
40315
42144
  this.supportedStance = supported;
42145
+ this.dualStance = dual;
40316
42146
  this.applyLoad();
40317
42147
  }
40318
42148
  snapshot() {
@@ -40840,6 +42670,15 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
40840
42670
  const from = [use.origin[0], use.origin[1] + wire.height, use.origin[2]];
40841
42671
  return { from, to: [from[0] + dx * wire.length, from[1], from[2] + dz * wire.length] };
40842
42672
  };
42673
+ function canDualWield(arsenal, weaponId) {
42674
+ const def = arsenal.weapons.get(weaponId);
42675
+ if (!def) return false;
42676
+ return isDualWieldable(arsenal.rules.dualWield, weaponId, def.category);
42677
+ }
42678
+ var isDualWieldable = (rule, weaponId, category) => {
42679
+ if (rule.exclude.includes(weaponId)) return false;
42680
+ return rule.weapons.includes(weaponId) || rule.categories.includes(category);
42681
+ };
40843
42682
  var isGenericKind = (kind, category) => kind === "firearm" && category !== "bow" && category !== "crossbow" || kind === "flame";
40844
42683
  function genericWeapons(arsenal) {
40845
42684
  const archetypes = new Set(ARCHETYPE_IDS);
@@ -47094,6 +48933,11 @@ void main(){
47094
48933
  };
47095
48934
 
47096
48935
  // src/explosives.ts
48936
+ var SMOKE_PUFFS_PER_SECOND = 36;
48937
+ var SMOKE_PUFF_LIFE = 3.2;
48938
+ var SMOKE_PUFF_SPEED = 1.7;
48939
+ var SMOKE_PUFF_SIZE = 5;
48940
+ var SMOKE_PUFF_ALPHA = 1.5;
47097
48941
  var CONE_BLAST_RADIUS = 0.5;
47098
48942
  var summarizeBlast = (results) => {
47099
48943
  const reports = results.flatMap((r) => r.blast ? [r.blast] : []);
@@ -47109,6 +48953,8 @@ void main(){
47109
48953
  function createExplosives(context) {
47110
48954
  const { THREE, scene: scene2, camera: camera2, world, renderer, arsenal, translate, format, action } = context;
47111
48955
  const projectiles = [];
48956
+ const smokeSources = [];
48957
+ const random = context.random ?? Math.random;
47112
48958
  const meshes = [];
47113
48959
  const up = new THREE.Vector3(0, 1, 0);
47114
48960
  const drop = (objects) => {
@@ -47116,6 +48962,7 @@ void main(){
47116
48962
  releaseObject3D(objects, { keep: scene2 });
47117
48963
  };
47118
48964
  const clear = () => {
48965
+ smokeSources.length = 0;
47119
48966
  projectiles.length = 0;
47120
48967
  drop(meshes);
47121
48968
  meshes.length = 0;
@@ -47282,6 +49129,12 @@ void main(){
47282
49129
  const detonate = (projectile, detonation) => {
47283
49130
  const weaponName = context.toolName(projectile.weapon);
47284
49131
  const reach = Math.max(detonation.injuryRadius, detonation.lethalRadius);
49132
+ if (detonation.smoke) {
49133
+ const [shortest, longest] = detonation.smoke.seconds;
49134
+ smokeSources.push({ point: detonation.point, left: shortest + (longest - shortest) * random(), owed: 0, color: detonation.smoke.color });
49135
+ action("smoke", translate("act.smoke", { weapon: weaponName, seconds: format(smokeSources[smokeSources.length - 1].left, 0) }), { data: { weapon: projectile.weapon, explosive: detonation.explosive, point: detonation.point } });
49136
+ return;
49137
+ }
47285
49138
  const results = detonation.energy <= 0 || detonation.cone ? [] : world.explode(detonation.point, detonation.energy, reach, "player", context.blast());
47286
49139
  const reached = detonation.energy <= 0 ? 0 : detonation.cone ? coneBlast(detonation) : results.length;
47287
49140
  if (!detonation.cone) told(results);
@@ -47403,7 +49256,22 @@ void main(){
47403
49256
  action("tripwire", translate("act.tripwire", { weapon: context.toolName(projectile.weapon), who: crosser }), { data: { weapon: projectile.weapon, body: crosser } });
47404
49257
  detonate(projectile, event.detonation);
47405
49258
  };
49259
+ const burnSmoke = (dt) => {
49260
+ for (let index = smokeSources.length - 1; index >= 0; index--) {
49261
+ const source = smokeSources[index];
49262
+ source.left -= dt;
49263
+ source.owed += SMOKE_PUFFS_PER_SECOND * dt;
49264
+ for (; source.owed >= 1; source.owed -= 1) {
49265
+ const angle = random() * Math.PI * 2;
49266
+ const speed = SMOKE_PUFF_SPEED * (0.4 + random());
49267
+ const velocity = [Math.cos(angle) * speed, 0.5 + random() * 0.8, Math.sin(angle) * speed];
49268
+ context.effects.spawn("smoke", [source.point[0], source.point[1] + 0.08, source.point[2]], velocity, { color: source.color, size: SMOKE_PUFF_SIZE, life: SMOKE_PUFF_LIFE, alpha: SMOKE_PUFF_ALPHA });
49269
+ }
49270
+ if (source.left <= 0) smokeSources.splice(index, 1);
49271
+ }
49272
+ };
47406
49273
  const update = (dt) => {
49274
+ burnSmoke(dt);
47407
49275
  for (let index = projectiles.length - 1; index >= 0; index--) {
47408
49276
  const projectile = projectiles[index];
47409
49277
  if (!projectile) continue;
@@ -47424,7 +49292,7 @@ void main(){
47424
49292
  }
47425
49293
  }
47426
49294
  };
47427
- return { throwUse, placeUse, backblastUse, detonateCharges, blastAt, update, clear };
49295
+ return { throwUse, placeUse, backblastUse, detonateCharges, blastAt, update, clear, smokeLeft: () => smokeSources.map((source) => source.left) };
47428
49296
  }
47429
49297
 
47430
49298
  // src/test-table.ts
@@ -47638,6 +49506,65 @@ void main(){
47638
49506
  }
47639
49507
  };
47640
49508
 
49509
+ // src/dual-reload.ts
49510
+ var otherHand = (hand) => hand === "R" ? "L" : "R";
49511
+ function createDualReload(host, stowSeconds) {
49512
+ let queue = [];
49513
+ let current = null;
49514
+ const next = () => {
49515
+ let hand = queue.shift();
49516
+ while (hand && !host.needs(hand)) hand = queue.shift();
49517
+ if (!hand) {
49518
+ current = null;
49519
+ return;
49520
+ }
49521
+ const seconds = stowSeconds();
49522
+ current = { hand, phase: "stow", left: seconds };
49523
+ host.stow(otherHand(hand), true, seconds);
49524
+ };
49525
+ const draw2 = (hand) => {
49526
+ const seconds = stowSeconds();
49527
+ current = { hand, phase: "draw", left: seconds };
49528
+ host.stow(otherHand(hand), false, seconds);
49529
+ };
49530
+ return {
49531
+ start(order) {
49532
+ if (current) return;
49533
+ queue = [...order];
49534
+ next();
49535
+ },
49536
+ step(dt) {
49537
+ if (!current) return;
49538
+ if (current.phase === "reload") {
49539
+ if (!host.reloading(current.hand)) draw2(current.hand);
49540
+ return;
49541
+ }
49542
+ current.left -= dt;
49543
+ if (current.left > 0) return;
49544
+ if (current.phase === "draw") {
49545
+ next();
49546
+ return;
49547
+ }
49548
+ current.phase = "reload";
49549
+ if (!host.startReload(current.hand)) draw2(current.hand);
49550
+ },
49551
+ get busy() {
49552
+ return current !== null;
49553
+ },
49554
+ get current() {
49555
+ return current ? { hand: current.hand, phase: current.phase } : null;
49556
+ },
49557
+ stowed(hand) {
49558
+ return current !== null && current.phase !== "draw" && otherHand(current.hand) === hand;
49559
+ },
49560
+ cancel() {
49561
+ if (current && current.phase !== "draw") host.stow(otherHand(current.hand), false, 0);
49562
+ current = null;
49563
+ queue = [];
49564
+ }
49565
+ };
49566
+ }
49567
+
47641
49568
  // src/loading-view.ts
47642
49569
  var LOADING_CLASS = "tr-loading";
47643
49570
  var LOADING_CSS = [
@@ -49644,6 +51571,10 @@ void main(){
49644
51571
  o.hooks.primaryDown();
49645
51572
  };
49646
51573
  const onMouseUp = (e) => {
51574
+ if (e.button === 2) {
51575
+ o.hooks.secondaryUp?.();
51576
+ return;
51577
+ }
49647
51578
  if (e.button !== 0) return;
49648
51579
  if (holdTimer) clearTimeout(holdTimer);
49649
51580
  if (dragging) {
@@ -53517,7 +55448,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53517
55448
  }
53518
55449
 
53519
55450
  // src/avatar.ts
53520
- var DEFAULT_STRIKE_CLIPS = { hand: "punch_right", fists: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
55451
+ var DEFAULT_STRIKE_CLIPS = { hand: "punch_right", fists: "punch_right", brass_knuckles: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
53521
55452
  var DEFAULT_HAND_STANCES = { fists: "fists" };
53522
55453
  var THIRD_PERSON_DEFAULTS = {
53523
55454
  back: 3.8,
@@ -53690,6 +55621,12 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53690
55621
  "act.clear": "Cleared the room",
53691
55622
  "act.reset": "Repaired every object",
53692
55623
  "act.tool": "Equipped {weapon}",
55624
+ "act.tool_left": "{weapon} in the left hand: left button fires the right one, right button the left one",
55625
+ "act.tool_left_off": "Left hand free: one weapon again",
55626
+ "act.stow": "{weapon} tucked against the body: the {hand} hand is free for the other magazine",
55627
+ "act.unstow": "{weapon} back in the {hand} hand",
55628
+ "hud.no_dual": "{weapon} does not go one in each hand",
55629
+ "hud.left": "L",
53693
55630
  "act.shoot.hit": "{weapon}: shot, {energy} J, hit {where} at {dist} m",
53694
55631
  "act.shoot.miss": "{weapon}: shot, {energy} J, hit nothing",
53695
55632
  "act.shoot.pellets": "{weapon}: shot, {n} projectiles of up to {energy} J, {hits} hit {where} at {dist} m",
@@ -53719,6 +55656,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53719
55656
  "hud.no_charges": "No charge set down waits for the remote detonator",
53720
55657
  "blast.zone.lethal": "lethal",
53721
55658
  "blast.zone.injury": "injury",
55659
+ "act.smoke": "{weapon}: smoke, burning for {seconds} s",
53722
55660
  "act.explode": "{weapon}: exploded, {energy} J within {radius} m, {n} objects in range",
53723
55661
  "act.molotov_fire": "{weapon}: fire on the ground, radius {radius} m for {seconds} s",
53724
55662
  "act.tool.none": "{weapon}: nothing in reach",
@@ -53857,6 +55795,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53857
55795
  "ui.clear_log": "Clear",
53858
55796
  "ui.catalog": "Catalogue",
53859
55797
  "ui.tool_menu": "Weapons",
55798
+ "ui.tool_menu.dual": "Shift+click: one in each hand",
53860
55799
  "ui.tool_menu.search": "Search weapons and tools",
53861
55800
  "ui.tool_menu.close": "Close",
53862
55801
  "ui.tool_menu.results": "{n} found",
@@ -54060,6 +55999,12 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54060
55999
  "act.clear": "Limpou a sala",
54061
56000
  "act.reset": "Consertou todos os objetos",
54062
56001
  "act.tool": "Equipou {weapon}",
56002
+ "act.tool_left": "{weapon} na m\xE3o esquerda: bot\xE3o esquerdo dispara a da direita, bot\xE3o direito a da esquerda",
56003
+ "act.tool_left_off": "M\xE3o esquerda livre: uma arma de novo",
56004
+ "act.stow": "{weapon} encostada no corpo: a m\xE3o {hand} fica livre para o outro carregador",
56005
+ "act.unstow": "{weapon} de volta na m\xE3o {hand}",
56006
+ "hud.no_dual": "{weapon} n\xE3o vai uma em cada m\xE3o",
56007
+ "hud.left": "E",
54063
56008
  "act.shoot.hit": "{weapon}: disparo, {energy} J, acertou {where} a {dist} m",
54064
56009
  "act.shoot.miss": "{weapon}: disparo, {energy} J, n\xE3o acertou nada",
54065
56010
  "act.shoot.pellets": "{weapon}: disparo, {n} proj\xE9teis de at\xE9 {energy} J, {hits} acertaram {where} a {dist} m",
@@ -54089,6 +56034,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54089
56034
  "hud.no_charges": "Nenhuma carga posta espera o detonador",
54090
56035
  "blast.zone.lethal": "letal",
54091
56036
  "blast.zone.injury": "de ferimento",
56037
+ "act.smoke": "{weapon}: fuma\xE7a, queima por {seconds} s",
54092
56038
  "act.explode": "{weapon}: explodiu, {energy} J em {radius} m, {n} objetos ao alcance",
54093
56039
  "act.molotov_fire": "{weapon}: fogo no ch\xE3o, raio de {radius} m por {seconds} s",
54094
56040
  "act.tool.none": "{weapon}: nada ao alcance",
@@ -54227,6 +56173,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54227
56173
  "ui.clear_log": "Limpar",
54228
56174
  "ui.catalog": "Cat\xE1logo",
54229
56175
  "ui.tool_menu": "Armas",
56176
+ "ui.tool_menu.dual": "Shift+clique: uma em cada m\xE3o",
54230
56177
  "ui.tool_menu.search": "Buscar armas e ferramentas",
54231
56178
  "ui.tool_menu.close": "Fechar",
54232
56179
  "ui.tool_menu.results": "{n} encontradas",
@@ -54435,6 +56382,27 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54435
56382
  return wrap;
54436
56383
  };
54437
56384
  let playhead = null;
56385
+ const sheetImage = (image) => {
56386
+ const figure = mk("figure", "tr-sheet-image");
56387
+ const picture = mk("img");
56388
+ picture.src = image.url;
56389
+ picture.alt = image.alt;
56390
+ picture.loading = "lazy";
56391
+ picture.referrerPolicy = "no-referrer";
56392
+ figure.appendChild(picture);
56393
+ if (image.credit || image.page) {
56394
+ const caption = mk("figcaption", "sub", image.credit ?? "");
56395
+ if (image.page) {
56396
+ const link = mk("a", void 0, image.credit ? " \u2197" : image.page);
56397
+ link.href = image.page;
56398
+ link.target = "_blank";
56399
+ link.rel = "noopener noreferrer";
56400
+ caption.appendChild(link);
56401
+ }
56402
+ figure.appendChild(caption);
56403
+ }
56404
+ return figure;
56405
+ };
54438
56406
  const sheetCard = (sheet) => {
54439
56407
  const card = mk("div", "tr-sheet");
54440
56408
  if (sheet.kicker) card.appendChild(mk("div", "sub", sheet.kicker));
@@ -54486,6 +56454,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54486
56454
  playhead = { node: head, metric, end: end3 };
54487
56455
  card.appendChild(strip);
54488
56456
  }
56457
+ if (sheet.image) card.appendChild(sheetImage(sheet.image));
54489
56458
  if (sheet.note) card.appendChild(mk("p", "sub", sheet.note));
54490
56459
  return card;
54491
56460
  };
@@ -54719,7 +56688,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54719
56688
  const close = mk3("button", "tr-btn", "\xD7");
54720
56689
  close.type = "button";
54721
56690
  close.title = o.t("ui.tool_menu.close");
54722
- header.append(mk3("h3", void 0, o.t("ui.tool_menu")), search, close);
56691
+ header.append(mk3("h3", void 0, o.t("ui.tool_menu")), mk3("small", "tr-hint", o.t("ui.tool_menu.dual")), search, close);
54723
56692
  const tabs = mk3("div", "tr-tabs");
54724
56693
  const body = mk3("div", "tr-wmenu-body");
54725
56694
  element.append(header, tabs, body);
@@ -54727,15 +56696,17 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54727
56696
  let activeGroup = "";
54728
56697
  let groups = [];
54729
56698
  const toolButton = (tool, group) => {
54730
- const button = mk3("button", tool.id === o.current() ? "tr-btn on" : "tr-btn");
56699
+ const left = o.currentLeft?.() === tool.id;
56700
+ const button = mk3("button", tool.id === o.current() || left ? "tr-btn on" : "tr-btn");
54731
56701
  button.type = "button";
54732
56702
  button.title = tool.name;
56703
+ if (left) button.dataset.left = "";
54733
56704
  button.dataset.tool = tool.id;
54734
56705
  if (tool.thumbnail) button.appendChild(o.thumb(tool.thumbnail, tool.thumbnailCredit));
54735
56706
  button.appendChild(mk3("span", void 0, tool.name));
54736
56707
  if (group) button.appendChild(mk3("small", void 0, group));
54737
- button.onclick = () => {
54738
- o.onPick(tool.id);
56708
+ button.onclick = (event) => {
56709
+ o.onPick(tool.id, { second: event.shiftKey });
54739
56710
  toggle(false);
54740
56711
  };
54741
56712
  return button;
@@ -54831,6 +56802,8 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54831
56802
  .tr-prog b{display:block;font-weight:500;margin-bottom:3px}
54832
56803
  .tr-sheet{margin-top:8px;padding:8px;border:1px solid #2a2e36;border-radius:6px;font-size:12px;line-height:1.4}
54833
56804
  .tr-sheet h4{margin:2px 0 4px;font-size:13px}.tr-sheet p{margin:4px 0}
56805
+ /* the picture of a sheet floats over the scene, top centre, next to what it shows: the bench panel is too short to hold it */
56806
+ .tr-sheet-image{position:fixed;top:52px;left:50%;transform:translateX(-50%);width:min(400px,42vw);z-index:5;margin:0;padding:6px;background:rgba(18,20,24,.92);border:1px solid #2a2e36;border-radius:6px;pointer-events:auto}.tr-sheet-image img{display:block;width:100%;max-height:200px;object-fit:contain;background:#f4f4f2;border-radius:4px}.tr-sheet-image figcaption{margin-top:3px;font-size:11px}.tr-sheet-image a{color:#8ab4f8}
54834
56807
  .tr-facts{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;margin:4px 0}.tr-facts dt{opacity:.7}.tr-facts dd{margin:0}
54835
56808
  .tr-steps{margin:4px 0;padding-left:16px}.tr-swatches{display:flex;flex-wrap:wrap;gap:6px}.tr-swatch i{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:4px}
54836
56809
  .tr-timeline{position:relative;height:22px;margin:8px 0;background:#1b1e24;border-radius:4px;overflow:hidden}
@@ -54958,7 +56931,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54958
56931
  bottom.append(held, hotbar);
54959
56932
  let currentTool = "";
54960
56933
  const menuOptions = o.toolMenu;
54961
- const toolMenu = menuOptions ? createToolMenu({ t, groups: menuOptions.groups, current: () => currentTool, onPick: menuOptions.onPick, ...menuOptions.onOpen ? { onOpen: menuOptions.onOpen } : {}, thumb }) : null;
56934
+ const toolMenu = menuOptions ? createToolMenu({ t, groups: menuOptions.groups, current: () => currentTool, onPick: menuOptions.onPick, ...menuOptions.onOpen ? { onOpen: menuOptions.onOpen } : {}, ...menuOptions.currentLeft ? { currentLeft: menuOptions.currentLeft } : {}, thumb }) : null;
54962
56935
  if (toolMenu && menuOptions) {
54963
56936
  const menuButton = mk4("button", "tr-slot tr-menu-btn");
54964
56937
  menuButton.type = "button";
@@ -55606,6 +57579,8 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
55606
57579
 
55607
57580
  // src/room.ts
55608
57581
  var FIXED_DT = 1 / 60;
57582
+ var LEFT_HAND_KEY = "#L";
57583
+ var OFF_HAND_SEED = 11;
55609
57584
  var TOOL_MENU_KEY_LABELS = { Backquote: "'" };
55610
57585
  var EXPLOSIVE_EFFECT_IDS = ["m67", "molotov", "pipe_bomb", "m79_40mm", "m72_66mm", "at4_84mm", "dynamite", "c4", "claymore"];
55611
57586
  var DEFAULT_PARTICLE_LIMIT = 900;
@@ -55901,6 +57876,11 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
55901
57876
  return s;
55902
57877
  };
55903
57878
  let tool = toolState(toolId);
57879
+ let offToolId = null;
57880
+ let offTool = null;
57881
+ let offHeld = false;
57882
+ let offFirst = false;
57883
+ let firedHand = "R";
55904
57884
  const catalogMap = /* @__PURE__ */ new Map();
55905
57885
  for (const i of options.catalog) catalogMap.set(i.id, i);
55906
57886
  const catalog = [...options.catalog];
@@ -56110,9 +58090,19 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56110
58090
  releaseTrigger();
56111
58091
  },
56112
58092
  secondary: () => {
58093
+ if (offTool && !heldEntry) {
58094
+ offHeld = true;
58095
+ offFirst = true;
58096
+ return;
58097
+ }
56113
58098
  if (heldEntry) throwHeld();
56114
58099
  else grab();
56115
58100
  },
58101
+ secondaryUp: () => {
58102
+ if (!offHeld) return;
58103
+ offHeld = false;
58104
+ releaseTrigger("L");
58105
+ },
56116
58106
  key: (code, down, e) => onKey(code, down, e),
56117
58107
  wheel: (dir) => cycleTool(dir)
56118
58108
  }
@@ -56651,6 +58641,10 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56651
58641
  for (const listener of [...resetListeners]) listener();
56652
58642
  tools.clear();
56653
58643
  tool = toolState(toolId);
58644
+ dualReload.cancel();
58645
+ offTool = null;
58646
+ offToolId = null;
58647
+ firedHand = "R";
56654
58648
  if (pristineWorld) {
56655
58649
  for (const id of world.ids()) world.remove(id);
56656
58650
  world.load(pristineWorld);
@@ -56916,12 +58910,13 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56916
58910
  const pocketAt = new THREE.Vector3();
56917
58911
  const aimTurn = new THREE.Quaternion();
56918
58912
  const aimEuler = new THREE.Euler(0, 0, 0, "YXZ");
56919
- const bodyHoldFrame = () => {
56920
- const shoulder = avatar?.shoulder?.("R");
58913
+ const bodyHoldFrame = (hand = "R") => {
58914
+ const shoulder = avatar?.shoulder?.(hand);
56921
58915
  if (!shoulder) return null;
56922
58916
  shoulder.getWorldPosition(pocketAt);
56923
- pocketAt.x -= Math.cos(player.yaw) * SHOULDER_POCKET_IN;
56924
- pocketAt.z += Math.sin(player.yaw) * SHOULDER_POCKET_IN;
58917
+ const inward = hand === "L" ? -SHOULDER_POCKET_IN : SHOULDER_POCKET_IN;
58918
+ pocketAt.x -= Math.cos(player.yaw) * inward;
58919
+ pocketAt.z += Math.sin(player.yaw) * inward;
56925
58920
  aimTurn.setFromEuler(aimEuler.set(player.pitch, player.yaw, 0, "YXZ"));
56926
58921
  return { position: [pocketAt.x, pocketAt.y, pocketAt.z], quaternion: [aimTurn.x, aimTurn.y, aimTurn.z, aimTurn.w] };
56927
58922
  };
@@ -57031,17 +59026,18 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57031
59026
  rng = rng * 1664525 + 1013904223 >>> 0;
57032
59027
  return rng / 4294967296;
57033
59028
  };
57034
- const muzzlePose = () => {
57035
- const lent = muzzleSource?.();
57036
- return lent ? { position: [...lent.position], drawsFlash: lent.drawsFlash } : { position: muzzle2(), drawsFlash: false };
59029
+ const muzzlePose = (hand = "R") => {
59030
+ const lent = muzzleSource?.(hand);
59031
+ return lent ? { position: [...lent.position], drawsFlash: lent.drawsFlash } : { position: muzzle2(hand), drawsFlash: false };
57037
59032
  };
57038
- const muzzle2 = () => {
59033
+ const muzzle2 = (hand = "R") => {
57039
59034
  aim();
57040
59035
  const e = camera2.matrixWorld.elements;
59036
+ const side = hand === "L" ? -0.16 : 0.16;
57041
59037
  return [
57042
- aimO[0] + aimD[0] * 0.45 + e[0] * 0.16 - e[4] * 0.1,
57043
- aimO[1] + aimD[1] * 0.45 + e[1] * 0.16 - e[5] * 0.1,
57044
- aimO[2] + aimD[2] * 0.45 + e[2] * 0.16 - e[6] * 0.1
59038
+ aimO[0] + aimD[0] * 0.45 + e[0] * side - e[4] * 0.1,
59039
+ aimO[1] + aimD[1] * 0.45 + e[1] * side - e[5] * 0.1,
59040
+ aimO[2] + aimD[2] * 0.45 + e[2] * side - e[6] * 0.1
57045
59041
  ];
57046
59042
  };
57047
59043
  const tracers = [];
@@ -57221,10 +59217,10 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57221
59217
  renderer.impulse(objectId, push2.impulse, push2.point);
57222
59218
  }
57223
59219
  };
57224
- const fireShots = (shots, recoil) => {
59220
+ const fireShots = (shots, recoil, id = toolId, hand = "R") => {
57225
59221
  if (shots.length === 0) return;
57226
- const w = toolName();
57227
- const { position: m, drawsFlash } = muzzlePose();
59222
+ const w = toolName(id);
59223
+ const { position: m, drawsFlash } = muzzlePose(hand);
57228
59224
  if (!drawsFlash) {
57229
59225
  renderer.emit("flash", m, [aimD[0], aimD[1], aimD[2]], 1, 0);
57230
59226
  renderer.emit("sparks", m, [aimD[0], aimD[1], aimD[2]], 4, 6);
@@ -57263,7 +59259,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57263
59259
  objectId: f?.object,
57264
59260
  itemId: f ? entryOf(f.object)?.so.itemId : void 0,
57265
59261
  part: f?.part,
57266
- data: { weapon: toolId, shots: shots.length, hits, energyJ: Math.round(energy), caliberMm: shots[0].caliber, distanceM: f ? Math.round(f.distance * 100) / 100 : null }
59262
+ data: { weapon: id, shots: shots.length, hits, energyJ: Math.round(energy), caliberMm: shots[0].caliber, distanceM: f ? Math.round(f.distance * 100) / 100 : null, ...hand === "L" ? { hand } : {} }
57267
59263
  });
57268
59264
  };
57269
59265
  const fireRays = (u) => {
@@ -57367,7 +59363,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57367
59363
  const kickLeg = options.kickLeg ?? "alternate";
57368
59364
  let nextLeg = "right";
57369
59365
  const punchHand = options.punchHand ?? "alternate";
57370
- const punchTools = new Set(options.punchTools ?? ["hand", "fists"]);
59366
+ const punchTools = new Set(options.punchTools ?? ["hand", "fists", "brass_knuckles"]);
57371
59367
  let nextHand = "right";
57372
59368
  const combo = options.strikeCombo;
57373
59369
  let comboOn = !!combo && options.strikeComboOn === true;
@@ -57381,16 +59377,16 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57381
59377
  action("combo", t(on ? "act.combo.on" : "act.combo.off", { name }), { data: { combo: combo.id, on } });
57382
59378
  say(t(on ? "act.combo.on" : "act.combo.off", { name }));
57383
59379
  };
57384
- const strikeUse = (u, id = toolId) => {
59380
+ const strikeUse = (u, id = toolId, side) => {
57385
59381
  const stepIndex = comboOn && combo?.tools.includes(id) && combo.steps.length > 0 ? simTime - comboAt > combo.reset ? 0 : comboNext : null;
57386
59382
  const step2 = stepIndex === null ? void 0 : combo.steps[stepIndex];
57387
59383
  const clip = step2 ? step2.clip : strikeClips[id];
57388
59384
  const leg = id === "kick" ? kickLeg === "alternate" ? nextLeg : kickLeg : void 0;
57389
- const hand = step2 ? step2.hand : punchTools.has(id) ? punchHand === "alternate" ? nextHand : punchHand : void 0;
57390
- const side = leg ? { leg } : hand ? { hand } : void 0;
57391
- const seconds = clip === void 0 ? null : avatar?.strike?.(clip, side) ?? null;
59385
+ const hand = side ? side.hand : step2 ? step2.hand : punchTools.has(id) ? punchHand === "alternate" ? nextHand : punchHand : void 0;
59386
+ const limb = leg ? { leg } : hand ? { hand } : void 0;
59387
+ const seconds = clip === void 0 ? null : avatar?.strike?.(clip, limb) ?? null;
57392
59388
  if (leg && seconds !== null && kickLeg === "alternate") nextLeg = leg === "right" ? "left" : "right";
57393
- if (!step2 && hand && seconds !== null && punchHand === "alternate") nextHand = hand === "right" ? "left" : "right";
59389
+ if (!step2 && !side && hand && seconds !== null && punchHand === "alternate") nextHand = hand === "right" ? "left" : "right";
57394
59390
  if (step2 && stepIndex !== null) {
57395
59391
  comboNext = (stepIndex + 1) % combo.steps.length;
57396
59392
  comboAt = simTime;
@@ -57411,7 +59407,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57411
59407
  data: { weapon: id, clip, impactIn: seconds, ...leg ? { leg } : {}, ...hand ? { hand } : {}, ...step2 ? { combo: combo.id, step: stepIndex } : {} }
57412
59408
  });
57413
59409
  strikes.push({
57414
- key: id,
59410
+ key: side?.key ?? id,
57415
59411
  remaining: seconds,
57416
59412
  ...swing && seconds > lead ? { swing, lead } : {},
57417
59413
  land: () => {
@@ -57541,20 +59537,50 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57541
59537
  say,
57542
59538
  projectileModel: options.projectileModel
57543
59539
  });
57544
- const useTool = (dt, held) => {
59540
+ const toolOf = (hand) => hand === "L" ? offTool && offToolId ? { id: offToolId, state: offTool } : null : { id: toolId, state: tool };
59541
+ const dualReload = createDualReload(
59542
+ {
59543
+ needs: (hand) => {
59544
+ const state = toolOf(hand)?.state;
59545
+ return state !== void 0 && (state.def.action.kind === "firearm" || state.def.action.kind === "launcher") && state.ammo < state.capacity && state.reserve > 0;
59546
+ },
59547
+ startReload: (hand) => startReload(hand),
59548
+ reloading: (hand) => toolOf(hand)?.state.reloading ?? false,
59549
+ stow: (hand, on, seconds) => {
59550
+ const slot2 = toolOf(hand);
59551
+ if (!slot2) return;
59552
+ if (on) slot2.state.release();
59553
+ emitHost({ type: "stow", weapon: slot2.id, on, duration: seconds, ...hand === "L" ? { hand } : {} });
59554
+ action("stow", t(on ? "act.stow" : "act.unstow", { weapon: toolName(slot2.id), hand: t(hand === "L" ? "hand.left" : "hand.right") }), { data: { weapon: slot2.id, hand, on, seconds } });
59555
+ }
59556
+ },
59557
+ () => arsenal.rules.dualWield.stowSeconds
59558
+ );
59559
+ const strikeKeyOf = (id, hand) => hand === "L" ? `${id}${LEFT_HAND_KEY}` : id;
59560
+ const useTool = (dt, held, hand = "R") => {
57545
59561
  aim();
57546
- const cat = tool.def.category;
59562
+ const slot2 = toolOf(hand);
59563
+ if (!slot2) return "cooldown";
59564
+ const { id, state } = slot2;
59565
+ if (offTool && dualReload.busy) {
59566
+ if (!held) say(t("hud.reloading"));
59567
+ return "reloading";
59568
+ }
59569
+ const cat = state.def.category;
57547
59570
  if (cat === "hand") {
57548
59571
  if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return "cooldown";
57549
59572
  strikeUse({ kind: "melee", damage: "blunt", energy: 25, radius: 0.1, reach: 0.8, origin: [aimO[0], aimO[1], aimO[2]], direction: [aimD[0], aimD[1], aimD[2]] }, "hand");
57550
59573
  return "ok";
57551
59574
  }
57552
- if (strikes.some((strike) => strike.key === toolId)) return "cooldown";
57553
- const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
57554
- flushEvents(toolId, tool, res.recoil);
59575
+ if (strikes.some((strike) => strike.key === strikeKeyOf(id, hand))) return "cooldown";
59576
+ const res = state.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
59577
+ flushEvents(id, state, res.recoil, hand);
57555
59578
  if (res.status === "empty") {
57556
- if (!tool.reloading && tool.reserve > 0) startReload();
57557
- say(t(tool.reloading ? "hud.reloading" : "hud.empty"));
59579
+ if (!state.reloading && state.reserve > 0) {
59580
+ if (offTool) dualReload.start([hand, hand === "R" ? "L" : "R"]);
59581
+ else startReload(hand);
59582
+ }
59583
+ say(t(state.reloading ? "hud.reloading" : "hud.empty"));
57558
59584
  return res.status;
57559
59585
  }
57560
59586
  if (res.status !== "ok") {
@@ -57564,15 +59590,18 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57564
59590
  else if (res.status === "safe") say(t("hud.safe"));
57565
59591
  return res.status;
57566
59592
  }
57567
- applyUses(res, dt);
59593
+ firedHand = hand;
59594
+ applyUses(res, dt, hand);
57568
59595
  return res.status;
57569
59596
  };
57570
- const applyUses = (res, dt) => {
57571
- if (res.shots.length) fireShots(res.shots, res.recoil);
59597
+ const applyUses = (res, dt, hand = "R") => {
59598
+ const id = toolOf(hand)?.id ?? toolId;
59599
+ if (res.shots.length) fireShots(res.shots, res.recoil, id, hand);
57572
59600
  for (const u of res.uses) {
57573
59601
  switch (u.kind) {
57574
59602
  case "melee":
57575
- strikeUse(u);
59603
+ if (hand === "L") strikeUse(u, id, { hand: "left", key: strikeKeyOf(id, hand) });
59604
+ else strikeUse(u);
57576
59605
  break;
57577
59606
  case "flame":
57578
59607
  heatRays(u, "fire");
@@ -57600,15 +59629,17 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57600
59629
  }
57601
59630
  }
57602
59631
  };
57603
- const releaseTrigger = () => {
57604
- if (!tool.cooking) {
57605
- tool.release();
59632
+ const releaseTrigger = (hand = "R") => {
59633
+ const slot2 = toolOf(hand);
59634
+ if (!slot2) return;
59635
+ if (!slot2.state.cooking) {
59636
+ slot2.state.release();
57606
59637
  return;
57607
59638
  }
57608
59639
  aim();
57609
- const res = tool.release([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]]);
57610
- flushEvents(toolId, tool);
57611
- if (res) applyUses(res, 0);
59640
+ const res = slot2.state.release([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]]);
59641
+ flushEvents(slot2.id, slot2.state, void 0, hand);
59642
+ if (res) applyUses(res, 0, hand);
57612
59643
  };
57613
59644
  const kick = () => {
57614
59645
  if (!arsenal.weapons.has("kick") || strikes.some((strike) => strike.key === "kick")) return false;
@@ -57637,8 +59668,43 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
57637
59668
  }));
57638
59669
  };
57639
59670
  const toggleToolMenu = (open) => ui?.toolMenu(open) ?? false;
59671
+ const dropOffTool = () => {
59672
+ if (!offTool) return;
59673
+ releaseTrigger("L");
59674
+ offTool = null;
59675
+ offToolId = null;
59676
+ offHeld = false;
59677
+ offFirst = false;
59678
+ dualReload.cancel();
59679
+ firedHand = "R";
59680
+ tool.setStance({ dualWield: false });
59681
+ action("tool_left", t("act.tool_left_off"), { data: { hand: "L" } });
59682
+ emitHost({ type: "tool", weapon: "", hand: "L" });
59683
+ };
59684
+ const setOffTool = (id) => {
59685
+ if (id === null) {
59686
+ dropOffTool();
59687
+ return true;
59688
+ }
59689
+ if (!arsenal.weapons.has(id)) throw new Error(`game-test-room: unknown weapon "${id}"`);
59690
+ if (!canDualWield(arsenal, id) || !canDualWield(arsenal, toolId)) return false;
59691
+ if (offTool) releaseTrigger("L");
59692
+ const state = arsenal.create(id, { seed: OFF_HAND_SEED, reserve: 999, ...toolSettings });
59693
+ state.setFuseLength(fuseLength);
59694
+ state.refill();
59695
+ state.setStance({ dualWield: true });
59696
+ tool.setStance({ dualWield: true });
59697
+ offTool = state;
59698
+ offToolId = id;
59699
+ dualReload.cancel();
59700
+ action("tool_left", t("act.tool_left", { weapon: toolName(id) }), { data: { weapon: id, hand: "L" } });
59701
+ state.draw();
59702
+ flushEvents(id, state, void 0, "L");
59703
+ return true;
59704
+ };
57640
59705
  const setTool = (id) => {
57641
59706
  if (!arsenal.weapons.has(id)) throw new Error(`game-test-room: unknown weapon "${id}"`);
59707
+ dropOffTool();
57642
59708
  releaseTrigger();
57643
59709
  toolId = id;
57644
59710
  const i = loadout.indexOf(id);
@@ -58131,15 +60197,13 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58131
60197
  case "KeyR":
58132
60198
  if (heldPiece) heldYaw += Math.PI / 8;
58133
60199
  else {
58134
- startReload();
60200
+ reloadHands();
58135
60201
  say(t("hud.reloading"));
58136
60202
  }
58137
60203
  break;
58138
- case "KeyQ": {
58139
- tool.cycleMode();
58140
- flushEvents(toolId, tool);
60204
+ case "KeyQ":
60205
+ cycleToolMode();
58141
60206
  break;
58142
- }
58143
60207
  case "KeyX":
58144
60208
  clearToolFault();
58145
60209
  break;
@@ -58196,6 +60260,20 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58196
60260
  if (!loadout.includes(weaponId)) loadout.push(weaponId);
58197
60261
  setTool(weaponId);
58198
60262
  };
60263
+ const pickTool = (weaponId, pick3 = { second: false }) => {
60264
+ if (!arsenal.weapons.has(weaponId)) return;
60265
+ if (!pick3.second) {
60266
+ equip(weaponId);
60267
+ return;
60268
+ }
60269
+ if (!canDualWield(arsenal, weaponId)) {
60270
+ equip(weaponId);
60271
+ say(t("hud.no_dual", { weapon: toolName(weaponId) }));
60272
+ return;
60273
+ }
60274
+ if (!offTool || !canDualWield(arsenal, toolId)) equip(weaponId);
60275
+ setOffTool(weaponId);
60276
+ };
58199
60277
  let hudAimHit;
58200
60278
  const inspectTarget = () => {
58201
60279
  if (hudAimHit !== void 0) return hudAimHit;
@@ -58399,6 +60477,11 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58399
60477
  s.update(dt);
58400
60478
  flushEvents(id, s);
58401
60479
  }
60480
+ if (offTool && offToolId) {
60481
+ offTool.update(dt);
60482
+ flushEvents(offToolId, offTool, void 0, "L");
60483
+ dualReload.step(dt);
60484
+ }
58402
60485
  if (audio) {
58403
60486
  const look = camera2.getWorldDirection(listenerDirection);
58404
60487
  audio.setListener({ position: [camera2.position.x, camera2.position.y, camera2.position.z], yaw: Math.atan2(-look.x, -look.z) });
@@ -58444,6 +60527,10 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58444
60527
  } else if (useHeld) useTool(dt, !useFirst);
58445
60528
  useFirst = false;
58446
60529
  }
60530
+ if ((offHeld || offFirst) && offTool && !isPaused && !push.active) {
60531
+ useTool(dt, !offFirst, "L");
60532
+ offFirst = false;
60533
+ }
58447
60534
  if (!isPaused) followHeld(dt);
58448
60535
  };
58449
60536
  const updatePresent = (dt) => {
@@ -58470,12 +60557,18 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58470
60557
  hudAimHit = void 0;
58471
60558
  }
58472
60559
  };
60560
+ const ammoText = (state) => {
60561
+ const kind = state.def.action.kind;
60562
+ if (kind === "firearm") return `${state.ammo}/${state.capacity}${state.reserve ? ` \xB7 ${state.reserve}` : ""}`;
60563
+ return kind === "hand" || kind === "melee" || kind === "tool" ? "" : `${num(state.ammo, 0)}`;
60564
+ };
58473
60565
  const readHudState = () => {
58474
60566
  const page = Math.floor(toolIndex / 10);
58475
60567
  const slots = loadout.slice(page * 10, page * 10 + 10).map((id) => arsenal.name(id));
58476
- const ammo = tool.def.action.kind === "firearm" ? `${tool.ammo}/${tool.capacity}${tool.reserve ? ` \xB7 ${tool.reserve}` : ""}` : tool.def.action.kind === "hand" ? "" : tool.def.action.kind === "melee" || tool.def.action.kind === "tool" ? "" : `${num(tool.ammo, 0)}`;
60568
+ const left = offTool ? ammoText(offTool) : "";
60569
+ const ammo = `${ammoText(tool)}${left ? ` | ${t("hud.left")} ${left}` : ""}`;
58477
60570
  return {
58478
- toolName: toolName(),
60571
+ toolName: offToolId ? `${toolName()} + ${toolName(offToolId)}` : toolName(),
58479
60572
  toolId,
58480
60573
  ammo,
58481
60574
  slots,
@@ -58665,26 +60758,53 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58665
60758
  if (event.type === "explosion") knockPlayer(event);
58666
60759
  for (const listener of hostListeners) listener(event);
58667
60760
  }
58668
- function flushEvents(id, state, recoil) {
60761
+ function flushEvents(id, state, recoil, hand = "R") {
58669
60762
  const phase = state.phase;
58670
60763
  for (const event of state.drain()) {
58671
60764
  const hostEvent = toHostEvent(id, event, { ...recoil ? { recoil } : {}, phase });
58672
- if (hostEvent) emitHost(hostEvent);
60765
+ if (hostEvent) emitHost(hand === "L" ? { ...hostEvent, hand } : hostEvent);
58673
60766
  }
58674
60767
  }
58675
- function startReload() {
58676
- tool.reload();
58677
- flushEvents(toolId, tool);
60768
+ function startReload(hand = "R") {
60769
+ const slot2 = toolOf(hand);
60770
+ if (!slot2) return false;
60771
+ const started = slot2.state.reload();
60772
+ flushEvents(slot2.id, slot2.state, void 0, hand);
60773
+ return started;
60774
+ }
60775
+ function reloadHands() {
60776
+ if (!offTool) {
60777
+ startReload();
60778
+ return;
60779
+ }
60780
+ dualReload.start(["R", "L"]);
58678
60781
  }
60782
+ const commandHand = () => offTool ? firedHand : "R";
58679
60783
  function clearToolFault() {
58680
- if (!tool.clearFault()) return;
58681
- flushEvents(toolId, tool);
58682
- say(t("hud.clearing"));
60784
+ const order = offTool ? [firedHand, firedHand === "R" ? "L" : "R"] : ["R"];
60785
+ for (const hand of order) {
60786
+ const slot2 = toolOf(hand);
60787
+ if (!slot2?.state.fault || !slot2.state.clearFault()) continue;
60788
+ flushEvents(slot2.id, slot2.state, void 0, hand);
60789
+ say(t("hud.clearing"));
60790
+ return;
60791
+ }
58683
60792
  }
58684
60793
  function toggleSafety() {
58685
- tool.setSafety(!tool.safety);
58686
- flushEvents(toolId, tool);
58687
- say(t(tool.safety ? "hud.safety_on" : "hud.safety_off"));
60794
+ const hand = commandHand();
60795
+ const slot2 = toolOf(hand);
60796
+ if (!slot2) return;
60797
+ slot2.state.setSafety(!slot2.state.safety);
60798
+ flushEvents(slot2.id, slot2.state, void 0, hand);
60799
+ say(`${t(slot2.state.safety ? "hud.safety_on" : "hud.safety_off")}${offTool ? ` (${toolName(slot2.id)}, ${t(hand === "L" ? "hand.left" : "hand.right")})` : ""}`);
60800
+ }
60801
+ function cycleToolMode() {
60802
+ const hand = commandHand();
60803
+ const slot2 = toolOf(hand);
60804
+ if (!slot2) return;
60805
+ slot2.state.cycleMode();
60806
+ flushEvents(slot2.id, slot2.state, void 0, hand);
60807
+ if (offTool) say(`${toolName(slot2.id)} (${t(hand === "L" ? "hand.left" : "hand.right")}): ${slot2.state.mode}`);
58688
60808
  }
58689
60809
  function runToolCommand(request) {
58690
60810
  const work = (done) => done.ok ? { ok: true, seconds: done.seconds } : { ok: false, reason: done.reason };
@@ -58706,6 +60826,24 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58706
60826
  return { ok: false, reason: "unknown" };
58707
60827
  }
58708
60828
  }
60829
+ const hostToolOf = (id, state) => ({
60830
+ weapon: id,
60831
+ category: state.def.category,
60832
+ ammo: state.ammo,
60833
+ capacity: state.capacity,
60834
+ reserve: state.reserve,
60835
+ reloading: state.reloading,
60836
+ mode: state.mode,
60837
+ ...state.fault ? { fault: state.fault } : {},
60838
+ safety: state.safety,
60839
+ heat: state.heatLevel,
60840
+ attachments: state.attachments.map((part) => ({ id: part.id, mount: part.mount, ...part.integral ? { integral: true } : {}, ...part.on === void 0 ? {} : { on: part.on } })),
60841
+ stock: state.stock,
60842
+ suppressed: state.suppressed,
60843
+ underbarrelActive: state.underbarrelActive,
60844
+ mass: state.mass,
60845
+ aimSeconds: state.aimSeconds
60846
+ });
58709
60847
  const host = {
58710
60848
  THREE,
58711
60849
  scene: scene2,
@@ -58732,22 +60870,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58732
60870
  ...options.blastShove ? { blastShove: options.blastShove } : {},
58733
60871
  blastReach: (point, target, ignore) => world.blastReach(point, target, ignore ? { ignore } : {}),
58734
60872
  tool: () => ({
58735
- weapon: toolId,
58736
- category: tool.def.category,
58737
- ammo: tool.ammo,
58738
- capacity: tool.capacity,
58739
- reserve: tool.reserve,
58740
- reloading: tool.reloading,
58741
- mode: tool.mode,
58742
- ...tool.fault ? { fault: tool.fault } : {},
58743
- safety: tool.safety,
58744
- heat: tool.heatLevel,
58745
- attachments: tool.attachments.map((part) => ({ id: part.id, mount: part.mount, ...part.integral ? { integral: true } : {}, ...part.on === void 0 ? {} : { on: part.on } })),
58746
- stock: tool.stock,
58747
- suppressed: tool.suppressed,
58748
- underbarrelActive: tool.underbarrelActive,
58749
- mass: tool.mass,
58750
- aimSeconds: tool.aimSeconds
60873
+ ...hostToolOf(toolId, tool),
60874
+ ...dualReload.stowed("R") ? { stowed: true } : {},
60875
+ ...offTool && offToolId ? { offHand: { ...hostToolOf(offToolId, offTool), ...dualReload.stowed("L") ? { stowed: true } : {} } } : {}
58751
60876
  }),
58752
60877
  command(request) {
58753
60878
  const result2 = runToolCommand(request);
@@ -58784,7 +60909,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58784
60909
  onFrame(update, frameOptions) {
58785
60910
  return scheduler.add((ctx) => update(ctx.dt), { phase: frameOptions?.phase ?? "lateSimulate", order: frameOptions?.order ?? 0, id: `module.frame#${moduleFrameCount++}` });
58786
60911
  },
58787
- holdFrame: () => bodyHoldFrame(),
60912
+ holdFrame: (hand) => bodyHoldFrame(hand),
58788
60913
  reachFor(reach) {
58789
60914
  if (!avatar) {
58790
60915
  reach.onGrip?.();
@@ -59160,6 +61285,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
59160
61285
  fluids,
59161
61286
  film: filmBridge,
59162
61287
  vfx: vfx.world,
61288
+ effects: vfx,
59163
61289
  fluidRenderer: fluidView,
59164
61290
  audio,
59165
61291
  environment,
@@ -59175,6 +61301,29 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
59175
61301
  clear,
59176
61302
  resetAll,
59177
61303
  setTool,
61304
+ setOffTool,
61305
+ get offTool() {
61306
+ return offTool;
61307
+ },
61308
+ useOff(hold = 0) {
61309
+ if (!offTool) return "cooldown";
61310
+ const status = useTool(FIXED_DT, false, "L");
61311
+ flush();
61312
+ let t0 = 0;
61313
+ while (t0 < hold) {
61314
+ useTool(FIXED_DT, true, "L");
61315
+ directStep = true;
61316
+ try {
61317
+ runHeadless(() => scheduler.advance(FIXED_DT));
61318
+ } finally {
61319
+ directStep = false;
61320
+ }
61321
+ t0 += FIXED_DT;
61322
+ }
61323
+ releaseTrigger("L");
61324
+ return status;
61325
+ },
61326
+ reload: () => reloadHands(),
59178
61327
  useItem,
59179
61328
  use(hold = 0) {
59180
61329
  if (tool.def.category === "hand") {
@@ -59472,7 +61621,8 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
59472
61621
  ...options.toolMenu !== false ? {
59473
61622
  toolMenu: {
59474
61623
  groups: toolMenuGroups,
59475
- onPick: equip,
61624
+ onPick: pickTool,
61625
+ currentLeft: () => offToolId,
59476
61626
  // the list takes clicks: the mouse leaves the view while it is open
59477
61627
  onOpen: (open) => {
59478
61628
  if (open && document.pointerLockElement) document.exitPointerLock?.();