docs+tool: locate & extract world factory production-line recipes
The fixed single-recipe world structures (conveyors, e.g. 1 Raw Aurogen Crystal -> 10 Energy Rods) are a separate mechanic from workbench recipes. Document where they live and how to read them, with a reproducible extractor. - docs/PRODUCTION_LINES.md: the 'where to find it' record — ECS classes (ProductionLineRecipeComponent -> CraftingRecipe), the epb_assets_all game_conveyor_*_epb prefabs, EntityBlueprint/Odin serialization, the extraction path, and the open energy_grid_*/island-placement lead. - bundle/extract_production_lines.py: UnityPy + odin_read extractor -> extracted/production_lines.json (14 conveyor recipes). - BUNDLES.md: add production_lines.json to the data-source map.
This commit is contained in:
@@ -11,7 +11,8 @@ Read the bundles in Python with `bundle/extract_data.py` / `loot_probe.py` (Unit
|
|||||||
| `extracted/` output | Source bundle | Asset |
|
| `extracted/` output | Source bundle | Asset |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `items_registry.json` / `item_definitions.json` | `configuration` | `CheatItemDefinitionsData.asset` |
|
| `items_registry.json` / `item_definitions.json` | `configuration` | `CheatItemDefinitionsData.asset` |
|
||||||
| `crafting_recipes.json` | `craftingrecipes` | `Recipes_*` |
|
| `crafting_recipes.json` | `craftingrecipes` | `Recipes_*` (workbench) |
|
||||||
|
| `production_lines.json` | `epb` | `game_conveyor_*_epb` (world factories) — see [docs/PRODUCTION_LINES.md](docs/PRODUCTION_LINES.md) |
|
||||||
| `loot_tables.json` | `configuration` | `conf_worldLootTables{Storm,Voyage}Config` (Odin-binary) |
|
| `loot_tables.json` | `configuration` | `conf_worldLootTables{Storm,Voyage}Config` (Odin-binary) |
|
||||||
| `CompartmentsDatabase.json` | `walkershared` | `CompartmentsDatabase.json` (TextAsset) |
|
| `CompartmentsDatabase.json` | `walkershared` | `CompartmentsDatabase.json` (TextAsset) |
|
||||||
| `epb_catalog.json` | `epb` | `*_epb.prefab` |
|
| `epb_catalog.json` | `epb` | `*_epb.prefab` |
|
||||||
|
|||||||
127
bundle/extract_production_lines.py
Normal file
127
bundle/extract_production_lines.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract SAND world 'Factory Production Line' (conveyor) recipes.
|
||||||
|
|
||||||
|
These are the fixed single-recipe world structures (e.g. 1 Raw Aurogen Crystal ->
|
||||||
|
10 Energy Rods), DISTINCT from the player workbench recipes in
|
||||||
|
extracted/crafting_recipes.json. Same data shape, different home:
|
||||||
|
|
||||||
|
Mechanic .... a "Factory Production Line": an item conveyor that runs ONE recipe.
|
||||||
|
ECS ......... Hologryph.Sand.Shared.Game.Features.Crafting
|
||||||
|
ProductionLineComponent (input/output entity indices)
|
||||||
|
ProductionLineRecipeComponent -> CraftingRecipe recipe
|
||||||
|
ProductionLine{Input,Output}DataComponent (isLargeItem flags)
|
||||||
|
CraftingRecipe = { CraftingIngredient[] inputIngredients,
|
||||||
|
CraftingIngredient[] outputIngredients,
|
||||||
|
float craftingTimeSeconds }
|
||||||
|
CraftingIngredient = { string itemId, int amount }
|
||||||
|
Where ....... epb_assets_all.bundle, prefabs named game_conveyor_*_epb.prefab
|
||||||
|
(Assets/Content/Game/game_conveyor/...). Each prefab GameObject has
|
||||||
|
an EntityBlueprint (Sirenix Odin SerializedMonoBehaviour) component;
|
||||||
|
the recipe lives in its Odin-serialized `serializationData`.
|
||||||
|
Islands ..... energy_grid_*_epb.prefab (Wunderinsel, Kaiserplatz, DeusExMachine,
|
||||||
|
LittleFactory1-3, LittleFactoryArmory*) place/group conveyors per
|
||||||
|
location. They reference conveyors by entity ref, NOT item-id string,
|
||||||
|
so recipe->island mapping needs the island prefab placements
|
||||||
|
(islands_assets_all.bundle) -- not done here.
|
||||||
|
|
||||||
|
Extraction path: UnityPy (typetree via IL2CPP generator) reads each EntityBlueprint
|
||||||
|
MonoBehaviour -> serializationData.SerializedBytes -> odin_read.parse -> walk for
|
||||||
|
the CraftingRecipe node. Mirrors extract_loot.py (loot tables are Odin too).
|
||||||
|
|
||||||
|
Writes extracted/production_lines.json.
|
||||||
|
"""
|
||||||
|
import os, sys, json, UnityPy
|
||||||
|
from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import odin_read
|
||||||
|
|
||||||
|
GAME = "/mnt/d/SteamLibrary/steamapps/common/Sand Playtest"
|
||||||
|
META = os.path.join(GAME, "Sand_Data/il2cpp_data/Metadata/global-metadata.dat")
|
||||||
|
DLL = os.path.join(GAME, "GameAssembly.dll")
|
||||||
|
BUNDLES = "/home/downloadpizza/sand_tools/bundles" # symlink -> StreamingAssets/aa/...
|
||||||
|
OUT = "/home/downloadpizza/sand_tools/extracted/production_lines.json"
|
||||||
|
UNITY = "6000.0.40f1"
|
||||||
|
|
||||||
|
|
||||||
|
def recipes_in(node, out):
|
||||||
|
"""Collect every CraftingRecipe-shaped dict in an odin_read tree."""
|
||||||
|
if isinstance(node, dict):
|
||||||
|
if "inputIngredients" in node or "outputIngredients" in node:
|
||||||
|
out.append(node)
|
||||||
|
for v in node.values():
|
||||||
|
recipes_in(v, out)
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for v in node:
|
||||||
|
recipes_in(v, out)
|
||||||
|
|
||||||
|
|
||||||
|
def ingredients(arr):
|
||||||
|
"""Flatten an Odin CraftingIngredient[] (possibly nested in $items) -> [(itemId, amount)]."""
|
||||||
|
if isinstance(arr, dict):
|
||||||
|
arr = arr.get("$items", arr)
|
||||||
|
out = []
|
||||||
|
for x in (arr or []):
|
||||||
|
rows = x if isinstance(x, list) else [x]
|
||||||
|
for y in rows:
|
||||||
|
if isinstance(y, dict) and "itemId" in y:
|
||||||
|
out.append((y.get("itemId"), y.get("amount")))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def entity_blueprint(go):
|
||||||
|
"""Return the EntityBlueprint MonoBehaviour object_reader on a prefab GameObject, or None."""
|
||||||
|
for c in go.m_Components:
|
||||||
|
co = c.read()
|
||||||
|
r = co.object_reader
|
||||||
|
if r.type.name == "MonoBehaviour" and co.m_Script.read().m_ClassName == "EntityBlueprint":
|
||||||
|
return r
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
gen = TypeTreeGenerator(UNITY)
|
||||||
|
gen.load_il2cpp(open(DLL, "rb").read(), open(META, "rb").read())
|
||||||
|
env = UnityPy.load(os.path.join(BUNDLES, "epb_assets_all.bundle"),
|
||||||
|
os.path.join(BUNDLES, "sand_monoscripts.bundle"))
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for path, obj in env.container.items():
|
||||||
|
base = path.split("/")[-1].replace("_epb.prefab", "")
|
||||||
|
if not base.startswith("game_conveyor_") or obj.type.name != "GameObject":
|
||||||
|
continue
|
||||||
|
eb = entity_blueprint(obj.read())
|
||||||
|
if eb is None:
|
||||||
|
continue
|
||||||
|
script = eb.read().m_Script.read()
|
||||||
|
full = (script.m_Namespace + "." if script.m_Namespace else "") + script.m_ClassName
|
||||||
|
nodes = json.loads(gen.get_nodes_as_json(script.m_AssemblyName, full))
|
||||||
|
sb = eb.read_typetree(nodes).get("serializationData", {}).get("SerializedBytes")
|
||||||
|
if not sb:
|
||||||
|
continue
|
||||||
|
parsed = odin_read.parse(bytes(sb))
|
||||||
|
found = []
|
||||||
|
recipes_in(parsed["roots"], found)
|
||||||
|
recipes_in(parsed["items"], found)
|
||||||
|
for r in found:
|
||||||
|
result[base] = {
|
||||||
|
"inputs": [{"itemId": i, "amount": a} for i, a in ingredients(r.get("inputIngredients"))],
|
||||||
|
"outputs": [{"itemId": i, "amount": a} for i, a in ingredients(r.get("outputIngredients"))],
|
||||||
|
"craftTimeSeconds": r.get("craftingTimeSeconds"),
|
||||||
|
}
|
||||||
|
|
||||||
|
out = {
|
||||||
|
"_source": "epb_assets_all.bundle game_conveyor_*_epb EntityBlueprint "
|
||||||
|
"(Odin) -> ProductionLineRecipeComponent.recipe (CraftingRecipe)",
|
||||||
|
"_count": len(result),
|
||||||
|
"production_lines": dict(sorted(result.items())),
|
||||||
|
}
|
||||||
|
json.dump(out, open(OUT, "w"), indent=1)
|
||||||
|
print(f"wrote {OUT} ({len(result)} conveyor recipes)")
|
||||||
|
for k, r in out["production_lines"].items():
|
||||||
|
inp = " + ".join(f"{i['amount']}x {i['itemId']}" for i in r["inputs"]) or "?"
|
||||||
|
outp = " + ".join(f"{i['amount']}x {i['itemId']}" for i in r["outputs"]) or "?"
|
||||||
|
print(f" {k.replace('game_conveyor_',''):24s} {inp:48s} -> {outp} ({r['craftTimeSeconds']}s)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
70
docs/PRODUCTION_LINES.md
Normal file
70
docs/PRODUCTION_LINES.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# World factory "production lines" (conveyors) — where to find them
|
||||||
|
|
||||||
|
The game has **fixed single-recipe world structures** — item conveyors that run *one*
|
||||||
|
recipe (e.g. **1 Raw Aurogen Crystal → 10 Energy Rods**). These are a separate mechanic
|
||||||
|
from the player **workbench** recipes (which live in `extracted/crafting_recipes.json`,
|
||||||
|
sourced from `CraftingRecipeBundle` assets). This note records **where they live** so the
|
||||||
|
data can be re-derived after a game update.
|
||||||
|
|
||||||
|
## The mechanic
|
||||||
|
|
||||||
|
In code it's a *"Factory Production Line"*. It's an Entitas ECS feature, namespace
|
||||||
|
`Hologryph.Sand.Shared.Game.Features.Crafting` (see `il2cpp/dump.cs`):
|
||||||
|
|
||||||
|
| Class | Holds |
|
||||||
|
|---|---|
|
||||||
|
| `ProductionLineComponent` | `int inputEntityIndex`, `int outputEntityIndex` |
|
||||||
|
| `ProductionLineRecipeComponent` | `CraftingRecipe recipe` ← the recipe |
|
||||||
|
| `ProductionLineInputDataComponent` / `…OutputDataComponent` | `bool isLargeItem{Input,Output}` |
|
||||||
|
| `CraftingRecipe` | `CraftingIngredient[] inputIngredients`, `CraftingIngredient[] outputIngredients`, `float craftingTimeSeconds` |
|
||||||
|
| `CraftingIngredient` | `string itemId`, `int amount` |
|
||||||
|
|
||||||
|
Note `CraftingRecipe` / `CraftingIngredient` are the **same types** the workbench uses —
|
||||||
|
only the container differs (a workbench holds `List<CraftingRecipeBundle> recipeBundles`;
|
||||||
|
a production line holds a single `CraftingRecipe` on the entity).
|
||||||
|
|
||||||
|
## Where the data lives
|
||||||
|
|
||||||
|
- **Bundle:** `epb_assets_all.bundle` (reachable via the `bundles/` symlink).
|
||||||
|
- **Prefabs:** `game_conveyor_*_epb.prefab`, under
|
||||||
|
`Assets/Content/Game/game_conveyor/...`. 14 carry a real recipe; the
|
||||||
|
`game_conveyor_base*` ones are template/test conversions (cf. `TestRecipesBundle`).
|
||||||
|
- **Serialization:** each prefab GameObject (`m_Name = game_conveyor_<x>_epb`) has two
|
||||||
|
components — a `Transform` and a `MonoBehaviour` whose script is **`EntityBlueprint`**.
|
||||||
|
`EntityBlueprint : SerializedMonoBehaviour` (Sirenix Odin), so the component data —
|
||||||
|
including the `CraftingRecipe` — is in its **Odin-serialized
|
||||||
|
`serializationData.SerializedBytes`**, not in plain typetree fields. (Same situation as
|
||||||
|
the loot tables — see [TASK.md](TASK.md) / `extract_loot.py`.)
|
||||||
|
|
||||||
|
### Extraction path (how to read it)
|
||||||
|
|
||||||
|
`UnityPy` (with an IL2CPP `TypeTreeGenerator` over `GameAssembly.dll` +
|
||||||
|
`global-metadata.dat`) reads the `EntityBlueprint` MonoBehaviour → pull
|
||||||
|
`serializationData.SerializedBytes` → `bundle/odin_read.py` parses the Odin binary →
|
||||||
|
walk the tree for the node with `inputIngredients` / `outputIngredients`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
venv/bin/python bundle/extract_production_lines.py # -> extracted/production_lines.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Key item ids seen here: `item_crystalHandles` = *Raw Aurogen Crystal*,
|
||||||
|
`item_energyBar` = *NZ Mk2 Energy Rod*, `item_blackBox` = *Black Box*,
|
||||||
|
`item_resourceCoralPiece` = *Coral Chunk*. Resolve any id → display name via
|
||||||
|
`extracted/items_registry.json` / `extracted/item_names.json` (built from
|
||||||
|
`CheatItemDefinitionsData`).
|
||||||
|
|
||||||
|
## Island placement — open lead
|
||||||
|
|
||||||
|
Which factory/island runs which conveyor is grouped by the
|
||||||
|
`energy_grid_*_epb.prefab` blueprints in the same bundle:
|
||||||
|
`Wunderinsel`, `Kaiserplatz`, `DeusExMachine`, `Factorio`, `LittleFactory1-3`,
|
||||||
|
`LittleFactoryArmory1-3`, `TestFactory`. The **`…Armory`** groups are the likely
|
||||||
|
**Sprengstofffabrik** (explosives factory) host for the explosive / grenade / rocket /
|
||||||
|
cannon conveyors.
|
||||||
|
|
||||||
|
**Not yet resolved:** an `energy_grid_*` blueprint's Odin data references its conveyors by
|
||||||
|
**entity reference, not item-id string** (a string scan of its serialized bytes finds no
|
||||||
|
`game_conveyor_*` / `item_*` ids). So mapping recipe → specific island needs the **island
|
||||||
|
prefab placements** in `islands_assets_all.bundle` (the `island_*` / fort / `loc_event_*`
|
||||||
|
prefabs, where `Sprengstofffabrik` is an i2 `Toponyms/` name), not the conveyor or
|
||||||
|
energy_grid EPBs alone. That linkage is the next step if island attribution is wanted.
|
||||||
260
extracted/production_lines.json
Normal file
260
extracted/production_lines.json
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
{
|
||||||
|
"_source": "epb_assets_all.bundle game_conveyor_*_epb EntityBlueprint (Odin) -> ProductionLineRecipeComponent.recipe (CraftingRecipe)",
|
||||||
|
"_count": 14,
|
||||||
|
"production_lines": {
|
||||||
|
"game_conveyor_40mmT3Cannon": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_alloySteel",
|
||||||
|
"amount": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t2",
|
||||||
|
"amount": 300
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "game_packedAutoTurretT3Container",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 55.0
|
||||||
|
},
|
||||||
|
"game_conveyor_70mmT3Cannon": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_alloySteel",
|
||||||
|
"amount": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t2",
|
||||||
|
"amount": 300
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "game_packedShotgunTurretT3Container",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 55.0
|
||||||
|
},
|
||||||
|
"game_conveyor_80mmT3Cannon": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_alloySteel",
|
||||||
|
"amount": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t2",
|
||||||
|
"amount": 300
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "game_packedTurretT3Container",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 55.0
|
||||||
|
},
|
||||||
|
"game_conveyor_armorPiercingRockets": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceFabric",
|
||||||
|
"amount": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceGunpowder",
|
||||||
|
"amount": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_rocketLauncherAmmoArmorPiercing",
|
||||||
|
"amount": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 5.0
|
||||||
|
},
|
||||||
|
"game_conveyor_baseInventoryToInventory": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_c4Dynamite",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 5.0
|
||||||
|
},
|
||||||
|
"game_conveyor_baseInventoryToLarge": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_c4Dynamite",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_explosiveBig",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 15.0
|
||||||
|
},
|
||||||
|
"game_conveyor_baseLargeToInventory": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_wineBox",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_coinCrown",
|
||||||
|
"amount": 1000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 15.0
|
||||||
|
},
|
||||||
|
"game_conveyor_baseLargeToLarge": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_wineBox",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_explosiveBig",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 15.0
|
||||||
|
},
|
||||||
|
"game_conveyor_computingModules": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_blackBox",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t3",
|
||||||
|
"amount": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 7.0
|
||||||
|
},
|
||||||
|
"game_conveyor_contactGrenades": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceFabric",
|
||||||
|
"amount": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceGunpowder",
|
||||||
|
"amount": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_grenadeContact",
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 5.0
|
||||||
|
},
|
||||||
|
"game_conveyor_coralDust": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceCoralPiece",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceCoralDust",
|
||||||
|
"amount": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 5.0
|
||||||
|
},
|
||||||
|
"game_conveyor_energyRods": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_crystalHandles",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_energyBar",
|
||||||
|
"amount": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 7.0
|
||||||
|
},
|
||||||
|
"game_conveyor_explosiveSmall": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceFabric",
|
||||||
|
"amount": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceGunpowder",
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_c4Dynamite",
|
||||||
|
"amount": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 5.0
|
||||||
|
},
|
||||||
|
"game_conveyor_mechanicalParts": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetalParts",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "item_resourceMetal_t1",
|
||||||
|
"amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craftTimeSeconds": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user