@adatechnology/game-items-goods 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -49,6 +49,12 @@ var createGoodsFindsProvider = (catalog, options = {}) => createFindsProvider(ca
49
49
  }
50
50
  });
51
51
 
52
+ // src/kinds.ts
53
+ import { checkCategorized, defineCategories } from "@adatechnology/game-contracts";
54
+
55
+ // src/contracts.ts
56
+ import { checkSpec } from "@adatechnology/game-contracts";
57
+
52
58
  // src/data/ammo-boxes.json
53
59
  var ammo_boxes_default = [
54
60
  {
@@ -2143,7 +2149,11 @@ var items_default = [
2143
2149
  "road"
2144
2150
  ]
2145
2151
  },
2146
- model: "canned_sardines"
2152
+ model: "canned_sardines",
2153
+ seal: {
2154
+ byHand: true,
2155
+ spoilsWhenBreached: true
2156
+ }
2147
2157
  },
2148
2158
  {
2149
2159
  id: "canned_corn_and_peas",
@@ -2325,7 +2335,13 @@ var items_default = [
2325
2335
  "school"
2326
2336
  ]
2327
2337
  },
2328
- model: "rice"
2338
+ model: "rice",
2339
+ powder: {
2340
+ color: 15722710
2341
+ },
2342
+ estimated: [
2343
+ "powder"
2344
+ ]
2329
2345
  },
2330
2346
  {
2331
2347
  id: "beans",
@@ -2663,7 +2679,10 @@ var items_default = [
2663
2679
  nonPerishable: true,
2664
2680
  estimated: [
2665
2681
  "nonPerishable"
2666
- ]
2682
+ ],
2683
+ seal: {
2684
+ byHand: true
2685
+ }
2667
2686
  },
2668
2687
  {
2669
2688
  id: "bread",
@@ -3205,7 +3224,10 @@ var items_default = [
3205
3224
  "lodge"
3206
3225
  ]
3207
3226
  },
3208
- model: "home_preserves"
3227
+ model: "home_preserves",
3228
+ seal: {
3229
+ byHand: true
3230
+ }
3209
3231
  },
3210
3232
  {
3211
3233
  id: "dog_food",
@@ -3278,8 +3300,12 @@ var items_default = [
3278
3300
  },
3279
3301
  model: "sugar",
3280
3302
  nonPerishable: true,
3303
+ powder: {
3304
+ color: 16184558
3305
+ },
3281
3306
  estimated: [
3282
- "nonPerishable"
3307
+ "nonPerishable",
3308
+ "powder"
3283
3309
  ]
3284
3310
  },
3285
3311
  {
@@ -3317,8 +3343,12 @@ var items_default = [
3317
3343
  },
3318
3344
  model: "salt",
3319
3345
  nonPerishable: true,
3346
+ powder: {
3347
+ color: 15921902
3348
+ },
3320
3349
  estimated: [
3321
- "nonPerishable"
3350
+ "nonPerishable",
3351
+ "powder"
3322
3352
  ]
3323
3353
  },
3324
3354
  {
@@ -3355,7 +3385,10 @@ var items_default = [
3355
3385
  "supermarket"
3356
3386
  ]
3357
3387
  },
3358
- model: "cooking_oil"
3388
+ model: "cooking_oil",
3389
+ seal: {
3390
+ byHand: true
3391
+ }
3359
3392
  },
3360
3393
  {
3361
3394
  id: "coffee",
@@ -3393,7 +3426,13 @@ var items_default = [
3393
3426
  "police_station"
3394
3427
  ]
3395
3428
  },
3396
- model: "coffee"
3429
+ model: "coffee",
3430
+ powder: {
3431
+ color: 4861984
3432
+ },
3433
+ estimated: [
3434
+ "powder"
3435
+ ]
3397
3436
  },
3398
3437
  {
3399
3438
  id: "water_bottle",
@@ -3437,7 +3476,10 @@ var items_default = [
3437
3476
  estimated: [
3438
3477
  "nonPerishable",
3439
3478
  "fluid"
3440
- ]
3479
+ ],
3480
+ seal: {
3481
+ byHand: true
3482
+ }
3441
3483
  },
3442
3484
  {
3443
3485
  id: "water_jug_5l",
@@ -3477,7 +3519,10 @@ var items_default = [
3477
3519
  estimated: [
3478
3520
  "nonPerishable",
3479
3521
  "fluid"
3480
- ]
3522
+ ],
3523
+ seal: {
3524
+ byHand: true
3525
+ }
3481
3526
  },
3482
3527
  {
3483
3528
  id: "dirty_water",
@@ -3514,7 +3559,10 @@ var items_default = [
3514
3559
  estimated: [
3515
3560
  "nonPerishable",
3516
3561
  "fluid"
3517
- ]
3562
+ ],
3563
+ seal: {
3564
+ byHand: true
3565
+ }
3518
3566
  },
3519
3567
  {
3520
3568
  id: "soda_can",
@@ -3722,7 +3770,10 @@ var items_default = [
3722
3770
  },
3723
3771
  estimated: [
3724
3772
  "fluid"
3725
- ]
3773
+ ],
3774
+ seal: {
3775
+ byHand: true
3776
+ }
3726
3777
  },
3727
3778
  {
3728
3779
  id: "energy_drink",
@@ -3763,7 +3814,11 @@ var items_default = [
3763
3814
  },
3764
3815
  estimated: [
3765
3816
  "fluid"
3766
- ]
3817
+ ],
3818
+ seal: {
3819
+ byHand: true,
3820
+ spoilsWhenBreached: true
3821
+ }
3767
3822
  },
3768
3823
  {
3769
3824
  id: "coconut_water",
@@ -3803,7 +3858,10 @@ var items_default = [
3803
3858
  },
3804
3859
  estimated: [
3805
3860
  "fluid"
3806
- ]
3861
+ ],
3862
+ seal: {
3863
+ byHand: true
3864
+ }
3807
3865
  },
3808
3866
  {
3809
3867
  id: "beer_can",
@@ -3903,7 +3961,10 @@ var items_default = [
3903
3961
  "parts",
3904
3962
  "impact",
3905
3963
  "leftover"
3906
- ]
3964
+ ],
3965
+ seal: {
3966
+ byHand: true
3967
+ }
3907
3968
  },
3908
3969
  {
3909
3970
  id: "communion_wine",
@@ -3959,7 +4020,10 @@ var items_default = [
3959
4020
  "parts",
3960
4021
  "impact",
3961
4022
  "leftover"
3962
- ]
4023
+ ],
4024
+ seal: {
4025
+ byHand: true
4026
+ }
3963
4027
  },
3964
4028
  {
3965
4029
  id: "bandage",
@@ -10505,9 +10569,13 @@ var items_default = [
10505
10569
  hydration: -8
10506
10570
  },
10507
10571
  shelfLife: 365,
10572
+ powder: {
10573
+ color: 15986662
10574
+ },
10508
10575
  estimated: [
10509
10576
  "nutrition",
10510
- "shelfLife"
10577
+ "shelfLife",
10578
+ "powder"
10511
10579
  ]
10512
10580
  },
10513
10581
  {
@@ -10532,9 +10600,13 @@ var items_default = [
10532
10600
  hydration: -5
10533
10601
  },
10534
10602
  shelfLife: 270,
10603
+ powder: {
10604
+ color: 14201962
10605
+ },
10535
10606
  estimated: [
10536
10607
  "nutrition",
10537
- "shelfLife"
10608
+ "shelfLife",
10609
+ "powder"
10538
10610
  ]
10539
10611
  },
10540
10612
  {
@@ -12733,6 +12805,15 @@ var kinds_default = [
12733
12805
  "chew"
12734
12806
  ]
12735
12807
  },
12808
+ seal: {
12809
+ openWith: [
12810
+ "can_opener",
12811
+ "pocket_knife",
12812
+ "knife",
12813
+ "tactical_knife"
12814
+ ],
12815
+ spoilsWhenBreached: true
12816
+ },
12736
12817
  leftover: {
12737
12818
  item: "empty_can",
12738
12819
  despawnSeconds: 600
@@ -12894,6 +12975,10 @@ var kinds_default = [
12894
12975
  "gulp"
12895
12976
  ]
12896
12977
  },
12978
+ seal: {
12979
+ byHand: true,
12980
+ spoilsWhenBreached: true
12981
+ },
12897
12982
  leftover: {
12898
12983
  item: "empty_can",
12899
12984
  despawnSeconds: 600
@@ -17407,202 +17492,455 @@ var GOODS_PROVENANCE = provenance_default;
17407
17492
  var GOODS_KINDS = kinds_default;
17408
17493
  var GOODS_KNOWN_GAPS = known_gaps_default;
17409
17494
 
17410
- // src/pack.ts
17411
- var goodsPack = {
17412
- id: "goods",
17413
- items: GOODS_ITEMS,
17414
- categories: GOODS_CATEGORIES,
17415
- places: GOODS_PLACES,
17416
- containers: GOODS_CONTAINERS,
17417
- useEffects: GOODS_USE_EFFECTS,
17418
- recipes: GOODS_RECIPES,
17419
- pools: GOODS_HOUSE_POOLS.pools,
17420
- poolAliases: { ...GOODS_HOUSE_POOLS.aliasOf, ...FLOORPLAN_POOL_ALIASES }
17495
+ // src/data/item-contracts.json
17496
+ var item_contracts_default = {
17497
+ densityKgPerLitre: { min: 0.02, max: 22 },
17498
+ impactResponses: ["dent", "break", "shatter", "tear", "squash", "pierce", "none"],
17499
+ heatResistantMaterials: ["steel", "cast_iron", "aluminium", "copper", "ceramic", "tempered_glass", "stone", "brick"],
17500
+ bodySlots: ["head", "eyes", "torso", "arms", "hands", "legs", "feet", "over"]
17421
17501
  };
17422
- var createGoodsItems = (options = {}) => createItems({ ...options, packs: [goodsPack, ...options.packs ?? []] });
17423
17502
 
17424
- // src/data/locales/en.json
17425
- var en_default = {
17426
- "item.canned_beans": "Canned beans",
17427
- "item.canned_sardines": "Canned sardines",
17428
- "item.canned_corn_and_peas": "Canned corn and peas",
17429
- "item.canned_sausages": "Canned sausages",
17430
- "item.condensed_milk": "Condensed milk",
17431
- "item.military_ration": "Army field ration",
17432
- "item.rice": "Rice (1 kg)",
17433
- "item.beans": "Beans (1 kg)",
17434
- "item.pasta": "Pasta",
17435
- "item.instant_noodles": "Instant noodles",
17436
- "item.cassava_flour": "Cassava flour (1 kg)",
17437
- "item.biscuits": "Biscuits",
17438
- "item.snack_chips": "Bagged snack chips",
17439
- "item.chocolate_bar": "Chocolate bar",
17440
- "item.rapadura": "Rapadura (raw cane sugar block)",
17441
- "item.honey_jar": "Jar of honey",
17442
- "item.bread": "Bread",
17443
- "item.cheese": "Cheese",
17444
- "item.mortadella": "Mortadella (whole piece)",
17445
- "item.raw_beef": "Raw beef (1 kg)",
17446
- "item.frozen_chicken": "Frozen chicken",
17447
- "item.eggs": "Eggs (dozen)",
17448
- "item.banana_bunch": "Bunch of bananas",
17449
- "item.orange": "Orange",
17450
- "item.potatoes": "Potatoes (1 kg)",
17451
- "item.cassava_root": "Cassava root",
17452
- "item.fresh_fish": "Fresh fish",
17453
- "item.salted_fish": "Salted fish",
17454
- "item.jerked_beef": "Jerked beef",
17455
- "item.game_meat": "Game meat (capybara, wild boar)",
17456
- "item.home_preserves": "Home preserves (jar)",
17457
- "item.dog_food": "Dog food (1 kg)",
17458
- "item.sugar": "Sugar",
17459
- "item.salt": "Salt",
17460
- "item.cooking_oil": "Cooking oil",
17461
- "item.coffee": "Coffee",
17462
- "item.water_bottle": "Water bottle",
17463
- "item.water_jug_5l": "Water jug (5 L)",
17464
- "item.dirty_water": "River or puddle water",
17465
- "item.soda_can": "Soda can",
17466
- "item.juice_box": "Juice box",
17467
- "item.milk_carton": "Milk carton",
17468
- "item.sports_drink": "Sports drink",
17469
- "item.energy_drink": "Energy drink (can)",
17470
- "item.coconut_water": "Coconut water",
17471
- "item.beer_can": "Beer (can)",
17472
- "item.cachaca": "Cachaca (bottle)",
17473
- "item.communion_wine": "Communion wine",
17474
- "item.bandage": "Bandage",
17475
- "item.sterile_gauze": "Sterile gauze",
17476
- "item.adhesive_bandages": "Adhesive bandages (box)",
17477
- "item.medical_tape": "Medical tape",
17478
- "item.alcohol_70": "Alcohol 70%",
17479
- "item.povidone_iodine": "Povidone-iodine",
17480
- "item.saline_iv_bag": "Saline bag with IV set",
17481
- "item.painkillers": "Painkillers",
17482
- "item.ibuprofen": "Anti-inflammatory (ibuprofen)",
17483
- "item.tramadol": "Strong painkiller (tramadol)",
17484
- "item.clonazepam": "Sedative (clonazepam)",
17485
- "item.amoxicillin": "Antibiotic (amoxicillin)",
17486
- "item.ceftriaxone": "Injectable antibiotic (ceftriaxone)",
17487
- "item.syringe": "Disposable syringe",
17488
- "item.suture_kit": "Suture kit",
17489
- "item.splint": "Immobilizing splint",
17490
- "item.first_aid_kit": "First aid kit",
17491
- "item.exam_gloves": "Exam gloves",
17492
- "item.n95_mask": "N95 mask",
17493
- "item.activated_charcoal": "Activated charcoal",
17494
- "item.rehydration_salts": "Rehydration salts",
17495
- "item.antihistamine": "Antihistamine",
17496
- "item.antivenom": "Snake antivenom",
17497
- "item.thermometer": "Thermometer",
17498
- "item.maintenance_medication": "Daily-use medication",
17499
- "item.multivitamin": "Multivitamin",
17500
- "item.hammer": "Hammer",
17501
- "item.screwdriver": "Screwdriver",
17502
- "item.crowbar": "Crowbar",
17503
- "item.handsaw": "Handsaw",
17504
- "item.hacksaw": "Hacksaw",
17505
- "item.bolt_cutters": "Bolt cutters",
17506
- "item.pliers": "Pliers",
17507
- "item.adjustable_wrench": "Adjustable wrench",
17508
- "item.mechanic_toolbox": "Mechanic's toolbox",
17509
- "item.car_jack": "Car jack and wheel wrench",
17510
- "item.gas_torch": "Gas torch",
17511
- "item.inverter_welder": "Inverter welder",
17512
- "item.cordless_drill": "Cordless drill",
17513
- "item.shovel": "Shovel",
17514
- "item.hoe": "Hoe",
17515
- "item.pocket_knife": "Multi-tool pocket knife",
17516
- "item.can_opener": "Can opener",
17517
- "item.scissors": "Scissors",
17518
- "item.duct_tape": "Duct tape",
17519
- "item.pot": "Pot",
17520
- "item.camp_stove": "Camping stove",
17521
- "item.sewing_kit": "Sewing kit",
17522
- "item.lockpick_kit": "Locksmith kit (lock picks)",
17523
- "item.lighter": "Lighter",
17524
- "item.matches": "Matches",
17525
- "item.flint_striker": "Flint and striker",
17526
- "item.bucket": "Bucket",
17527
- "item.binoculars": "Binoculars",
17528
- "item.fire_extinguisher": "Dry-powder fire extinguisher",
17529
- "item.sharpening_stone": "Sharpening stone",
17530
- "item.knife": "Kitchen knife",
17531
- "item.tactical_knife": "Tactical knife",
17532
- "item.machete": "Machete",
17533
- "item.meat_cleaver": "Butcher's cleaver",
17534
- "item.hatchet": "Hatchet",
17535
- "item.axe": "Axe",
17536
- "item.baseball_bat": "Baseball bat",
17537
- "item.nail_bat": "Nail bat",
17538
- "item.iron_pipe": "Iron pipe",
17539
- "item.sledgehammer": "Sledgehammer",
17540
- "item.pickaxe": "Pickaxe",
17541
- "item.sickle": "Sickle",
17542
- "item.police_baton": "Police baton",
17543
- "item.cast_iron_skillet": "Cast iron skillet",
17544
- "item.improvised_spear": "Improvised spear",
17545
- "item.chainsaw": "Chainsaw",
17546
- "item.slingshot": "Slingshot",
17547
- "item.recurve_bow": "Recurve bow",
17548
- "item.crossbow": "Crossbow",
17549
- "item.arrows_and_bolts": "Arrows and bolts (6)",
17550
- "item.wooden_plank": "Wooden plank",
17551
- "item.nails": "Nails (box of 100)",
17552
- "item.screws": "Screws (box of 50)",
17553
- "item.sheet_metal": "Sheet metal",
17554
- "item.scrap_metal": "Scrap metal",
17555
- "item.wire_roll": "Wire (10 m roll)",
17556
- "item.barbed_wire": "Barbed wire (25 m roll)",
17557
- "item.rope": "Rope",
17558
- "item.tarp": "Tarp (4 x 4 m)",
17559
- "item.raffia_sack": "Raffia sack",
17560
- "item.bricks": "Bricks (10)",
17561
- "item.cement_bag": "Cement (25 kg bag)",
17562
- "item.rag": "Rag",
17563
- "item.glass_bottle": "Empty glass bottle",
17564
- "item.pet_bottle": "Empty PET bottle",
17565
- "item.empty_can": "Empty can",
17566
- "item.glass_shards": "Glass shards",
17567
- "item.epoxy_glue": "Epoxy glue",
17568
- "item.electronic_components": "Electronic components",
17569
- "item.copper_wire": "Copper wire (5 m)",
17570
- "item.firewood": "Firewood",
17571
- "item.rebar": "Rebar",
17572
- "item.lubricant_spray": "Spray lubricant oil",
17573
- "item.tshirt": "T-shirt",
17574
- "item.hoodie": "Hoodie",
17575
- "item.denim_jacket": "Denim jacket",
17576
- "item.leather_motorcycle_jacket": "Motorcycle jacket (leather)",
17577
- "item.jeans": "Jeans",
17578
- "item.leather_motorcycle_pants": "Motorcycle pants",
17579
- "item.army_camouflage_uniform": "Army camouflage uniform",
17580
- "item.mechanic_coveralls": "Mechanic's coveralls",
17581
- "item.firefighter_suit": "Firefighter suit",
17582
- "item.beekeeper_suit": "Beekeeper suit",
17583
- "item.raincoat": "Raincoat",
17584
- "item.padded_coat": "Padded coat",
17585
- "item.motorcycle_helmet": "Full-face motorcycle helmet",
17586
- "item.hard_hat": "Hard hat (PPE)",
17587
- "item.safety_goggles": "Safety goggles",
17588
- "item.leather_work_gloves": "Leather work gloves",
17589
- "item.combat_boots": "Combat boots",
17590
- "item.rubber_boots": "Rubber boots",
17591
- "item.sneakers": "Sneakers",
17592
- "item.flip_flops": "Flip-flops",
17593
- "item.magazine_arm_guard": "Magazine arm guard",
17594
- "item.sheet_metal_forearm_guard": "Sheet metal forearm guard",
17595
- "item.ballistic_vest": "Ballistic vests (II-A, III-A, III)",
17596
- "item.shopping_bag": "Shopping bag",
17597
- "item.trash_bag": "Trash bag",
17598
- "item.fanny_pack": "Fanny pack",
17599
- "item.crossbody_bag": "Crossbody bag",
17600
- "item.backpack": "Backpack",
17601
- "item.delivery_bag": "Delivery bag",
17602
- "item.hiking_backpack_40l": "Hiking backpack 40 L",
17603
- "item.military_backpack": "Military backpack",
17604
- "item.trekking_backpack_70l": "Trekking backpack 70 L",
17605
- "item.rolling_suitcase": "Rolling suitcase",
17503
+ // src/contracts.ts
17504
+ var IMPACT_RESPONSES = item_contracts_default.impactResponses;
17505
+ var HEAT_RESISTANT_MATERIALS = item_contracts_default.heatResistantMaterials;
17506
+ var DENSITY_KG_PER_LITRE = item_contracts_default.densityKgPerLitre;
17507
+ var BODY_SLOTS = item_contracts_default.bodySlots;
17508
+ var SNAKE_CASE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
17509
+ var LITRES_PER_CUBIC_METRE = 1e3;
17510
+ var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
17511
+ var isFraction = (value) => isNumber(value) && value >= 0 && value <= 1;
17512
+ var catalogOf = (options) => options.items ?? GOODS_ITEMS;
17513
+ var parts = (item) => Array.isArray(item.parts) ? item.parts : [];
17514
+ var physical = (options) => {
17515
+ const range = options.density ?? DENSITY_KG_PER_LITRE;
17516
+ return {
17517
+ id: "physical",
17518
+ check: (item) => {
17519
+ const problems = [];
17520
+ const size = item.size;
17521
+ const sizeOk = Array.isArray(size) && size.length === 3 && size.every((side) => isNumber(side) && side > 0);
17522
+ if (!sizeOk) problems.push("size must be 3 finite positive numbers in metres");
17523
+ if (!isNumber(item.weight) || item.weight < 0) problems.push("weight must be a finite number >= 0 (kg)");
17524
+ if (sizeOk && isNumber(item.weight) && item.weight > 0) {
17525
+ const [width, height, depth] = size;
17526
+ const density = item.weight / (width * height * depth * LITRES_PER_CUBIC_METRE);
17527
+ if (density < range.min || density > range.max) problems.push(`density ${Number(density.toPrecision(2))} kg/L is outside ${range.min}..${range.max}`);
17528
+ }
17529
+ return problems;
17530
+ }
17531
+ };
17532
+ };
17533
+ var destructible = (options) => {
17534
+ const materials = options.materials ? new Set(options.materials) : void 0;
17535
+ return {
17536
+ id: "destructible",
17537
+ check: (item) => {
17538
+ const list = parts(item);
17539
+ if (list.length === 0) return ["missing parts"];
17540
+ const problems = [];
17541
+ const seen = /* @__PURE__ */ new Set();
17542
+ for (const part of list) {
17543
+ if (typeof part.id !== "string" || part.id === "") problems.push("a part has no id");
17544
+ else if (seen.has(part.id)) problems.push(`duplicate part "${part.id}"`);
17545
+ else seen.add(part.id);
17546
+ if (typeof part.material !== "string" || part.material === "") problems.push(`part "${part.id}" has no material`);
17547
+ else if (materials && !materials.has(part.material)) problems.push(`part "${part.id}" has unknown material "${part.material}"`);
17548
+ if (part.thickness !== void 0 && !(isNumber(part.thickness) && part.thickness > 0)) problems.push(`part "${part.id}" thickness must be a positive number`);
17549
+ }
17550
+ if (problems.length === 0 && list.every((part) => part.shape !== void 0 && part.position !== void 0)) {
17551
+ const report = checkSpec({ parts: list }, materials ? { materials } : {});
17552
+ for (const problem of report.problems) problems.push(`spec: ${problem}`);
17553
+ }
17554
+ return problems;
17555
+ }
17556
+ };
17557
+ };
17558
+ var impact = {
17559
+ id: "impact",
17560
+ check: (item) => {
17561
+ const value = item.impact;
17562
+ if (!value) return ["missing impact"];
17563
+ const problems = [];
17564
+ if (!IMPACT_RESPONSES.includes(value.response ?? "")) problems.push(`impact.response must be one of ${IMPACT_RESPONSES.join(", ")}`);
17565
+ if (typeof value.pushImpulse !== "boolean") problems.push("impact.pushImpulse must be true or false");
17566
+ if (value.shed !== void 0 && typeof value.shed !== "boolean") problems.push("impact.shed must be true or false");
17567
+ return problems;
17568
+ }
17569
+ };
17570
+ var useAnimation = {
17571
+ id: "use_animation",
17572
+ check: (item) => {
17573
+ const problems = [];
17574
+ const clip = item.use?.clip;
17575
+ if (clip === void 0) problems.push("missing use.clip");
17576
+ else if (!SNAKE_CASE.test(clip)) problems.push(`use.clip "${clip}" must be snake_case`);
17577
+ const sounds = item.use?.sounds;
17578
+ if (!Array.isArray(sounds) || sounds.length === 0 || sounds.some((sound) => typeof sound !== "string" || sound === "")) problems.push("missing use.sounds (a non-empty list of sound ids)");
17579
+ return problems;
17580
+ }
17581
+ };
17582
+ var leftover = (options) => {
17583
+ const ids = new Set(catalogOf(options).map((item) => item.id));
17584
+ return {
17585
+ id: "leftover",
17586
+ check: (item) => {
17587
+ const value = item.leftover;
17588
+ if (value === void 0) return ["missing leftover"];
17589
+ if (value === "none") return [];
17590
+ const problems = [];
17591
+ if (typeof value !== "object" || value === null) return ['leftover must be an object or "none"'];
17592
+ if (typeof value.item !== "string" || value.item === "") problems.push("missing leftover.item");
17593
+ else if (!ids.has(value.item)) problems.push(`leftover.item "${value.item}" is not in the catalog`);
17594
+ if (!isNumber(value.despawnSeconds) || value.despawnSeconds < 0) problems.push("missing leftover.despawnSeconds");
17595
+ return problems;
17596
+ }
17597
+ };
17598
+ };
17599
+ var edible = {
17600
+ id: "edible",
17601
+ check: (item) => {
17602
+ const problems = [];
17603
+ const nutrition = item.nutrition;
17604
+ if (!nutrition || !(isNumber(nutrition.satiety) || isNumber(nutrition.hydration))) problems.push("missing nutrition");
17605
+ if (item.shelfLife === void 0 && item.nonPerishable !== true) problems.push("missing shelfLife (a number of days, or nonPerishable: true)");
17606
+ else if (item.shelfLife !== void 0 && !(isNumber(item.shelfLife) && item.shelfLife > 0)) problems.push("shelfLife must be a positive number of days");
17607
+ if (!isFraction(item.rawEffect?.sickChance)) problems.push("missing rawEffect.sickChance (0..1)");
17608
+ return problems;
17609
+ }
17610
+ };
17611
+ var inputsOf = (recipe) => recipe.inputs ?? [];
17612
+ var toolsOf = (recipe) => recipe.tools ?? [];
17613
+ var consumes = (input, item) => {
17614
+ if (input.item === item.id) return true;
17615
+ if (input.any?.includes(item.id)) return true;
17616
+ if (!input.match) return false;
17617
+ const entries = Object.entries(input.match);
17618
+ return entries.length > 0 && entries.every(([key, value]) => item[key] === value);
17619
+ };
17620
+ var recipeInput = (options) => {
17621
+ const recipes = options.recipes ?? GOODS_RECIPES;
17622
+ return {
17623
+ id: "recipe_input",
17624
+ check: (item) => recipes.some((recipe) => inputsOf(recipe).some((input) => consumes(input, item))) ? [] : ["is not an input of any recipe"]
17625
+ };
17626
+ };
17627
+ var preparation = (options) => {
17628
+ const recipes = options.recipes ?? GOODS_RECIPES;
17629
+ const cookware = new Set(catalogOf(options).filter((item) => item.kind === "cookware").map((item) => item.id));
17630
+ return {
17631
+ id: "preparation",
17632
+ check: (item) => {
17633
+ const usable = recipes.filter((recipe) => inputsOf(recipe).some((input) => consumes(input, item)));
17634
+ if (usable.length === 0) return ["has no recipe that prepares it"];
17635
+ const prepares = usable.some((recipe) => {
17636
+ const heat = toolsOf(recipe).some((tool) => tool.world?.includes("fire"));
17637
+ const vessel = [...inputsOf(recipe).flatMap((input) => input.item ? [input.item] : []), ...toolsOf(recipe).flatMap((tool) => tool.anyOf ?? [])].some((id) => cookware.has(id));
17638
+ return heat && vessel;
17639
+ });
17640
+ return prepares ? [] : ["has no recipe that needs fire and a cooking vessel"];
17641
+ }
17642
+ };
17643
+ };
17644
+ var liquidContainer = {
17645
+ id: "liquid_container",
17646
+ check: (item) => {
17647
+ const fluid = item.fluid;
17648
+ if (fluid) {
17649
+ const problems = [];
17650
+ if (typeof fluid.liquid !== "string" || !SNAKE_CASE.test(fluid.liquid)) problems.push("fluid.liquid must be a liquid id");
17651
+ if (!isNumber(fluid.liters) || fluid.liters < 0) problems.push("fluid.liters must be a number >= 0");
17652
+ return problems;
17653
+ }
17654
+ return isNumber(item.container?.capacityLiters) && (item.container?.capacityLiters ?? 0) > 0 ? [] : ["missing fluid { liquid, liters } or container.capacityLiters"];
17655
+ }
17656
+ };
17657
+ var heatResistant = (options) => {
17658
+ const allowed = new Set(options.heatResistant ?? HEAT_RESISTANT_MATERIALS);
17659
+ return {
17660
+ id: "heat_resistant",
17661
+ check: (item) => parts(item).filter((part) => !allowed.has(part.material)).map((part) => `part "${part.id}" is ${part.material}, which does not take a flame`)
17662
+ };
17663
+ };
17664
+ var weapon = (options) => {
17665
+ const weaponIds = options.weaponIds ? new Set(options.weaponIds) : void 0;
17666
+ const calibers = options.calibers ? new Set(options.calibers) : void 0;
17667
+ return {
17668
+ id: "weapon",
17669
+ check: (item, ctx) => {
17670
+ if (ctx.chain.includes("ammo")) {
17671
+ if (typeof item.caliber !== "string" || item.caliber === "") return ["missing caliber"];
17672
+ return calibers && !calibers.has(item.caliber) ? [`caliber "${item.caliber}" is not in the arsenal`] : [];
17673
+ }
17674
+ if (typeof item.weapon !== "string" || item.weapon === "") return ["missing weapon (the arsenal id)"];
17675
+ return weaponIds && !weaponIds.has(item.weapon) ? [`weapon "${item.weapon}" is not in the arsenal`] : [];
17676
+ }
17677
+ };
17678
+ };
17679
+ var wearable = {
17680
+ id: "wearable",
17681
+ check: (item, ctx) => {
17682
+ const problems = [];
17683
+ const { protection, carry } = item;
17684
+ if (!protection && !carry) return [ctx.chain.includes("bag") ? "missing carry" : "missing protection"];
17685
+ if (ctx.chain.includes("bag") && !carry) problems.push("missing carry");
17686
+ if (ctx.chain.includes("clothing") && !protection) problems.push("missing protection");
17687
+ if (protection) {
17688
+ const slots = protection.slots;
17689
+ if (!Array.isArray(slots) || slots.length === 0) problems.push("protection.slots must list at least one body slot");
17690
+ else for (const slot of slots) if (!BODY_SLOTS.includes(slot)) problems.push(`protection.slots has unknown slot "${slot}"`);
17691
+ for (const field of ["bite", "scratch", "warmth"]) if (protection[field] !== void 0 && !isFraction(protection[field])) problems.push(`protection.${field} must be between 0 and 1`);
17692
+ }
17693
+ if (carry) {
17694
+ if (!(isNumber(carry.capacityKg) && carry.capacityKg > 0)) problems.push("carry.capacityKg must be positive");
17695
+ if (!isFraction(carry.weightReduction)) problems.push("carry.weightReduction must be between 0 and 1");
17696
+ if (!(isNumber(carry.quickSlots) && carry.quickSlots >= 0)) problems.push("carry.quickSlots must be >= 0");
17697
+ }
17698
+ return problems;
17699
+ }
17700
+ };
17701
+ var battery = {
17702
+ id: "battery",
17703
+ check: (item) => isNumber(item.charge?.lossPerMonth) && (item.charge?.lossPerMonth ?? -1) >= 0 ? [] : ["missing charge.lossPerMonth"]
17704
+ };
17705
+ var createItemContracts = (options = {}) => [
17706
+ physical(options),
17707
+ destructible(options),
17708
+ impact,
17709
+ useAnimation,
17710
+ leftover(options),
17711
+ edible,
17712
+ preparation(options),
17713
+ recipeInput(options),
17714
+ liquidContainer,
17715
+ heatResistant(options),
17716
+ weapon(options),
17717
+ wearable,
17718
+ battery
17719
+ ];
17720
+ var ITEM_CONTRACTS = createItemContracts();
17721
+
17722
+ // src/kinds.ts
17723
+ var createGoodsKinds = (options = {}) => defineCategories(GOODS_KINDS, options);
17724
+ var lootCategoriesOf = (kinds, kind) => {
17725
+ const value = kinds.defaults(kind).lootCategory;
17726
+ if (value === void 0) return void 0;
17727
+ return Array.isArray(value) ? value : [String(value)];
17728
+ };
17729
+ var checkGoodsItems = (options = {}) => {
17730
+ const kinds = options.kinds ?? createGoodsKinds();
17731
+ const entries = options.entries ?? GOODS_ITEMS;
17732
+ const report = checkCategorized(entries, kinds, createItemContracts({ items: entries, ...options }), { allow: options.allow ?? GOODS_KNOWN_GAPS });
17733
+ const problems = [...report.problems];
17734
+ for (const entry of entries) {
17735
+ if (!kinds.has(entry.kind) || kinds.get(entry.kind)?.abstract) continue;
17736
+ const declared = lootCategoriesOf(kinds, entry.kind);
17737
+ if (declared && !declared.includes(entry.category)) problems.push(`"${entry.id}" (${entry.kind}): category "${entry.category}" is not one of ${declared.join(", ")}, required by the kind's lootCategory`);
17738
+ }
17739
+ return { ok: problems.length === 0, problems, allowed: report.allowed };
17740
+ };
17741
+
17742
+ // src/pack.ts
17743
+ var KINDS = createGoodsKinds();
17744
+ var withSeal = (item) => {
17745
+ const kind = item.kind;
17746
+ const seal = kind && KINDS.has(kind) ? KINDS.defaults(kind).seal : void 0;
17747
+ return seal ? { ...item, seal: { ...seal, ...item.seal } } : item;
17748
+ };
17749
+ var goodsPack = {
17750
+ id: "goods",
17751
+ items: GOODS_ITEMS.map(withSeal),
17752
+ categories: GOODS_CATEGORIES,
17753
+ places: GOODS_PLACES,
17754
+ containers: GOODS_CONTAINERS,
17755
+ useEffects: GOODS_USE_EFFECTS,
17756
+ recipes: GOODS_RECIPES,
17757
+ pools: GOODS_HOUSE_POOLS.pools,
17758
+ poolAliases: { ...GOODS_HOUSE_POOLS.aliasOf, ...FLOORPLAN_POOL_ALIASES }
17759
+ };
17760
+ var createGoodsItems = (options = {}) => createItems({ ...options, packs: [goodsPack, ...options.packs ?? []] });
17761
+
17762
+ // src/data/locales/en.json
17763
+ var en_default = {
17764
+ "item.canned_beans": "Canned beans",
17765
+ "item.canned_sardines": "Canned sardines",
17766
+ "item.canned_corn_and_peas": "Canned corn and peas",
17767
+ "item.canned_sausages": "Canned sausages",
17768
+ "item.condensed_milk": "Condensed milk",
17769
+ "item.military_ration": "Army field ration",
17770
+ "item.rice": "Rice (1 kg)",
17771
+ "item.beans": "Beans (1 kg)",
17772
+ "item.pasta": "Pasta",
17773
+ "item.instant_noodles": "Instant noodles",
17774
+ "item.cassava_flour": "Cassava flour (1 kg)",
17775
+ "item.biscuits": "Biscuits",
17776
+ "item.snack_chips": "Bagged snack chips",
17777
+ "item.chocolate_bar": "Chocolate bar",
17778
+ "item.rapadura": "Rapadura (raw cane sugar block)",
17779
+ "item.honey_jar": "Jar of honey",
17780
+ "item.bread": "Bread",
17781
+ "item.cheese": "Cheese",
17782
+ "item.mortadella": "Mortadella (whole piece)",
17783
+ "item.raw_beef": "Raw beef (1 kg)",
17784
+ "item.frozen_chicken": "Frozen chicken",
17785
+ "item.eggs": "Eggs (dozen)",
17786
+ "item.banana_bunch": "Bunch of bananas",
17787
+ "item.orange": "Orange",
17788
+ "item.potatoes": "Potatoes (1 kg)",
17789
+ "item.cassava_root": "Cassava root",
17790
+ "item.fresh_fish": "Fresh fish",
17791
+ "item.salted_fish": "Salted fish",
17792
+ "item.jerked_beef": "Jerked beef",
17793
+ "item.game_meat": "Game meat (capybara, wild boar)",
17794
+ "item.home_preserves": "Home preserves (jar)",
17795
+ "item.dog_food": "Dog food (1 kg)",
17796
+ "item.sugar": "Sugar",
17797
+ "item.salt": "Salt",
17798
+ "item.cooking_oil": "Cooking oil",
17799
+ "item.coffee": "Coffee",
17800
+ "item.water_bottle": "Water bottle",
17801
+ "item.water_jug_5l": "Water jug (5 L)",
17802
+ "item.dirty_water": "River or puddle water",
17803
+ "item.soda_can": "Soda can",
17804
+ "item.juice_box": "Juice box",
17805
+ "item.milk_carton": "Milk carton",
17806
+ "item.sports_drink": "Sports drink",
17807
+ "item.energy_drink": "Energy drink (can)",
17808
+ "item.coconut_water": "Coconut water",
17809
+ "item.beer_can": "Beer (can)",
17810
+ "item.cachaca": "Cachaca (bottle)",
17811
+ "item.communion_wine": "Communion wine",
17812
+ "item.bandage": "Bandage",
17813
+ "item.sterile_gauze": "Sterile gauze",
17814
+ "item.adhesive_bandages": "Adhesive bandages (box)",
17815
+ "item.medical_tape": "Medical tape",
17816
+ "item.alcohol_70": "Alcohol 70%",
17817
+ "item.povidone_iodine": "Povidone-iodine",
17818
+ "item.saline_iv_bag": "Saline bag with IV set",
17819
+ "item.painkillers": "Painkillers",
17820
+ "item.ibuprofen": "Anti-inflammatory (ibuprofen)",
17821
+ "item.tramadol": "Strong painkiller (tramadol)",
17822
+ "item.clonazepam": "Sedative (clonazepam)",
17823
+ "item.amoxicillin": "Antibiotic (amoxicillin)",
17824
+ "item.ceftriaxone": "Injectable antibiotic (ceftriaxone)",
17825
+ "item.syringe": "Disposable syringe",
17826
+ "item.suture_kit": "Suture kit",
17827
+ "item.splint": "Immobilizing splint",
17828
+ "item.first_aid_kit": "First aid kit",
17829
+ "item.exam_gloves": "Exam gloves",
17830
+ "item.n95_mask": "N95 mask",
17831
+ "item.activated_charcoal": "Activated charcoal",
17832
+ "item.rehydration_salts": "Rehydration salts",
17833
+ "item.antihistamine": "Antihistamine",
17834
+ "item.antivenom": "Snake antivenom",
17835
+ "item.thermometer": "Thermometer",
17836
+ "item.maintenance_medication": "Daily-use medication",
17837
+ "item.multivitamin": "Multivitamin",
17838
+ "item.hammer": "Hammer",
17839
+ "item.screwdriver": "Screwdriver",
17840
+ "item.crowbar": "Crowbar",
17841
+ "item.handsaw": "Handsaw",
17842
+ "item.hacksaw": "Hacksaw",
17843
+ "item.bolt_cutters": "Bolt cutters",
17844
+ "item.pliers": "Pliers",
17845
+ "item.adjustable_wrench": "Adjustable wrench",
17846
+ "item.mechanic_toolbox": "Mechanic's toolbox",
17847
+ "item.car_jack": "Car jack and wheel wrench",
17848
+ "item.gas_torch": "Gas torch",
17849
+ "item.inverter_welder": "Inverter welder",
17850
+ "item.cordless_drill": "Cordless drill",
17851
+ "item.shovel": "Shovel",
17852
+ "item.hoe": "Hoe",
17853
+ "item.pocket_knife": "Multi-tool pocket knife",
17854
+ "item.can_opener": "Can opener",
17855
+ "item.scissors": "Scissors",
17856
+ "item.duct_tape": "Duct tape",
17857
+ "item.pot": "Pot",
17858
+ "item.camp_stove": "Camping stove",
17859
+ "item.sewing_kit": "Sewing kit",
17860
+ "item.lockpick_kit": "Locksmith kit (lock picks)",
17861
+ "item.lighter": "Lighter",
17862
+ "item.matches": "Matches",
17863
+ "item.flint_striker": "Flint and striker",
17864
+ "item.bucket": "Bucket",
17865
+ "item.binoculars": "Binoculars",
17866
+ "item.fire_extinguisher": "Dry-powder fire extinguisher",
17867
+ "item.sharpening_stone": "Sharpening stone",
17868
+ "item.knife": "Kitchen knife",
17869
+ "item.tactical_knife": "Tactical knife",
17870
+ "item.machete": "Machete",
17871
+ "item.meat_cleaver": "Butcher's cleaver",
17872
+ "item.hatchet": "Hatchet",
17873
+ "item.axe": "Axe",
17874
+ "item.baseball_bat": "Baseball bat",
17875
+ "item.nail_bat": "Nail bat",
17876
+ "item.iron_pipe": "Iron pipe",
17877
+ "item.sledgehammer": "Sledgehammer",
17878
+ "item.pickaxe": "Pickaxe",
17879
+ "item.sickle": "Sickle",
17880
+ "item.police_baton": "Police baton",
17881
+ "item.cast_iron_skillet": "Cast iron skillet",
17882
+ "item.improvised_spear": "Improvised spear",
17883
+ "item.chainsaw": "Chainsaw",
17884
+ "item.slingshot": "Slingshot",
17885
+ "item.recurve_bow": "Recurve bow",
17886
+ "item.crossbow": "Crossbow",
17887
+ "item.arrows_and_bolts": "Arrows and bolts (6)",
17888
+ "item.wooden_plank": "Wooden plank",
17889
+ "item.nails": "Nails (box of 100)",
17890
+ "item.screws": "Screws (box of 50)",
17891
+ "item.sheet_metal": "Sheet metal",
17892
+ "item.scrap_metal": "Scrap metal",
17893
+ "item.wire_roll": "Wire (10 m roll)",
17894
+ "item.barbed_wire": "Barbed wire (25 m roll)",
17895
+ "item.rope": "Rope",
17896
+ "item.tarp": "Tarp (4 x 4 m)",
17897
+ "item.raffia_sack": "Raffia sack",
17898
+ "item.bricks": "Bricks (10)",
17899
+ "item.cement_bag": "Cement (25 kg bag)",
17900
+ "item.rag": "Rag",
17901
+ "item.glass_bottle": "Empty glass bottle",
17902
+ "item.pet_bottle": "Empty PET bottle",
17903
+ "item.empty_can": "Empty can",
17904
+ "item.glass_shards": "Glass shards",
17905
+ "item.epoxy_glue": "Epoxy glue",
17906
+ "item.electronic_components": "Electronic components",
17907
+ "item.copper_wire": "Copper wire (5 m)",
17908
+ "item.firewood": "Firewood",
17909
+ "item.rebar": "Rebar",
17910
+ "item.lubricant_spray": "Spray lubricant oil",
17911
+ "item.tshirt": "T-shirt",
17912
+ "item.hoodie": "Hoodie",
17913
+ "item.denim_jacket": "Denim jacket",
17914
+ "item.leather_motorcycle_jacket": "Motorcycle jacket (leather)",
17915
+ "item.jeans": "Jeans",
17916
+ "item.leather_motorcycle_pants": "Motorcycle pants",
17917
+ "item.army_camouflage_uniform": "Army camouflage uniform",
17918
+ "item.mechanic_coveralls": "Mechanic's coveralls",
17919
+ "item.firefighter_suit": "Firefighter suit",
17920
+ "item.beekeeper_suit": "Beekeeper suit",
17921
+ "item.raincoat": "Raincoat",
17922
+ "item.padded_coat": "Padded coat",
17923
+ "item.motorcycle_helmet": "Full-face motorcycle helmet",
17924
+ "item.hard_hat": "Hard hat (PPE)",
17925
+ "item.safety_goggles": "Safety goggles",
17926
+ "item.leather_work_gloves": "Leather work gloves",
17927
+ "item.combat_boots": "Combat boots",
17928
+ "item.rubber_boots": "Rubber boots",
17929
+ "item.sneakers": "Sneakers",
17930
+ "item.flip_flops": "Flip-flops",
17931
+ "item.magazine_arm_guard": "Magazine arm guard",
17932
+ "item.sheet_metal_forearm_guard": "Sheet metal forearm guard",
17933
+ "item.ballistic_vest": "Ballistic vests (II-A, III-A, III)",
17934
+ "item.shopping_bag": "Shopping bag",
17935
+ "item.trash_bag": "Trash bag",
17936
+ "item.fanny_pack": "Fanny pack",
17937
+ "item.crossbody_bag": "Crossbody bag",
17938
+ "item.backpack": "Backpack",
17939
+ "item.delivery_bag": "Delivery bag",
17940
+ "item.hiking_backpack_40l": "Hiking backpack 40 L",
17941
+ "item.military_backpack": "Military backpack",
17942
+ "item.trekking_backpack_70l": "Trekking backpack 70 L",
17943
+ "item.rolling_suitcase": "Rolling suitcase",
17606
17944
  "item.tactical_vest": "Tactical vest",
17607
17945
  "item.tool_belt": "Tool belt",
17608
17946
  "item.cooler_box": "Cooler box",
@@ -18445,257 +18783,6 @@ var pt_BR_default = {
18445
18783
  var GOODS_STRINGS = { en: en_default, "pt-BR": pt_BR_default };
18446
18784
  var pickGoodsLocale = (locale) => locale?.toLowerCase().startsWith("pt") ? "pt-BR" : "en";
18447
18785
  var goodsText = (key, locale) => GOODS_STRINGS[pickGoodsLocale(locale)][key] ?? key;
18448
-
18449
- // src/contracts.ts
18450
- import { checkSpec } from "@adatechnology/game-contracts";
18451
-
18452
- // src/data/item-contracts.json
18453
- var item_contracts_default = {
18454
- densityKgPerLitre: { min: 0.02, max: 22 },
18455
- impactResponses: ["dent", "break", "shatter", "tear", "squash", "pierce", "none"],
18456
- heatResistantMaterials: ["steel", "cast_iron", "aluminium", "copper", "ceramic", "tempered_glass", "stone", "brick"],
18457
- bodySlots: ["head", "eyes", "torso", "arms", "hands", "legs", "feet", "over"]
18458
- };
18459
-
18460
- // src/contracts.ts
18461
- var IMPACT_RESPONSES = item_contracts_default.impactResponses;
18462
- var HEAT_RESISTANT_MATERIALS = item_contracts_default.heatResistantMaterials;
18463
- var DENSITY_KG_PER_LITRE = item_contracts_default.densityKgPerLitre;
18464
- var BODY_SLOTS = item_contracts_default.bodySlots;
18465
- var SNAKE_CASE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
18466
- var LITRES_PER_CUBIC_METRE = 1e3;
18467
- var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
18468
- var isFraction = (value) => isNumber(value) && value >= 0 && value <= 1;
18469
- var catalogOf = (options) => options.items ?? GOODS_ITEMS;
18470
- var parts = (item) => Array.isArray(item.parts) ? item.parts : [];
18471
- var physical = (options) => {
18472
- const range = options.density ?? DENSITY_KG_PER_LITRE;
18473
- return {
18474
- id: "physical",
18475
- check: (item) => {
18476
- const problems = [];
18477
- const size = item.size;
18478
- const sizeOk = Array.isArray(size) && size.length === 3 && size.every((side) => isNumber(side) && side > 0);
18479
- if (!sizeOk) problems.push("size must be 3 finite positive numbers in metres");
18480
- if (!isNumber(item.weight) || item.weight < 0) problems.push("weight must be a finite number >= 0 (kg)");
18481
- if (sizeOk && isNumber(item.weight) && item.weight > 0) {
18482
- const [width, height, depth] = size;
18483
- const density = item.weight / (width * height * depth * LITRES_PER_CUBIC_METRE);
18484
- if (density < range.min || density > range.max) problems.push(`density ${Number(density.toPrecision(2))} kg/L is outside ${range.min}..${range.max}`);
18485
- }
18486
- return problems;
18487
- }
18488
- };
18489
- };
18490
- var destructible = (options) => {
18491
- const materials = options.materials ? new Set(options.materials) : void 0;
18492
- return {
18493
- id: "destructible",
18494
- check: (item) => {
18495
- const list = parts(item);
18496
- if (list.length === 0) return ["missing parts"];
18497
- const problems = [];
18498
- const seen = /* @__PURE__ */ new Set();
18499
- for (const part of list) {
18500
- if (typeof part.id !== "string" || part.id === "") problems.push("a part has no id");
18501
- else if (seen.has(part.id)) problems.push(`duplicate part "${part.id}"`);
18502
- else seen.add(part.id);
18503
- if (typeof part.material !== "string" || part.material === "") problems.push(`part "${part.id}" has no material`);
18504
- else if (materials && !materials.has(part.material)) problems.push(`part "${part.id}" has unknown material "${part.material}"`);
18505
- if (part.thickness !== void 0 && !(isNumber(part.thickness) && part.thickness > 0)) problems.push(`part "${part.id}" thickness must be a positive number`);
18506
- }
18507
- if (problems.length === 0 && list.every((part) => part.shape !== void 0 && part.position !== void 0)) {
18508
- const report = checkSpec({ parts: list }, materials ? { materials } : {});
18509
- for (const problem of report.problems) problems.push(`spec: ${problem}`);
18510
- }
18511
- return problems;
18512
- }
18513
- };
18514
- };
18515
- var impact = {
18516
- id: "impact",
18517
- check: (item) => {
18518
- const value = item.impact;
18519
- if (!value) return ["missing impact"];
18520
- const problems = [];
18521
- if (!IMPACT_RESPONSES.includes(value.response ?? "")) problems.push(`impact.response must be one of ${IMPACT_RESPONSES.join(", ")}`);
18522
- if (typeof value.pushImpulse !== "boolean") problems.push("impact.pushImpulse must be true or false");
18523
- if (value.shed !== void 0 && typeof value.shed !== "boolean") problems.push("impact.shed must be true or false");
18524
- return problems;
18525
- }
18526
- };
18527
- var useAnimation = {
18528
- id: "use_animation",
18529
- check: (item) => {
18530
- const problems = [];
18531
- const clip = item.use?.clip;
18532
- if (clip === void 0) problems.push("missing use.clip");
18533
- else if (!SNAKE_CASE.test(clip)) problems.push(`use.clip "${clip}" must be snake_case`);
18534
- const sounds = item.use?.sounds;
18535
- if (!Array.isArray(sounds) || sounds.length === 0 || sounds.some((sound) => typeof sound !== "string" || sound === "")) problems.push("missing use.sounds (a non-empty list of sound ids)");
18536
- return problems;
18537
- }
18538
- };
18539
- var leftover = (options) => {
18540
- const ids = new Set(catalogOf(options).map((item) => item.id));
18541
- return {
18542
- id: "leftover",
18543
- check: (item) => {
18544
- const value = item.leftover;
18545
- if (value === void 0) return ["missing leftover"];
18546
- if (value === "none") return [];
18547
- const problems = [];
18548
- if (typeof value !== "object" || value === null) return ['leftover must be an object or "none"'];
18549
- if (typeof value.item !== "string" || value.item === "") problems.push("missing leftover.item");
18550
- else if (!ids.has(value.item)) problems.push(`leftover.item "${value.item}" is not in the catalog`);
18551
- if (!isNumber(value.despawnSeconds) || value.despawnSeconds < 0) problems.push("missing leftover.despawnSeconds");
18552
- return problems;
18553
- }
18554
- };
18555
- };
18556
- var edible = {
18557
- id: "edible",
18558
- check: (item) => {
18559
- const problems = [];
18560
- const nutrition = item.nutrition;
18561
- if (!nutrition || !(isNumber(nutrition.satiety) || isNumber(nutrition.hydration))) problems.push("missing nutrition");
18562
- if (item.shelfLife === void 0 && item.nonPerishable !== true) problems.push("missing shelfLife (a number of days, or nonPerishable: true)");
18563
- else if (item.shelfLife !== void 0 && !(isNumber(item.shelfLife) && item.shelfLife > 0)) problems.push("shelfLife must be a positive number of days");
18564
- if (!isFraction(item.rawEffect?.sickChance)) problems.push("missing rawEffect.sickChance (0..1)");
18565
- return problems;
18566
- }
18567
- };
18568
- var inputsOf = (recipe) => recipe.inputs ?? [];
18569
- var toolsOf = (recipe) => recipe.tools ?? [];
18570
- var consumes = (input, item) => {
18571
- if (input.item === item.id) return true;
18572
- if (input.any?.includes(item.id)) return true;
18573
- if (!input.match) return false;
18574
- const entries = Object.entries(input.match);
18575
- return entries.length > 0 && entries.every(([key, value]) => item[key] === value);
18576
- };
18577
- var recipeInput = (options) => {
18578
- const recipes = options.recipes ?? GOODS_RECIPES;
18579
- return {
18580
- id: "recipe_input",
18581
- check: (item) => recipes.some((recipe) => inputsOf(recipe).some((input) => consumes(input, item))) ? [] : ["is not an input of any recipe"]
18582
- };
18583
- };
18584
- var preparation = (options) => {
18585
- const recipes = options.recipes ?? GOODS_RECIPES;
18586
- const cookware = new Set(catalogOf(options).filter((item) => item.kind === "cookware").map((item) => item.id));
18587
- return {
18588
- id: "preparation",
18589
- check: (item) => {
18590
- const usable = recipes.filter((recipe) => inputsOf(recipe).some((input) => consumes(input, item)));
18591
- if (usable.length === 0) return ["has no recipe that prepares it"];
18592
- const prepares = usable.some((recipe) => {
18593
- const heat = toolsOf(recipe).some((tool) => tool.world?.includes("fire"));
18594
- const vessel = [...inputsOf(recipe).flatMap((input) => input.item ? [input.item] : []), ...toolsOf(recipe).flatMap((tool) => tool.anyOf ?? [])].some((id) => cookware.has(id));
18595
- return heat && vessel;
18596
- });
18597
- return prepares ? [] : ["has no recipe that needs fire and a cooking vessel"];
18598
- }
18599
- };
18600
- };
18601
- var liquidContainer = {
18602
- id: "liquid_container",
18603
- check: (item) => {
18604
- const fluid = item.fluid;
18605
- if (fluid) {
18606
- const problems = [];
18607
- if (typeof fluid.liquid !== "string" || !SNAKE_CASE.test(fluid.liquid)) problems.push("fluid.liquid must be a liquid id");
18608
- if (!isNumber(fluid.liters) || fluid.liters < 0) problems.push("fluid.liters must be a number >= 0");
18609
- return problems;
18610
- }
18611
- return isNumber(item.container?.capacityLiters) && (item.container?.capacityLiters ?? 0) > 0 ? [] : ["missing fluid { liquid, liters } or container.capacityLiters"];
18612
- }
18613
- };
18614
- var heatResistant = (options) => {
18615
- const allowed = new Set(options.heatResistant ?? HEAT_RESISTANT_MATERIALS);
18616
- return {
18617
- id: "heat_resistant",
18618
- check: (item) => parts(item).filter((part) => !allowed.has(part.material)).map((part) => `part "${part.id}" is ${part.material}, which does not take a flame`)
18619
- };
18620
- };
18621
- var weapon = (options) => {
18622
- const weaponIds = options.weaponIds ? new Set(options.weaponIds) : void 0;
18623
- const calibers = options.calibers ? new Set(options.calibers) : void 0;
18624
- return {
18625
- id: "weapon",
18626
- check: (item, ctx) => {
18627
- if (ctx.chain.includes("ammo")) {
18628
- if (typeof item.caliber !== "string" || item.caliber === "") return ["missing caliber"];
18629
- return calibers && !calibers.has(item.caliber) ? [`caliber "${item.caliber}" is not in the arsenal`] : [];
18630
- }
18631
- if (typeof item.weapon !== "string" || item.weapon === "") return ["missing weapon (the arsenal id)"];
18632
- return weaponIds && !weaponIds.has(item.weapon) ? [`weapon "${item.weapon}" is not in the arsenal`] : [];
18633
- }
18634
- };
18635
- };
18636
- var wearable = {
18637
- id: "wearable",
18638
- check: (item, ctx) => {
18639
- const problems = [];
18640
- const { protection, carry } = item;
18641
- if (!protection && !carry) return [ctx.chain.includes("bag") ? "missing carry" : "missing protection"];
18642
- if (ctx.chain.includes("bag") && !carry) problems.push("missing carry");
18643
- if (ctx.chain.includes("clothing") && !protection) problems.push("missing protection");
18644
- if (protection) {
18645
- const slots = protection.slots;
18646
- if (!Array.isArray(slots) || slots.length === 0) problems.push("protection.slots must list at least one body slot");
18647
- else for (const slot of slots) if (!BODY_SLOTS.includes(slot)) problems.push(`protection.slots has unknown slot "${slot}"`);
18648
- for (const field of ["bite", "scratch", "warmth"]) if (protection[field] !== void 0 && !isFraction(protection[field])) problems.push(`protection.${field} must be between 0 and 1`);
18649
- }
18650
- if (carry) {
18651
- if (!(isNumber(carry.capacityKg) && carry.capacityKg > 0)) problems.push("carry.capacityKg must be positive");
18652
- if (!isFraction(carry.weightReduction)) problems.push("carry.weightReduction must be between 0 and 1");
18653
- if (!(isNumber(carry.quickSlots) && carry.quickSlots >= 0)) problems.push("carry.quickSlots must be >= 0");
18654
- }
18655
- return problems;
18656
- }
18657
- };
18658
- var battery = {
18659
- id: "battery",
18660
- check: (item) => isNumber(item.charge?.lossPerMonth) && (item.charge?.lossPerMonth ?? -1) >= 0 ? [] : ["missing charge.lossPerMonth"]
18661
- };
18662
- var createItemContracts = (options = {}) => [
18663
- physical(options),
18664
- destructible(options),
18665
- impact,
18666
- useAnimation,
18667
- leftover(options),
18668
- edible,
18669
- preparation(options),
18670
- recipeInput(options),
18671
- liquidContainer,
18672
- heatResistant(options),
18673
- weapon(options),
18674
- wearable,
18675
- battery
18676
- ];
18677
- var ITEM_CONTRACTS = createItemContracts();
18678
-
18679
- // src/kinds.ts
18680
- import { checkCategorized, defineCategories } from "@adatechnology/game-contracts";
18681
- var createGoodsKinds = (options = {}) => defineCategories(GOODS_KINDS, options);
18682
- var lootCategoriesOf = (kinds, kind) => {
18683
- const value = kinds.defaults(kind).lootCategory;
18684
- if (value === void 0) return void 0;
18685
- return Array.isArray(value) ? value : [String(value)];
18686
- };
18687
- var checkGoodsItems = (options = {}) => {
18688
- const kinds = options.kinds ?? createGoodsKinds();
18689
- const entries = options.entries ?? GOODS_ITEMS;
18690
- const report = checkCategorized(entries, kinds, createItemContracts({ items: entries, ...options }), { allow: options.allow ?? GOODS_KNOWN_GAPS });
18691
- const problems = [...report.problems];
18692
- for (const entry of entries) {
18693
- if (!kinds.has(entry.kind) || kinds.get(entry.kind)?.abstract) continue;
18694
- const declared = lootCategoriesOf(kinds, entry.kind);
18695
- if (declared && !declared.includes(entry.category)) problems.push(`"${entry.id}" (${entry.kind}): category "${entry.category}" is not one of ${declared.join(", ")}, required by the kind's lootCategory`);
18696
- }
18697
- return { ok: problems.length === 0, problems, allowed: report.allowed };
18698
- };
18699
18786
  export {
18700
18787
  BODY_SLOTS,
18701
18788
  DENSITY_KG_PER_LITRE,