@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.
@@ -2787,7 +2787,11 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
2787
2787
  "road"
2788
2788
  ]
2789
2789
  },
2790
- model: "canned_sardines"
2790
+ model: "canned_sardines",
2791
+ seal: {
2792
+ byHand: true,
2793
+ spoilsWhenBreached: true
2794
+ }
2791
2795
  },
2792
2796
  {
2793
2797
  id: "canned_corn_and_peas",
@@ -2969,7 +2973,13 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
2969
2973
  "school"
2970
2974
  ]
2971
2975
  },
2972
- model: "rice"
2976
+ model: "rice",
2977
+ powder: {
2978
+ color: 15722710
2979
+ },
2980
+ estimated: [
2981
+ "powder"
2982
+ ]
2973
2983
  },
2974
2984
  {
2975
2985
  id: "beans",
@@ -3307,7 +3317,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
3307
3317
  nonPerishable: true,
3308
3318
  estimated: [
3309
3319
  "nonPerishable"
3310
- ]
3320
+ ],
3321
+ seal: {
3322
+ byHand: true
3323
+ }
3311
3324
  },
3312
3325
  {
3313
3326
  id: "bread",
@@ -3849,7 +3862,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
3849
3862
  "lodge"
3850
3863
  ]
3851
3864
  },
3852
- model: "home_preserves"
3865
+ model: "home_preserves",
3866
+ seal: {
3867
+ byHand: true
3868
+ }
3853
3869
  },
3854
3870
  {
3855
3871
  id: "dog_food",
@@ -3922,8 +3938,12 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
3922
3938
  },
3923
3939
  model: "sugar",
3924
3940
  nonPerishable: true,
3941
+ powder: {
3942
+ color: 16184558
3943
+ },
3925
3944
  estimated: [
3926
- "nonPerishable"
3945
+ "nonPerishable",
3946
+ "powder"
3927
3947
  ]
3928
3948
  },
3929
3949
  {
@@ -3961,8 +3981,12 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
3961
3981
  },
3962
3982
  model: "salt",
3963
3983
  nonPerishable: true,
3984
+ powder: {
3985
+ color: 15921902
3986
+ },
3964
3987
  estimated: [
3965
- "nonPerishable"
3988
+ "nonPerishable",
3989
+ "powder"
3966
3990
  ]
3967
3991
  },
3968
3992
  {
@@ -3999,7 +4023,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
3999
4023
  "supermarket"
4000
4024
  ]
4001
4025
  },
4002
- model: "cooking_oil"
4026
+ model: "cooking_oil",
4027
+ seal: {
4028
+ byHand: true
4029
+ }
4003
4030
  },
4004
4031
  {
4005
4032
  id: "coffee",
@@ -4037,7 +4064,13 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4037
4064
  "police_station"
4038
4065
  ]
4039
4066
  },
4040
- model: "coffee"
4067
+ model: "coffee",
4068
+ powder: {
4069
+ color: 4861984
4070
+ },
4071
+ estimated: [
4072
+ "powder"
4073
+ ]
4041
4074
  },
4042
4075
  {
4043
4076
  id: "water_bottle",
@@ -4081,7 +4114,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4081
4114
  estimated: [
4082
4115
  "nonPerishable",
4083
4116
  "fluid"
4084
- ]
4117
+ ],
4118
+ seal: {
4119
+ byHand: true
4120
+ }
4085
4121
  },
4086
4122
  {
4087
4123
  id: "water_jug_5l",
@@ -4121,7 +4157,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4121
4157
  estimated: [
4122
4158
  "nonPerishable",
4123
4159
  "fluid"
4124
- ]
4160
+ ],
4161
+ seal: {
4162
+ byHand: true
4163
+ }
4125
4164
  },
4126
4165
  {
4127
4166
  id: "dirty_water",
@@ -4158,7 +4197,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4158
4197
  estimated: [
4159
4198
  "nonPerishable",
4160
4199
  "fluid"
4161
- ]
4200
+ ],
4201
+ seal: {
4202
+ byHand: true
4203
+ }
4162
4204
  },
4163
4205
  {
4164
4206
  id: "soda_can",
@@ -4366,7 +4408,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4366
4408
  },
4367
4409
  estimated: [
4368
4410
  "fluid"
4369
- ]
4411
+ ],
4412
+ seal: {
4413
+ byHand: true
4414
+ }
4370
4415
  },
4371
4416
  {
4372
4417
  id: "energy_drink",
@@ -4407,7 +4452,11 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4407
4452
  },
4408
4453
  estimated: [
4409
4454
  "fluid"
4410
- ]
4455
+ ],
4456
+ seal: {
4457
+ byHand: true,
4458
+ spoilsWhenBreached: true
4459
+ }
4411
4460
  },
4412
4461
  {
4413
4462
  id: "coconut_water",
@@ -4447,7 +4496,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4447
4496
  },
4448
4497
  estimated: [
4449
4498
  "fluid"
4450
- ]
4499
+ ],
4500
+ seal: {
4501
+ byHand: true
4502
+ }
4451
4503
  },
4452
4504
  {
4453
4505
  id: "beer_can",
@@ -4547,7 +4599,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4547
4599
  "parts",
4548
4600
  "impact",
4549
4601
  "leftover"
4550
- ]
4602
+ ],
4603
+ seal: {
4604
+ byHand: true
4605
+ }
4551
4606
  },
4552
4607
  {
4553
4608
  id: "communion_wine",
@@ -4603,7 +4658,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
4603
4658
  "parts",
4604
4659
  "impact",
4605
4660
  "leftover"
4606
- ]
4661
+ ],
4662
+ seal: {
4663
+ byHand: true
4664
+ }
4607
4665
  },
4608
4666
  {
4609
4667
  id: "bandage",
@@ -11149,9 +11207,13 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
11149
11207
  hydration: -8
11150
11208
  },
11151
11209
  shelfLife: 365,
11210
+ powder: {
11211
+ color: 15986662
11212
+ },
11152
11213
  estimated: [
11153
11214
  "nutrition",
11154
- "shelfLife"
11215
+ "shelfLife",
11216
+ "powder"
11155
11217
  ]
11156
11218
  },
11157
11219
  {
@@ -11176,9 +11238,13 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
11176
11238
  hydration: -5
11177
11239
  },
11178
11240
  shelfLife: 270,
11241
+ powder: {
11242
+ color: 14201962
11243
+ },
11179
11244
  estimated: [
11180
11245
  "nutrition",
11181
- "shelfLife"
11246
+ "shelfLife",
11247
+ "powder"
11182
11248
  ]
11183
11249
  },
11184
11250
  {
@@ -13377,6 +13443,15 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
13377
13443
  "chew"
13378
13444
  ]
13379
13445
  },
13446
+ seal: {
13447
+ openWith: [
13448
+ "can_opener",
13449
+ "pocket_knife",
13450
+ "knife",
13451
+ "tactical_knife"
13452
+ ],
13453
+ spoilsWhenBreached: true
13454
+ },
13380
13455
  leftover: {
13381
13456
  item: "empty_can",
13382
13457
  despawnSeconds: 600
@@ -13538,6 +13613,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
13538
13613
  "gulp"
13539
13614
  ]
13540
13615
  },
13616
+ seal: {
13617
+ byHand: true,
13618
+ spoilsWhenBreached: true
13619
+ },
13541
13620
  leftover: {
13542
13621
  item: "empty_can",
13543
13622
  despawnSeconds: 600
@@ -18051,208 +18130,461 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
18051
18130
  var GOODS_KINDS = kinds_default;
18052
18131
  var GOODS_KNOWN_GAPS = known_gaps_default;
18053
18132
 
18054
- // src/pack.ts
18055
- var goodsPack = {
18056
- id: "goods",
18057
- items: GOODS_ITEMS,
18058
- categories: GOODS_CATEGORIES,
18059
- places: GOODS_PLACES,
18060
- containers: GOODS_CONTAINERS,
18061
- useEffects: GOODS_USE_EFFECTS,
18062
- recipes: GOODS_RECIPES,
18063
- pools: GOODS_HOUSE_POOLS.pools,
18064
- poolAliases: { ...GOODS_HOUSE_POOLS.aliasOf, ...FLOORPLAN_POOL_ALIASES }
18133
+ // src/data/item-contracts.json
18134
+ var item_contracts_default = {
18135
+ densityKgPerLitre: { min: 0.02, max: 22 },
18136
+ impactResponses: ["dent", "break", "shatter", "tear", "squash", "pierce", "none"],
18137
+ heatResistantMaterials: ["steel", "cast_iron", "aluminium", "copper", "ceramic", "tempered_glass", "stone", "brick"],
18138
+ bodySlots: ["head", "eyes", "torso", "arms", "hands", "legs", "feet", "over"]
18065
18139
  };
18066
- var createGoodsItems = (options = {}) => createItems({ ...options, packs: [goodsPack, ...options.packs ?? []] });
18067
18140
 
18068
- // src/data/locales/en.json
18069
- var en_default = {
18070
- "item.canned_beans": "Canned beans",
18071
- "item.canned_sardines": "Canned sardines",
18072
- "item.canned_corn_and_peas": "Canned corn and peas",
18073
- "item.canned_sausages": "Canned sausages",
18074
- "item.condensed_milk": "Condensed milk",
18075
- "item.military_ration": "Army field ration",
18076
- "item.rice": "Rice (1 kg)",
18077
- "item.beans": "Beans (1 kg)",
18078
- "item.pasta": "Pasta",
18079
- "item.instant_noodles": "Instant noodles",
18080
- "item.cassava_flour": "Cassava flour (1 kg)",
18081
- "item.biscuits": "Biscuits",
18082
- "item.snack_chips": "Bagged snack chips",
18083
- "item.chocolate_bar": "Chocolate bar",
18084
- "item.rapadura": "Rapadura (raw cane sugar block)",
18085
- "item.honey_jar": "Jar of honey",
18086
- "item.bread": "Bread",
18087
- "item.cheese": "Cheese",
18088
- "item.mortadella": "Mortadella (whole piece)",
18089
- "item.raw_beef": "Raw beef (1 kg)",
18090
- "item.frozen_chicken": "Frozen chicken",
18091
- "item.eggs": "Eggs (dozen)",
18092
- "item.banana_bunch": "Bunch of bananas",
18093
- "item.orange": "Orange",
18094
- "item.potatoes": "Potatoes (1 kg)",
18095
- "item.cassava_root": "Cassava root",
18096
- "item.fresh_fish": "Fresh fish",
18097
- "item.salted_fish": "Salted fish",
18098
- "item.jerked_beef": "Jerked beef",
18099
- "item.game_meat": "Game meat (capybara, wild boar)",
18100
- "item.home_preserves": "Home preserves (jar)",
18101
- "item.dog_food": "Dog food (1 kg)",
18102
- "item.sugar": "Sugar",
18103
- "item.salt": "Salt",
18104
- "item.cooking_oil": "Cooking oil",
18105
- "item.coffee": "Coffee",
18106
- "item.water_bottle": "Water bottle",
18107
- "item.water_jug_5l": "Water jug (5 L)",
18108
- "item.dirty_water": "River or puddle water",
18109
- "item.soda_can": "Soda can",
18110
- "item.juice_box": "Juice box",
18111
- "item.milk_carton": "Milk carton",
18112
- "item.sports_drink": "Sports drink",
18113
- "item.energy_drink": "Energy drink (can)",
18114
- "item.coconut_water": "Coconut water",
18115
- "item.beer_can": "Beer (can)",
18116
- "item.cachaca": "Cachaca (bottle)",
18117
- "item.communion_wine": "Communion wine",
18118
- "item.bandage": "Bandage",
18119
- "item.sterile_gauze": "Sterile gauze",
18120
- "item.adhesive_bandages": "Adhesive bandages (box)",
18121
- "item.medical_tape": "Medical tape",
18122
- "item.alcohol_70": "Alcohol 70%",
18123
- "item.povidone_iodine": "Povidone-iodine",
18124
- "item.saline_iv_bag": "Saline bag with IV set",
18125
- "item.painkillers": "Painkillers",
18126
- "item.ibuprofen": "Anti-inflammatory (ibuprofen)",
18127
- "item.tramadol": "Strong painkiller (tramadol)",
18128
- "item.clonazepam": "Sedative (clonazepam)",
18129
- "item.amoxicillin": "Antibiotic (amoxicillin)",
18130
- "item.ceftriaxone": "Injectable antibiotic (ceftriaxone)",
18131
- "item.syringe": "Disposable syringe",
18132
- "item.suture_kit": "Suture kit",
18133
- "item.splint": "Immobilizing splint",
18134
- "item.first_aid_kit": "First aid kit",
18135
- "item.exam_gloves": "Exam gloves",
18136
- "item.n95_mask": "N95 mask",
18137
- "item.activated_charcoal": "Activated charcoal",
18138
- "item.rehydration_salts": "Rehydration salts",
18139
- "item.antihistamine": "Antihistamine",
18140
- "item.antivenom": "Snake antivenom",
18141
- "item.thermometer": "Thermometer",
18142
- "item.maintenance_medication": "Daily-use medication",
18143
- "item.multivitamin": "Multivitamin",
18144
- "item.hammer": "Hammer",
18145
- "item.screwdriver": "Screwdriver",
18146
- "item.crowbar": "Crowbar",
18147
- "item.handsaw": "Handsaw",
18148
- "item.hacksaw": "Hacksaw",
18149
- "item.bolt_cutters": "Bolt cutters",
18150
- "item.pliers": "Pliers",
18151
- "item.adjustable_wrench": "Adjustable wrench",
18152
- "item.mechanic_toolbox": "Mechanic's toolbox",
18153
- "item.car_jack": "Car jack and wheel wrench",
18154
- "item.gas_torch": "Gas torch",
18155
- "item.inverter_welder": "Inverter welder",
18156
- "item.cordless_drill": "Cordless drill",
18157
- "item.shovel": "Shovel",
18158
- "item.hoe": "Hoe",
18159
- "item.pocket_knife": "Multi-tool pocket knife",
18160
- "item.can_opener": "Can opener",
18161
- "item.scissors": "Scissors",
18162
- "item.duct_tape": "Duct tape",
18163
- "item.pot": "Pot",
18164
- "item.camp_stove": "Camping stove",
18165
- "item.sewing_kit": "Sewing kit",
18166
- "item.lockpick_kit": "Locksmith kit (lock picks)",
18167
- "item.lighter": "Lighter",
18168
- "item.matches": "Matches",
18169
- "item.flint_striker": "Flint and striker",
18170
- "item.bucket": "Bucket",
18171
- "item.binoculars": "Binoculars",
18172
- "item.fire_extinguisher": "Dry-powder fire extinguisher",
18173
- "item.sharpening_stone": "Sharpening stone",
18174
- "item.knife": "Kitchen knife",
18175
- "item.tactical_knife": "Tactical knife",
18176
- "item.machete": "Machete",
18177
- "item.meat_cleaver": "Butcher's cleaver",
18178
- "item.hatchet": "Hatchet",
18179
- "item.axe": "Axe",
18180
- "item.baseball_bat": "Baseball bat",
18181
- "item.nail_bat": "Nail bat",
18182
- "item.iron_pipe": "Iron pipe",
18183
- "item.sledgehammer": "Sledgehammer",
18184
- "item.pickaxe": "Pickaxe",
18185
- "item.sickle": "Sickle",
18186
- "item.police_baton": "Police baton",
18187
- "item.cast_iron_skillet": "Cast iron skillet",
18188
- "item.improvised_spear": "Improvised spear",
18189
- "item.chainsaw": "Chainsaw",
18190
- "item.slingshot": "Slingshot",
18191
- "item.recurve_bow": "Recurve bow",
18192
- "item.crossbow": "Crossbow",
18193
- "item.arrows_and_bolts": "Arrows and bolts (6)",
18194
- "item.wooden_plank": "Wooden plank",
18195
- "item.nails": "Nails (box of 100)",
18196
- "item.screws": "Screws (box of 50)",
18197
- "item.sheet_metal": "Sheet metal",
18198
- "item.scrap_metal": "Scrap metal",
18199
- "item.wire_roll": "Wire (10 m roll)",
18200
- "item.barbed_wire": "Barbed wire (25 m roll)",
18201
- "item.rope": "Rope",
18202
- "item.tarp": "Tarp (4 x 4 m)",
18203
- "item.raffia_sack": "Raffia sack",
18204
- "item.bricks": "Bricks (10)",
18205
- "item.cement_bag": "Cement (25 kg bag)",
18206
- "item.rag": "Rag",
18207
- "item.glass_bottle": "Empty glass bottle",
18208
- "item.pet_bottle": "Empty PET bottle",
18209
- "item.empty_can": "Empty can",
18210
- "item.glass_shards": "Glass shards",
18211
- "item.epoxy_glue": "Epoxy glue",
18212
- "item.electronic_components": "Electronic components",
18213
- "item.copper_wire": "Copper wire (5 m)",
18214
- "item.firewood": "Firewood",
18215
- "item.rebar": "Rebar",
18216
- "item.lubricant_spray": "Spray lubricant oil",
18217
- "item.tshirt": "T-shirt",
18218
- "item.hoodie": "Hoodie",
18219
- "item.denim_jacket": "Denim jacket",
18220
- "item.leather_motorcycle_jacket": "Motorcycle jacket (leather)",
18221
- "item.jeans": "Jeans",
18222
- "item.leather_motorcycle_pants": "Motorcycle pants",
18223
- "item.army_camouflage_uniform": "Army camouflage uniform",
18224
- "item.mechanic_coveralls": "Mechanic's coveralls",
18225
- "item.firefighter_suit": "Firefighter suit",
18226
- "item.beekeeper_suit": "Beekeeper suit",
18227
- "item.raincoat": "Raincoat",
18228
- "item.padded_coat": "Padded coat",
18229
- "item.motorcycle_helmet": "Full-face motorcycle helmet",
18230
- "item.hard_hat": "Hard hat (PPE)",
18231
- "item.safety_goggles": "Safety goggles",
18232
- "item.leather_work_gloves": "Leather work gloves",
18233
- "item.combat_boots": "Combat boots",
18234
- "item.rubber_boots": "Rubber boots",
18235
- "item.sneakers": "Sneakers",
18236
- "item.flip_flops": "Flip-flops",
18237
- "item.magazine_arm_guard": "Magazine arm guard",
18238
- "item.sheet_metal_forearm_guard": "Sheet metal forearm guard",
18239
- "item.ballistic_vest": "Ballistic vests (II-A, III-A, III)",
18240
- "item.shopping_bag": "Shopping bag",
18241
- "item.trash_bag": "Trash bag",
18242
- "item.fanny_pack": "Fanny pack",
18243
- "item.crossbody_bag": "Crossbody bag",
18244
- "item.backpack": "Backpack",
18245
- "item.delivery_bag": "Delivery bag",
18246
- "item.hiking_backpack_40l": "Hiking backpack 40 L",
18247
- "item.military_backpack": "Military backpack",
18248
- "item.trekking_backpack_70l": "Trekking backpack 70 L",
18249
- "item.rolling_suitcase": "Rolling suitcase",
18250
- "item.tactical_vest": "Tactical vest",
18251
- "item.tool_belt": "Tool belt",
18252
- "item.cooler_box": "Cooler box",
18253
- "item.shopping_cart": "Shopping cart",
18254
- "item.wheelbarrow": "Wheelbarrow",
18255
- "item.flashlight": "Flashlight",
18141
+ // src/contracts.ts
18142
+ var IMPACT_RESPONSES = item_contracts_default.impactResponses;
18143
+ var HEAT_RESISTANT_MATERIALS = item_contracts_default.heatResistantMaterials;
18144
+ var DENSITY_KG_PER_LITRE = item_contracts_default.densityKgPerLitre;
18145
+ var BODY_SLOTS2 = item_contracts_default.bodySlots;
18146
+ var SNAKE_CASE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
18147
+ var LITRES_PER_CUBIC_METRE2 = 1e3;
18148
+ var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
18149
+ var isFraction2 = (value) => isNumber(value) && value >= 0 && value <= 1;
18150
+ var catalogOf = (options) => options.items ?? GOODS_ITEMS;
18151
+ var parts = (item) => Array.isArray(item.parts) ? item.parts : [];
18152
+ var physical = (options) => {
18153
+ const range = options.density ?? DENSITY_KG_PER_LITRE;
18154
+ return {
18155
+ id: "physical",
18156
+ check: (item) => {
18157
+ const problems = [];
18158
+ const size = item.size;
18159
+ const sizeOk = Array.isArray(size) && size.length === 3 && size.every((side) => isNumber(side) && side > 0);
18160
+ if (!sizeOk) problems.push("size must be 3 finite positive numbers in metres");
18161
+ if (!isNumber(item.weight) || item.weight < 0) problems.push("weight must be a finite number >= 0 (kg)");
18162
+ if (sizeOk && isNumber(item.weight) && item.weight > 0) {
18163
+ const [width, height, depth] = size;
18164
+ const density = item.weight / (width * height * depth * LITRES_PER_CUBIC_METRE2);
18165
+ if (density < range.min || density > range.max) problems.push(`density ${Number(density.toPrecision(2))} kg/L is outside ${range.min}..${range.max}`);
18166
+ }
18167
+ return problems;
18168
+ }
18169
+ };
18170
+ };
18171
+ var destructible = (options) => {
18172
+ const materials = options.materials ? new Set(options.materials) : void 0;
18173
+ return {
18174
+ id: "destructible",
18175
+ check: (item) => {
18176
+ const list = parts(item);
18177
+ if (list.length === 0) return ["missing parts"];
18178
+ const problems = [];
18179
+ const seen = /* @__PURE__ */ new Set();
18180
+ for (const part of list) {
18181
+ if (typeof part.id !== "string" || part.id === "") problems.push("a part has no id");
18182
+ else if (seen.has(part.id)) problems.push(`duplicate part "${part.id}"`);
18183
+ else seen.add(part.id);
18184
+ if (typeof part.material !== "string" || part.material === "") problems.push(`part "${part.id}" has no material`);
18185
+ else if (materials && !materials.has(part.material)) problems.push(`part "${part.id}" has unknown material "${part.material}"`);
18186
+ if (part.thickness !== void 0 && !(isNumber(part.thickness) && part.thickness > 0)) problems.push(`part "${part.id}" thickness must be a positive number`);
18187
+ }
18188
+ if (problems.length === 0 && list.every((part) => part.shape !== void 0 && part.position !== void 0)) {
18189
+ const report = checkSpec({ parts: list }, materials ? { materials } : {});
18190
+ for (const problem of report.problems) problems.push(`spec: ${problem}`);
18191
+ }
18192
+ return problems;
18193
+ }
18194
+ };
18195
+ };
18196
+ var impact = {
18197
+ id: "impact",
18198
+ check: (item) => {
18199
+ const value = item.impact;
18200
+ if (!value) return ["missing impact"];
18201
+ const problems = [];
18202
+ if (!IMPACT_RESPONSES.includes(value.response ?? "")) problems.push(`impact.response must be one of ${IMPACT_RESPONSES.join(", ")}`);
18203
+ if (typeof value.pushImpulse !== "boolean") problems.push("impact.pushImpulse must be true or false");
18204
+ if (value.shed !== void 0 && typeof value.shed !== "boolean") problems.push("impact.shed must be true or false");
18205
+ return problems;
18206
+ }
18207
+ };
18208
+ var useAnimation = {
18209
+ id: "use_animation",
18210
+ check: (item) => {
18211
+ const problems = [];
18212
+ const clip = item.use?.clip;
18213
+ if (clip === void 0) problems.push("missing use.clip");
18214
+ else if (!SNAKE_CASE.test(clip)) problems.push(`use.clip "${clip}" must be snake_case`);
18215
+ const sounds = item.use?.sounds;
18216
+ 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)");
18217
+ return problems;
18218
+ }
18219
+ };
18220
+ var leftover = (options) => {
18221
+ const ids = new Set(catalogOf(options).map((item) => item.id));
18222
+ return {
18223
+ id: "leftover",
18224
+ check: (item) => {
18225
+ const value = item.leftover;
18226
+ if (value === void 0) return ["missing leftover"];
18227
+ if (value === "none") return [];
18228
+ const problems = [];
18229
+ if (typeof value !== "object" || value === null) return ['leftover must be an object or "none"'];
18230
+ if (typeof value.item !== "string" || value.item === "") problems.push("missing leftover.item");
18231
+ else if (!ids.has(value.item)) problems.push(`leftover.item "${value.item}" is not in the catalog`);
18232
+ if (!isNumber(value.despawnSeconds) || value.despawnSeconds < 0) problems.push("missing leftover.despawnSeconds");
18233
+ return problems;
18234
+ }
18235
+ };
18236
+ };
18237
+ var edible = {
18238
+ id: "edible",
18239
+ check: (item) => {
18240
+ const problems = [];
18241
+ const nutrition = item.nutrition;
18242
+ if (!nutrition || !(isNumber(nutrition.satiety) || isNumber(nutrition.hydration))) problems.push("missing nutrition");
18243
+ if (item.shelfLife === void 0 && item.nonPerishable !== true) problems.push("missing shelfLife (a number of days, or nonPerishable: true)");
18244
+ else if (item.shelfLife !== void 0 && !(isNumber(item.shelfLife) && item.shelfLife > 0)) problems.push("shelfLife must be a positive number of days");
18245
+ if (!isFraction2(item.rawEffect?.sickChance)) problems.push("missing rawEffect.sickChance (0..1)");
18246
+ return problems;
18247
+ }
18248
+ };
18249
+ var inputsOf = (recipe) => recipe.inputs ?? [];
18250
+ var toolsOf = (recipe) => recipe.tools ?? [];
18251
+ var consumes = (input, item) => {
18252
+ if (input.item === item.id) return true;
18253
+ if (input.any?.includes(item.id)) return true;
18254
+ if (!input.match) return false;
18255
+ const entries = Object.entries(input.match);
18256
+ return entries.length > 0 && entries.every(([key, value]) => item[key] === value);
18257
+ };
18258
+ var recipeInput = (options) => {
18259
+ const recipes = options.recipes ?? GOODS_RECIPES;
18260
+ return {
18261
+ id: "recipe_input",
18262
+ check: (item) => recipes.some((recipe) => inputsOf(recipe).some((input) => consumes(input, item))) ? [] : ["is not an input of any recipe"]
18263
+ };
18264
+ };
18265
+ var preparation = (options) => {
18266
+ const recipes = options.recipes ?? GOODS_RECIPES;
18267
+ const cookware = new Set(catalogOf(options).filter((item) => item.kind === "cookware").map((item) => item.id));
18268
+ return {
18269
+ id: "preparation",
18270
+ check: (item) => {
18271
+ const usable = recipes.filter((recipe) => inputsOf(recipe).some((input) => consumes(input, item)));
18272
+ if (usable.length === 0) return ["has no recipe that prepares it"];
18273
+ const prepares = usable.some((recipe) => {
18274
+ const heat = toolsOf(recipe).some((tool) => tool.world?.includes("fire"));
18275
+ const vessel = [...inputsOf(recipe).flatMap((input) => input.item ? [input.item] : []), ...toolsOf(recipe).flatMap((tool) => tool.anyOf ?? [])].some((id) => cookware.has(id));
18276
+ return heat && vessel;
18277
+ });
18278
+ return prepares ? [] : ["has no recipe that needs fire and a cooking vessel"];
18279
+ }
18280
+ };
18281
+ };
18282
+ var liquidContainer = {
18283
+ id: "liquid_container",
18284
+ check: (item) => {
18285
+ const fluid = item.fluid;
18286
+ if (fluid) {
18287
+ const problems = [];
18288
+ if (typeof fluid.liquid !== "string" || !SNAKE_CASE.test(fluid.liquid)) problems.push("fluid.liquid must be a liquid id");
18289
+ if (!isNumber(fluid.liters) || fluid.liters < 0) problems.push("fluid.liters must be a number >= 0");
18290
+ return problems;
18291
+ }
18292
+ return isNumber(item.container?.capacityLiters) && (item.container?.capacityLiters ?? 0) > 0 ? [] : ["missing fluid { liquid, liters } or container.capacityLiters"];
18293
+ }
18294
+ };
18295
+ var heatResistant = (options) => {
18296
+ const allowed = new Set(options.heatResistant ?? HEAT_RESISTANT_MATERIALS);
18297
+ return {
18298
+ id: "heat_resistant",
18299
+ check: (item) => parts(item).filter((part) => !allowed.has(part.material)).map((part) => `part "${part.id}" is ${part.material}, which does not take a flame`)
18300
+ };
18301
+ };
18302
+ var weapon = (options) => {
18303
+ const weaponIds = options.weaponIds ? new Set(options.weaponIds) : void 0;
18304
+ const calibers = options.calibers ? new Set(options.calibers) : void 0;
18305
+ return {
18306
+ id: "weapon",
18307
+ check: (item, ctx) => {
18308
+ if (ctx.chain.includes("ammo")) {
18309
+ if (typeof item.caliber !== "string" || item.caliber === "") return ["missing caliber"];
18310
+ return calibers && !calibers.has(item.caliber) ? [`caliber "${item.caliber}" is not in the arsenal`] : [];
18311
+ }
18312
+ if (typeof item.weapon !== "string" || item.weapon === "") return ["missing weapon (the arsenal id)"];
18313
+ return weaponIds && !weaponIds.has(item.weapon) ? [`weapon "${item.weapon}" is not in the arsenal`] : [];
18314
+ }
18315
+ };
18316
+ };
18317
+ var wearable = {
18318
+ id: "wearable",
18319
+ check: (item, ctx) => {
18320
+ const problems = [];
18321
+ const { protection, carry } = item;
18322
+ if (!protection && !carry) return [ctx.chain.includes("bag") ? "missing carry" : "missing protection"];
18323
+ if (ctx.chain.includes("bag") && !carry) problems.push("missing carry");
18324
+ if (ctx.chain.includes("clothing") && !protection) problems.push("missing protection");
18325
+ if (protection) {
18326
+ const slots = protection.slots;
18327
+ if (!Array.isArray(slots) || slots.length === 0) problems.push("protection.slots must list at least one body slot");
18328
+ else for (const slot of slots) if (!BODY_SLOTS2.includes(slot)) problems.push(`protection.slots has unknown slot "${slot}"`);
18329
+ for (const field of ["bite", "scratch", "warmth"]) if (protection[field] !== void 0 && !isFraction2(protection[field])) problems.push(`protection.${field} must be between 0 and 1`);
18330
+ }
18331
+ if (carry) {
18332
+ if (!(isNumber(carry.capacityKg) && carry.capacityKg > 0)) problems.push("carry.capacityKg must be positive");
18333
+ if (!isFraction2(carry.weightReduction)) problems.push("carry.weightReduction must be between 0 and 1");
18334
+ if (!(isNumber(carry.quickSlots) && carry.quickSlots >= 0)) problems.push("carry.quickSlots must be >= 0");
18335
+ }
18336
+ return problems;
18337
+ }
18338
+ };
18339
+ var battery = {
18340
+ id: "battery",
18341
+ check: (item) => isNumber(item.charge?.lossPerMonth) && (item.charge?.lossPerMonth ?? -1) >= 0 ? [] : ["missing charge.lossPerMonth"]
18342
+ };
18343
+ var createItemContracts = (options = {}) => [
18344
+ physical(options),
18345
+ destructible(options),
18346
+ impact,
18347
+ useAnimation,
18348
+ leftover(options),
18349
+ edible,
18350
+ preparation(options),
18351
+ recipeInput(options),
18352
+ liquidContainer,
18353
+ heatResistant(options),
18354
+ weapon(options),
18355
+ wearable,
18356
+ battery
18357
+ ];
18358
+ var ITEM_CONTRACTS = createItemContracts();
18359
+
18360
+ // src/kinds.ts
18361
+ var createGoodsKinds = (options = {}) => defineCategories(GOODS_KINDS, options);
18362
+ var lootCategoriesOf = (kinds, kind) => {
18363
+ const value = kinds.defaults(kind).lootCategory;
18364
+ if (value === void 0) return void 0;
18365
+ return Array.isArray(value) ? value : [String(value)];
18366
+ };
18367
+ var checkGoodsItems = (options = {}) => {
18368
+ const kinds = options.kinds ?? createGoodsKinds();
18369
+ const entries = options.entries ?? GOODS_ITEMS;
18370
+ const report = checkCategorized(entries, kinds, createItemContracts({ items: entries, ...options }), { allow: options.allow ?? GOODS_KNOWN_GAPS });
18371
+ const problems = [...report.problems];
18372
+ for (const entry of entries) {
18373
+ if (!kinds.has(entry.kind) || kinds.get(entry.kind)?.abstract) continue;
18374
+ const declared = lootCategoriesOf(kinds, entry.kind);
18375
+ 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`);
18376
+ }
18377
+ return { ok: problems.length === 0, problems, allowed: report.allowed };
18378
+ };
18379
+
18380
+ // src/pack.ts
18381
+ var KINDS = createGoodsKinds();
18382
+ var withSeal = (item) => {
18383
+ const kind = item.kind;
18384
+ const seal = kind && KINDS.has(kind) ? KINDS.defaults(kind).seal : void 0;
18385
+ return seal ? { ...item, seal: { ...seal, ...item.seal } } : item;
18386
+ };
18387
+ var goodsPack = {
18388
+ id: "goods",
18389
+ items: GOODS_ITEMS.map(withSeal),
18390
+ categories: GOODS_CATEGORIES,
18391
+ places: GOODS_PLACES,
18392
+ containers: GOODS_CONTAINERS,
18393
+ useEffects: GOODS_USE_EFFECTS,
18394
+ recipes: GOODS_RECIPES,
18395
+ pools: GOODS_HOUSE_POOLS.pools,
18396
+ poolAliases: { ...GOODS_HOUSE_POOLS.aliasOf, ...FLOORPLAN_POOL_ALIASES }
18397
+ };
18398
+ var createGoodsItems = (options = {}) => createItems({ ...options, packs: [goodsPack, ...options.packs ?? []] });
18399
+
18400
+ // src/data/locales/en.json
18401
+ var en_default = {
18402
+ "item.canned_beans": "Canned beans",
18403
+ "item.canned_sardines": "Canned sardines",
18404
+ "item.canned_corn_and_peas": "Canned corn and peas",
18405
+ "item.canned_sausages": "Canned sausages",
18406
+ "item.condensed_milk": "Condensed milk",
18407
+ "item.military_ration": "Army field ration",
18408
+ "item.rice": "Rice (1 kg)",
18409
+ "item.beans": "Beans (1 kg)",
18410
+ "item.pasta": "Pasta",
18411
+ "item.instant_noodles": "Instant noodles",
18412
+ "item.cassava_flour": "Cassava flour (1 kg)",
18413
+ "item.biscuits": "Biscuits",
18414
+ "item.snack_chips": "Bagged snack chips",
18415
+ "item.chocolate_bar": "Chocolate bar",
18416
+ "item.rapadura": "Rapadura (raw cane sugar block)",
18417
+ "item.honey_jar": "Jar of honey",
18418
+ "item.bread": "Bread",
18419
+ "item.cheese": "Cheese",
18420
+ "item.mortadella": "Mortadella (whole piece)",
18421
+ "item.raw_beef": "Raw beef (1 kg)",
18422
+ "item.frozen_chicken": "Frozen chicken",
18423
+ "item.eggs": "Eggs (dozen)",
18424
+ "item.banana_bunch": "Bunch of bananas",
18425
+ "item.orange": "Orange",
18426
+ "item.potatoes": "Potatoes (1 kg)",
18427
+ "item.cassava_root": "Cassava root",
18428
+ "item.fresh_fish": "Fresh fish",
18429
+ "item.salted_fish": "Salted fish",
18430
+ "item.jerked_beef": "Jerked beef",
18431
+ "item.game_meat": "Game meat (capybara, wild boar)",
18432
+ "item.home_preserves": "Home preserves (jar)",
18433
+ "item.dog_food": "Dog food (1 kg)",
18434
+ "item.sugar": "Sugar",
18435
+ "item.salt": "Salt",
18436
+ "item.cooking_oil": "Cooking oil",
18437
+ "item.coffee": "Coffee",
18438
+ "item.water_bottle": "Water bottle",
18439
+ "item.water_jug_5l": "Water jug (5 L)",
18440
+ "item.dirty_water": "River or puddle water",
18441
+ "item.soda_can": "Soda can",
18442
+ "item.juice_box": "Juice box",
18443
+ "item.milk_carton": "Milk carton",
18444
+ "item.sports_drink": "Sports drink",
18445
+ "item.energy_drink": "Energy drink (can)",
18446
+ "item.coconut_water": "Coconut water",
18447
+ "item.beer_can": "Beer (can)",
18448
+ "item.cachaca": "Cachaca (bottle)",
18449
+ "item.communion_wine": "Communion wine",
18450
+ "item.bandage": "Bandage",
18451
+ "item.sterile_gauze": "Sterile gauze",
18452
+ "item.adhesive_bandages": "Adhesive bandages (box)",
18453
+ "item.medical_tape": "Medical tape",
18454
+ "item.alcohol_70": "Alcohol 70%",
18455
+ "item.povidone_iodine": "Povidone-iodine",
18456
+ "item.saline_iv_bag": "Saline bag with IV set",
18457
+ "item.painkillers": "Painkillers",
18458
+ "item.ibuprofen": "Anti-inflammatory (ibuprofen)",
18459
+ "item.tramadol": "Strong painkiller (tramadol)",
18460
+ "item.clonazepam": "Sedative (clonazepam)",
18461
+ "item.amoxicillin": "Antibiotic (amoxicillin)",
18462
+ "item.ceftriaxone": "Injectable antibiotic (ceftriaxone)",
18463
+ "item.syringe": "Disposable syringe",
18464
+ "item.suture_kit": "Suture kit",
18465
+ "item.splint": "Immobilizing splint",
18466
+ "item.first_aid_kit": "First aid kit",
18467
+ "item.exam_gloves": "Exam gloves",
18468
+ "item.n95_mask": "N95 mask",
18469
+ "item.activated_charcoal": "Activated charcoal",
18470
+ "item.rehydration_salts": "Rehydration salts",
18471
+ "item.antihistamine": "Antihistamine",
18472
+ "item.antivenom": "Snake antivenom",
18473
+ "item.thermometer": "Thermometer",
18474
+ "item.maintenance_medication": "Daily-use medication",
18475
+ "item.multivitamin": "Multivitamin",
18476
+ "item.hammer": "Hammer",
18477
+ "item.screwdriver": "Screwdriver",
18478
+ "item.crowbar": "Crowbar",
18479
+ "item.handsaw": "Handsaw",
18480
+ "item.hacksaw": "Hacksaw",
18481
+ "item.bolt_cutters": "Bolt cutters",
18482
+ "item.pliers": "Pliers",
18483
+ "item.adjustable_wrench": "Adjustable wrench",
18484
+ "item.mechanic_toolbox": "Mechanic's toolbox",
18485
+ "item.car_jack": "Car jack and wheel wrench",
18486
+ "item.gas_torch": "Gas torch",
18487
+ "item.inverter_welder": "Inverter welder",
18488
+ "item.cordless_drill": "Cordless drill",
18489
+ "item.shovel": "Shovel",
18490
+ "item.hoe": "Hoe",
18491
+ "item.pocket_knife": "Multi-tool pocket knife",
18492
+ "item.can_opener": "Can opener",
18493
+ "item.scissors": "Scissors",
18494
+ "item.duct_tape": "Duct tape",
18495
+ "item.pot": "Pot",
18496
+ "item.camp_stove": "Camping stove",
18497
+ "item.sewing_kit": "Sewing kit",
18498
+ "item.lockpick_kit": "Locksmith kit (lock picks)",
18499
+ "item.lighter": "Lighter",
18500
+ "item.matches": "Matches",
18501
+ "item.flint_striker": "Flint and striker",
18502
+ "item.bucket": "Bucket",
18503
+ "item.binoculars": "Binoculars",
18504
+ "item.fire_extinguisher": "Dry-powder fire extinguisher",
18505
+ "item.sharpening_stone": "Sharpening stone",
18506
+ "item.knife": "Kitchen knife",
18507
+ "item.tactical_knife": "Tactical knife",
18508
+ "item.machete": "Machete",
18509
+ "item.meat_cleaver": "Butcher's cleaver",
18510
+ "item.hatchet": "Hatchet",
18511
+ "item.axe": "Axe",
18512
+ "item.baseball_bat": "Baseball bat",
18513
+ "item.nail_bat": "Nail bat",
18514
+ "item.iron_pipe": "Iron pipe",
18515
+ "item.sledgehammer": "Sledgehammer",
18516
+ "item.pickaxe": "Pickaxe",
18517
+ "item.sickle": "Sickle",
18518
+ "item.police_baton": "Police baton",
18519
+ "item.cast_iron_skillet": "Cast iron skillet",
18520
+ "item.improvised_spear": "Improvised spear",
18521
+ "item.chainsaw": "Chainsaw",
18522
+ "item.slingshot": "Slingshot",
18523
+ "item.recurve_bow": "Recurve bow",
18524
+ "item.crossbow": "Crossbow",
18525
+ "item.arrows_and_bolts": "Arrows and bolts (6)",
18526
+ "item.wooden_plank": "Wooden plank",
18527
+ "item.nails": "Nails (box of 100)",
18528
+ "item.screws": "Screws (box of 50)",
18529
+ "item.sheet_metal": "Sheet metal",
18530
+ "item.scrap_metal": "Scrap metal",
18531
+ "item.wire_roll": "Wire (10 m roll)",
18532
+ "item.barbed_wire": "Barbed wire (25 m roll)",
18533
+ "item.rope": "Rope",
18534
+ "item.tarp": "Tarp (4 x 4 m)",
18535
+ "item.raffia_sack": "Raffia sack",
18536
+ "item.bricks": "Bricks (10)",
18537
+ "item.cement_bag": "Cement (25 kg bag)",
18538
+ "item.rag": "Rag",
18539
+ "item.glass_bottle": "Empty glass bottle",
18540
+ "item.pet_bottle": "Empty PET bottle",
18541
+ "item.empty_can": "Empty can",
18542
+ "item.glass_shards": "Glass shards",
18543
+ "item.epoxy_glue": "Epoxy glue",
18544
+ "item.electronic_components": "Electronic components",
18545
+ "item.copper_wire": "Copper wire (5 m)",
18546
+ "item.firewood": "Firewood",
18547
+ "item.rebar": "Rebar",
18548
+ "item.lubricant_spray": "Spray lubricant oil",
18549
+ "item.tshirt": "T-shirt",
18550
+ "item.hoodie": "Hoodie",
18551
+ "item.denim_jacket": "Denim jacket",
18552
+ "item.leather_motorcycle_jacket": "Motorcycle jacket (leather)",
18553
+ "item.jeans": "Jeans",
18554
+ "item.leather_motorcycle_pants": "Motorcycle pants",
18555
+ "item.army_camouflage_uniform": "Army camouflage uniform",
18556
+ "item.mechanic_coveralls": "Mechanic's coveralls",
18557
+ "item.firefighter_suit": "Firefighter suit",
18558
+ "item.beekeeper_suit": "Beekeeper suit",
18559
+ "item.raincoat": "Raincoat",
18560
+ "item.padded_coat": "Padded coat",
18561
+ "item.motorcycle_helmet": "Full-face motorcycle helmet",
18562
+ "item.hard_hat": "Hard hat (PPE)",
18563
+ "item.safety_goggles": "Safety goggles",
18564
+ "item.leather_work_gloves": "Leather work gloves",
18565
+ "item.combat_boots": "Combat boots",
18566
+ "item.rubber_boots": "Rubber boots",
18567
+ "item.sneakers": "Sneakers",
18568
+ "item.flip_flops": "Flip-flops",
18569
+ "item.magazine_arm_guard": "Magazine arm guard",
18570
+ "item.sheet_metal_forearm_guard": "Sheet metal forearm guard",
18571
+ "item.ballistic_vest": "Ballistic vests (II-A, III-A, III)",
18572
+ "item.shopping_bag": "Shopping bag",
18573
+ "item.trash_bag": "Trash bag",
18574
+ "item.fanny_pack": "Fanny pack",
18575
+ "item.crossbody_bag": "Crossbody bag",
18576
+ "item.backpack": "Backpack",
18577
+ "item.delivery_bag": "Delivery bag",
18578
+ "item.hiking_backpack_40l": "Hiking backpack 40 L",
18579
+ "item.military_backpack": "Military backpack",
18580
+ "item.trekking_backpack_70l": "Trekking backpack 70 L",
18581
+ "item.rolling_suitcase": "Rolling suitcase",
18582
+ "item.tactical_vest": "Tactical vest",
18583
+ "item.tool_belt": "Tool belt",
18584
+ "item.cooler_box": "Cooler box",
18585
+ "item.shopping_cart": "Shopping cart",
18586
+ "item.wheelbarrow": "Wheelbarrow",
18587
+ "item.flashlight": "Flashlight",
18256
18588
  "item.headlamp": "Headlamp",
18257
18589
  "item.batteries": "Batteries",
18258
18590
  "item.battery_9v": "9 V battery",
@@ -19089,253 +19421,6 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19089
19421
  var GOODS_STRINGS = { en: en_default, "pt-BR": pt_BR_default };
19090
19422
  var pickGoodsLocale = (locale) => locale?.toLowerCase().startsWith("pt") ? "pt-BR" : "en";
19091
19423
  var goodsText = (key, locale) => GOODS_STRINGS[pickGoodsLocale(locale)][key] ?? key;
19092
-
19093
- // src/data/item-contracts.json
19094
- var item_contracts_default = {
19095
- densityKgPerLitre: { min: 0.02, max: 22 },
19096
- impactResponses: ["dent", "break", "shatter", "tear", "squash", "pierce", "none"],
19097
- heatResistantMaterials: ["steel", "cast_iron", "aluminium", "copper", "ceramic", "tempered_glass", "stone", "brick"],
19098
- bodySlots: ["head", "eyes", "torso", "arms", "hands", "legs", "feet", "over"]
19099
- };
19100
-
19101
- // src/contracts.ts
19102
- var IMPACT_RESPONSES = item_contracts_default.impactResponses;
19103
- var HEAT_RESISTANT_MATERIALS = item_contracts_default.heatResistantMaterials;
19104
- var DENSITY_KG_PER_LITRE = item_contracts_default.densityKgPerLitre;
19105
- var BODY_SLOTS2 = item_contracts_default.bodySlots;
19106
- var SNAKE_CASE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
19107
- var LITRES_PER_CUBIC_METRE2 = 1e3;
19108
- var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
19109
- var isFraction2 = (value) => isNumber(value) && value >= 0 && value <= 1;
19110
- var catalogOf = (options) => options.items ?? GOODS_ITEMS;
19111
- var parts = (item) => Array.isArray(item.parts) ? item.parts : [];
19112
- var physical = (options) => {
19113
- const range = options.density ?? DENSITY_KG_PER_LITRE;
19114
- return {
19115
- id: "physical",
19116
- check: (item) => {
19117
- const problems = [];
19118
- const size = item.size;
19119
- const sizeOk = Array.isArray(size) && size.length === 3 && size.every((side) => isNumber(side) && side > 0);
19120
- if (!sizeOk) problems.push("size must be 3 finite positive numbers in metres");
19121
- if (!isNumber(item.weight) || item.weight < 0) problems.push("weight must be a finite number >= 0 (kg)");
19122
- if (sizeOk && isNumber(item.weight) && item.weight > 0) {
19123
- const [width, height, depth] = size;
19124
- const density = item.weight / (width * height * depth * LITRES_PER_CUBIC_METRE2);
19125
- if (density < range.min || density > range.max) problems.push(`density ${Number(density.toPrecision(2))} kg/L is outside ${range.min}..${range.max}`);
19126
- }
19127
- return problems;
19128
- }
19129
- };
19130
- };
19131
- var destructible = (options) => {
19132
- const materials = options.materials ? new Set(options.materials) : void 0;
19133
- return {
19134
- id: "destructible",
19135
- check: (item) => {
19136
- const list = parts(item);
19137
- if (list.length === 0) return ["missing parts"];
19138
- const problems = [];
19139
- const seen = /* @__PURE__ */ new Set();
19140
- for (const part of list) {
19141
- if (typeof part.id !== "string" || part.id === "") problems.push("a part has no id");
19142
- else if (seen.has(part.id)) problems.push(`duplicate part "${part.id}"`);
19143
- else seen.add(part.id);
19144
- if (typeof part.material !== "string" || part.material === "") problems.push(`part "${part.id}" has no material`);
19145
- else if (materials && !materials.has(part.material)) problems.push(`part "${part.id}" has unknown material "${part.material}"`);
19146
- if (part.thickness !== void 0 && !(isNumber(part.thickness) && part.thickness > 0)) problems.push(`part "${part.id}" thickness must be a positive number`);
19147
- }
19148
- if (problems.length === 0 && list.every((part) => part.shape !== void 0 && part.position !== void 0)) {
19149
- const report = checkSpec({ parts: list }, materials ? { materials } : {});
19150
- for (const problem of report.problems) problems.push(`spec: ${problem}`);
19151
- }
19152
- return problems;
19153
- }
19154
- };
19155
- };
19156
- var impact = {
19157
- id: "impact",
19158
- check: (item) => {
19159
- const value = item.impact;
19160
- if (!value) return ["missing impact"];
19161
- const problems = [];
19162
- if (!IMPACT_RESPONSES.includes(value.response ?? "")) problems.push(`impact.response must be one of ${IMPACT_RESPONSES.join(", ")}`);
19163
- if (typeof value.pushImpulse !== "boolean") problems.push("impact.pushImpulse must be true or false");
19164
- if (value.shed !== void 0 && typeof value.shed !== "boolean") problems.push("impact.shed must be true or false");
19165
- return problems;
19166
- }
19167
- };
19168
- var useAnimation = {
19169
- id: "use_animation",
19170
- check: (item) => {
19171
- const problems = [];
19172
- const clip = item.use?.clip;
19173
- if (clip === void 0) problems.push("missing use.clip");
19174
- else if (!SNAKE_CASE.test(clip)) problems.push(`use.clip "${clip}" must be snake_case`);
19175
- const sounds = item.use?.sounds;
19176
- 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)");
19177
- return problems;
19178
- }
19179
- };
19180
- var leftover = (options) => {
19181
- const ids = new Set(catalogOf(options).map((item) => item.id));
19182
- return {
19183
- id: "leftover",
19184
- check: (item) => {
19185
- const value = item.leftover;
19186
- if (value === void 0) return ["missing leftover"];
19187
- if (value === "none") return [];
19188
- const problems = [];
19189
- if (typeof value !== "object" || value === null) return ['leftover must be an object or "none"'];
19190
- if (typeof value.item !== "string" || value.item === "") problems.push("missing leftover.item");
19191
- else if (!ids.has(value.item)) problems.push(`leftover.item "${value.item}" is not in the catalog`);
19192
- if (!isNumber(value.despawnSeconds) || value.despawnSeconds < 0) problems.push("missing leftover.despawnSeconds");
19193
- return problems;
19194
- }
19195
- };
19196
- };
19197
- var edible = {
19198
- id: "edible",
19199
- check: (item) => {
19200
- const problems = [];
19201
- const nutrition = item.nutrition;
19202
- if (!nutrition || !(isNumber(nutrition.satiety) || isNumber(nutrition.hydration))) problems.push("missing nutrition");
19203
- if (item.shelfLife === void 0 && item.nonPerishable !== true) problems.push("missing shelfLife (a number of days, or nonPerishable: true)");
19204
- else if (item.shelfLife !== void 0 && !(isNumber(item.shelfLife) && item.shelfLife > 0)) problems.push("shelfLife must be a positive number of days");
19205
- if (!isFraction2(item.rawEffect?.sickChance)) problems.push("missing rawEffect.sickChance (0..1)");
19206
- return problems;
19207
- }
19208
- };
19209
- var inputsOf = (recipe) => recipe.inputs ?? [];
19210
- var toolsOf = (recipe) => recipe.tools ?? [];
19211
- var consumes = (input, item) => {
19212
- if (input.item === item.id) return true;
19213
- if (input.any?.includes(item.id)) return true;
19214
- if (!input.match) return false;
19215
- const entries = Object.entries(input.match);
19216
- return entries.length > 0 && entries.every(([key, value]) => item[key] === value);
19217
- };
19218
- var recipeInput = (options) => {
19219
- const recipes = options.recipes ?? GOODS_RECIPES;
19220
- return {
19221
- id: "recipe_input",
19222
- check: (item) => recipes.some((recipe) => inputsOf(recipe).some((input) => consumes(input, item))) ? [] : ["is not an input of any recipe"]
19223
- };
19224
- };
19225
- var preparation = (options) => {
19226
- const recipes = options.recipes ?? GOODS_RECIPES;
19227
- const cookware = new Set(catalogOf(options).filter((item) => item.kind === "cookware").map((item) => item.id));
19228
- return {
19229
- id: "preparation",
19230
- check: (item) => {
19231
- const usable = recipes.filter((recipe) => inputsOf(recipe).some((input) => consumes(input, item)));
19232
- if (usable.length === 0) return ["has no recipe that prepares it"];
19233
- const prepares = usable.some((recipe) => {
19234
- const heat = toolsOf(recipe).some((tool) => tool.world?.includes("fire"));
19235
- const vessel = [...inputsOf(recipe).flatMap((input) => input.item ? [input.item] : []), ...toolsOf(recipe).flatMap((tool) => tool.anyOf ?? [])].some((id) => cookware.has(id));
19236
- return heat && vessel;
19237
- });
19238
- return prepares ? [] : ["has no recipe that needs fire and a cooking vessel"];
19239
- }
19240
- };
19241
- };
19242
- var liquidContainer = {
19243
- id: "liquid_container",
19244
- check: (item) => {
19245
- const fluid = item.fluid;
19246
- if (fluid) {
19247
- const problems = [];
19248
- if (typeof fluid.liquid !== "string" || !SNAKE_CASE.test(fluid.liquid)) problems.push("fluid.liquid must be a liquid id");
19249
- if (!isNumber(fluid.liters) || fluid.liters < 0) problems.push("fluid.liters must be a number >= 0");
19250
- return problems;
19251
- }
19252
- return isNumber(item.container?.capacityLiters) && (item.container?.capacityLiters ?? 0) > 0 ? [] : ["missing fluid { liquid, liters } or container.capacityLiters"];
19253
- }
19254
- };
19255
- var heatResistant = (options) => {
19256
- const allowed = new Set(options.heatResistant ?? HEAT_RESISTANT_MATERIALS);
19257
- return {
19258
- id: "heat_resistant",
19259
- check: (item) => parts(item).filter((part) => !allowed.has(part.material)).map((part) => `part "${part.id}" is ${part.material}, which does not take a flame`)
19260
- };
19261
- };
19262
- var weapon = (options) => {
19263
- const weaponIds = options.weaponIds ? new Set(options.weaponIds) : void 0;
19264
- const calibers = options.calibers ? new Set(options.calibers) : void 0;
19265
- return {
19266
- id: "weapon",
19267
- check: (item, ctx) => {
19268
- if (ctx.chain.includes("ammo")) {
19269
- if (typeof item.caliber !== "string" || item.caliber === "") return ["missing caliber"];
19270
- return calibers && !calibers.has(item.caliber) ? [`caliber "${item.caliber}" is not in the arsenal`] : [];
19271
- }
19272
- if (typeof item.weapon !== "string" || item.weapon === "") return ["missing weapon (the arsenal id)"];
19273
- return weaponIds && !weaponIds.has(item.weapon) ? [`weapon "${item.weapon}" is not in the arsenal`] : [];
19274
- }
19275
- };
19276
- };
19277
- var wearable = {
19278
- id: "wearable",
19279
- check: (item, ctx) => {
19280
- const problems = [];
19281
- const { protection, carry } = item;
19282
- if (!protection && !carry) return [ctx.chain.includes("bag") ? "missing carry" : "missing protection"];
19283
- if (ctx.chain.includes("bag") && !carry) problems.push("missing carry");
19284
- if (ctx.chain.includes("clothing") && !protection) problems.push("missing protection");
19285
- if (protection) {
19286
- const slots = protection.slots;
19287
- if (!Array.isArray(slots) || slots.length === 0) problems.push("protection.slots must list at least one body slot");
19288
- else for (const slot of slots) if (!BODY_SLOTS2.includes(slot)) problems.push(`protection.slots has unknown slot "${slot}"`);
19289
- for (const field of ["bite", "scratch", "warmth"]) if (protection[field] !== void 0 && !isFraction2(protection[field])) problems.push(`protection.${field} must be between 0 and 1`);
19290
- }
19291
- if (carry) {
19292
- if (!(isNumber(carry.capacityKg) && carry.capacityKg > 0)) problems.push("carry.capacityKg must be positive");
19293
- if (!isFraction2(carry.weightReduction)) problems.push("carry.weightReduction must be between 0 and 1");
19294
- if (!(isNumber(carry.quickSlots) && carry.quickSlots >= 0)) problems.push("carry.quickSlots must be >= 0");
19295
- }
19296
- return problems;
19297
- }
19298
- };
19299
- var battery = {
19300
- id: "battery",
19301
- check: (item) => isNumber(item.charge?.lossPerMonth) && (item.charge?.lossPerMonth ?? -1) >= 0 ? [] : ["missing charge.lossPerMonth"]
19302
- };
19303
- var createItemContracts = (options = {}) => [
19304
- physical(options),
19305
- destructible(options),
19306
- impact,
19307
- useAnimation,
19308
- leftover(options),
19309
- edible,
19310
- preparation(options),
19311
- recipeInput(options),
19312
- liquidContainer,
19313
- heatResistant(options),
19314
- weapon(options),
19315
- wearable,
19316
- battery
19317
- ];
19318
- var ITEM_CONTRACTS = createItemContracts();
19319
-
19320
- // src/kinds.ts
19321
- var createGoodsKinds = (options = {}) => defineCategories(GOODS_KINDS, options);
19322
- var lootCategoriesOf = (kinds, kind) => {
19323
- const value = kinds.defaults(kind).lootCategory;
19324
- if (value === void 0) return void 0;
19325
- return Array.isArray(value) ? value : [String(value)];
19326
- };
19327
- var checkGoodsItems = (options = {}) => {
19328
- const kinds = options.kinds ?? createGoodsKinds();
19329
- const entries = options.entries ?? GOODS_ITEMS;
19330
- const report = checkCategorized(entries, kinds, createItemContracts({ items: entries, ...options }), { allow: options.allow ?? GOODS_KNOWN_GAPS });
19331
- const problems = [...report.problems];
19332
- for (const entry of entries) {
19333
- if (!kinds.has(entry.kind) || kinds.get(entry.kind)?.abstract) continue;
19334
- const declared = lootCategoriesOf(kinds, entry.kind);
19335
- 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`);
19336
- }
19337
- return { ok: problems.length === 0, problems, allowed: report.allowed };
19338
- };
19339
19424
  return __toCommonJS(index_exports);
19340
19425
  })();
19341
19426
  //# sourceMappingURL=index.global.js.map