From 8abe4bcecf54eb632738a7482b23892fe082d450 Mon Sep 17 00:00:00 2001 From: DownloadPizza Date: Thu, 11 Jun 2026 19:38:13 +0200 Subject: [PATCH] production lines: resolve conveyor -> island placement mapping The recipe conveyors (game_conveyor__epb) are placed as named child GameObjects in the island_* prefabs (islands_assets_all.bundle), NOT via energy_grid (power grid only) or any config asset. New extractor bundle/extract_conveyor_placements.py walks every island prefab and cross-references production_lines.json -> extracted/conveyor_placements.json. Result: Factorio (energyRods, 80mmT3Cannon, contactGrenades, armorPiercingRocket), Kaiserplatz (energyRods, 40mmT3Cannon), Demo_Wunderinsel (70mmT3Cannon), DeusExMashineSmall (computingModules, coralDust), testIsland(+Tramplers) (the 4 base test conveyors). Corrects the earlier "Armory = Sprengstofffabrik hosts explosives" guess: the LittleFactory/LittleFactoryArmory islands place NO recipe conveyors; grenades/rockets are on Factorio. Unplaced blueprints: explosiveSmall, mechanicalParts (+ the plural armorPiercingRockets, whose singular-named instance is on Factorio). --- bundle/extract_conveyor_placements.py | 101 ++++++ docs/PRODUCTION_LINES.md | 49 ++- extracted/conveyor_placements.json | 491 ++++++++++++++++++++++++++ 3 files changed, 628 insertions(+), 13 deletions(-) create mode 100644 bundle/extract_conveyor_placements.py create mode 100644 extracted/conveyor_placements.json diff --git a/bundle/extract_conveyor_placements.py b/bundle/extract_conveyor_placements.py new file mode 100644 index 0000000..e33e2d9 --- /dev/null +++ b/bundle/extract_conveyor_placements.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Map each world island/location to the production-line conveyors placed on it. + +The recipe-bearing conveyors (game_conveyor__epb) are placed as named child +GameObjects inside the island_* prefabs in islands_assets_all.bundle (NOT via energy_grid, +which is only the power grid, and NOT in any config/designed-environment asset). This walks +every island prefab's Transform hierarchy, collects the conveyor instances (with local +position), and cross-references each recipe conveyor against extracted/production_lines.json. + +Writes extracted/conveyor_placements.json. +""" +import os, sys, json, UnityPy + +BUNDLES = "/home/downloadpizza/sand_tools/bundles" +PL = "/home/downloadpizza/sand_tools/extracted/production_lines.json" +OUT = "/home/downloadpizza/sand_tools/extracted/conveyor_placements.json" + + +def walk(go, seen, out): + """Yield (name, localPosition) for every descendant GameObject.""" + for c in go.m_Components: + try: + co = c.read() + except Exception: + continue + if co.object_reader.type.name != "Transform": + continue + for ch in co.m_Children: + try: + tr = ch.read() + cgo = tr.m_GameObject.read() + except Exception: + continue + pid = cgo.object_reader.path_id + if pid in seen: + continue + seen.add(pid) + pos = getattr(tr, "m_LocalPosition", None) + xyz = [round(pos.x, 2), round(pos.y, 2), round(pos.z, 2)] if pos else None + out.append((getattr(cgo, "m_Name", "?"), xyz)) + walk(cgo, seen, out) + + +def main(): + pl = json.load(open(PL))["production_lines"] + env = UnityPy.load(os.path.join(BUNDLES, "islands_assets_all.bundle")) + islands = {p.split("/")[-1].replace(".prefab", ""): obj + for p, obj in env.container.items() + if obj.type.name == "GameObject" and p.split("/")[-1].startswith("island_")} + + result = {} + placed_recipes = set() + for iname in sorted(islands): + acc = [] + walk(islands[iname].read(), set(), acc) + recipe_conv = [] + unmatched = [] + infra = [] + for name, pos in acc: + base = name.replace("_epb", "") + # product conveyors are game_conveyor_ (underscore); the slot/switch/ + # out/in pieces are game_conveyorXxx (no underscore) = generic infrastructure. + if base.startswith("game_conveyor_"): + if base in pl: + recipe_conv.append({"conveyor": base, "pos": pos, "recipe": pl[base]}) + placed_recipes.add(base) + else: + unmatched.append({"conveyor": base, "pos": pos}) # placed, no recipe key (name mismatch?) + elif name.startswith("game_conveyor"): + infra.append(name) + if recipe_conv or unmatched: + result[iname] = {"recipes": recipe_conv, + "product_conveyors_without_matching_recipe": unmatched, + "infrastructure": sorted(set(infra))} + + unplaced = sorted(set(pl) - placed_recipes) + out = { + "_source": "islands_assets_all.bundle island_* prefab hierarchy " + "(game_conveyor__epb child GameObjects); recipes from production_lines.json", + "_unplaced_recipes": unplaced, + "islands": result, + } + json.dump(out, open(OUT, "w"), indent=1) + + def fmt(r): + i = " + ".join("%sx %s" % (x["amount"], x["itemId"]) for x in r["inputs"]) + o = " + ".join("%sx %s" % (x["amount"], x["itemId"]) for x in r["outputs"]) + return "%s -> %s (%ss)" % (i, o, r["craftTimeSeconds"]) + + print("wrote %s\n" % OUT) + for iname, d in result.items(): + print("### %s" % iname.replace("island_", "")) + for rc in d["recipes"]: + print(" %-34s %s" % (rc["conveyor"].replace("game_conveyor_", ""), fmt(rc["recipe"]))) + for u in d["product_conveyors_without_matching_recipe"]: + print(" %-34s (placed, no recipe blueprint of this exact name)" % u["conveyor"].replace("game_conveyor_", "")) + print("\nrecipe blueprints NOT placed on any island:", unplaced) + + +if __name__ == "__main__": + main() diff --git a/docs/PRODUCTION_LINES.md b/docs/PRODUCTION_LINES.md index 0d6fa25..3e044f1 100644 --- a/docs/PRODUCTION_LINES.md +++ b/docs/PRODUCTION_LINES.md @@ -54,18 +54,41 @@ Key item ids seen here: `item_crystalHandles` = *Raw Aurogen Crystal*, `extracted/items_registry.json` / `extracted/item_names.json` (built from `CheatItemDefinitionsData`). -## Island placement — open lead +## Island placement — RESOLVED -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. +Each recipe conveyor is placed as a **named child GameObject** (`game_conveyor__epb`) +in the `island_*` prefab's Transform hierarchy inside `islands_assets_all.bundle`. It is NOT +in the `energy_grid_*` blueprint (that holds only `EnergyGridDataComponent` — the power grid) +and NOT in any config / designed-environment asset. Extract with: -**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. +```bash +venv/bin/python bundle/extract_conveyor_placements.py # -> extracted/conveyor_placements.json +``` + +This walks every island prefab, collects the `game_conveyor_` children (with local +position) and cross-references each against `production_lines.json`. Result for this build: + +| Island | Recipe conveyors placed | +|---|---| +| `island_Factorio` | energyRods, 80mmT3Cannon, contactGrenades, armorPiercingRocket* | +| `island_Kaiserplatz` | energyRods, 40mmT3Cannon | +| `island_Demo_Wunderinsel` | 70mmT3Cannon | +| `island_DeusExMashineSmall` | computingModules, coralDust | +| `island_testIsland`, `island_testIslandTramplers` | the 4 `base*` conveyors (inv↔large test combos, incl. the `1x wineBox → 1000x coinCrown` one) | + +Notes / corrections: +- The earlier guess that the **`…Armory` (Sprengstofffabrik)** islands host the explosive / + grenade / rocket conveyors is **not supported by the placements**: `island_LittleFactory01-03` + and `island_LittleFactoryArmory01-03` exist (each has an `energy_grid_*`) but place **no** + recipe conveyors at all. The grenade (`contactGrenades`) and rocket (`armorPiercingRocket`) + lines are on **Factorio**. Factorio is the main multi-product factory here. +- `*` Factorio's child is named `game_conveyor_armorPiercingRocket` (singular) while the recipe + blueprint is `game_conveyor_armorPiercingRockets` (plural) — a name mismatch; same line + (→ `3x item_rocketLauncherAmmoArmorPiercing`). +- Defined-but-**unplaced** recipe blueprints in this build: `armorPiercingRockets` (plural; + the singular instance is on Factorio), `explosiveSmall` (→ `5x item_c4Dynamite`), + `mechanicalParts` (→ `item_resourceMetal_t1`). +- Infrastructure conveyors (`game_conveyorSlotInput`, `…OutInventory`, `…OutLargeItem`, + `…InSwitch`) accompany the recipe ones on each factory island — these are the input/output + ports, not recipes (no underscore after `conveyor` = generic infra; `game_conveyor_` = + recipe line). diff --git a/extracted/conveyor_placements.json b/extracted/conveyor_placements.json new file mode 100644 index 0000000..6385768 --- /dev/null +++ b/extracted/conveyor_placements.json @@ -0,0 +1,491 @@ +{ + "_source": "islands_assets_all.bundle island_* prefab hierarchy (game_conveyor__epb child GameObjects); recipes from production_lines.json", + "_unplaced_recipes": [ + "game_conveyor_armorPiercingRockets", + "game_conveyor_explosiveSmall", + "game_conveyor_mechanicalParts" + ], + "islands": { + "island_Demo_Wunderinsel": { + "recipes": [ + { + "conveyor": "game_conveyor_70mmT3Cannon", + "pos": [ + -3.27, + -0.01, + -7.78 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_alloySteel", + "amount": 40 + }, + { + "itemId": "item_resourceMetal_t2", + "amount": 300 + } + ], + "outputs": [ + { + "itemId": "game_packedShotgunTurretT3Container", + "amount": 1 + } + ], + "craftTimeSeconds": 55.0 + } + } + ], + "product_conveyors_without_matching_recipe": [], + "infrastructure": [ + "game_conveyorInSwitch_epb", + "game_conveyorOutLargeItem_epb", + "game_conveyorSlot" + ] + }, + "island_DeusExMashineSmall": { + "recipes": [ + { + "conveyor": "game_conveyor_computingModules", + "pos": [ + 19.04, + -0.34, + -11.85 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_blackBox", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_resourceMetal_t3", + "amount": 10 + } + ], + "craftTimeSeconds": 7.0 + } + }, + { + "conveyor": "game_conveyor_coralDust", + "pos": [ + 0.9, + 0.0, + 3.2 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_resourceCoralPiece", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_resourceCoralDust", + "amount": 10 + }, + { + "itemId": "item_resourceMetal_t1", + "amount": 2 + } + ], + "craftTimeSeconds": 5.0 + } + } + ], + "product_conveyors_without_matching_recipe": [], + "infrastructure": [ + "game_conveyorInSwitch_epb", + "game_conveyorOutInventory_epb", + "game_conveyorSlotInput_epb" + ] + }, + "island_Factorio": { + "recipes": [ + { + "conveyor": "game_conveyor_energyRods", + "pos": [ + -10.25, + -3.44, + -0.51 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_crystalHandles", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_energyBar", + "amount": 10 + } + ], + "craftTimeSeconds": 7.0 + } + }, + { + "conveyor": "game_conveyor_80mmT3Cannon", + "pos": [ + 3.49, + -3.45, + -7.43 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_alloySteel", + "amount": 40 + }, + { + "itemId": "item_resourceMetal_t2", + "amount": 300 + } + ], + "outputs": [ + { + "itemId": "game_packedTurretT3Container", + "amount": 1 + } + ], + "craftTimeSeconds": 55.0 + } + }, + { + "conveyor": "game_conveyor_contactGrenades", + "pos": [ + 1.25, + 3.52, + 1.93 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_resourceFabric", + "amount": 10 + }, + { + "itemId": "item_resourceGunpowder", + "amount": 10 + } + ], + "outputs": [ + { + "itemId": "item_grenadeContact", + "amount": 5 + } + ], + "craftTimeSeconds": 5.0 + } + } + ], + "product_conveyors_without_matching_recipe": [ + { + "conveyor": "game_conveyor_armorPiercingRocket", + "pos": [ + -8.05, + 3.52, + -8.79 + ] + } + ], + "infrastructure": [ + "game_conveyorInSwitch_epb", + "game_conveyorOutInventory_epb", + "game_conveyorOutLargeItem_epb", + "game_conveyorSlot", + "game_conveyorSlotInput_epb" + ] + }, + "island_Kaiserplatz": { + "recipes": [ + { + "conveyor": "game_conveyor_energyRods", + "pos": [ + 3.06, + 0.05, + 1.41 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_crystalHandles", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_energyBar", + "amount": 10 + } + ], + "craftTimeSeconds": 7.0 + } + }, + { + "conveyor": "game_conveyor_40mmT3Cannon", + "pos": [ + 1.13, + 0.05, + -6.21 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_alloySteel", + "amount": 40 + }, + { + "itemId": "item_resourceMetal_t2", + "amount": 300 + } + ], + "outputs": [ + { + "itemId": "game_packedAutoTurretT3Container", + "amount": 1 + } + ], + "craftTimeSeconds": 55.0 + } + } + ], + "product_conveyors_without_matching_recipe": [], + "infrastructure": [ + "game_conveyorInSwitch_epb", + "game_conveyorOutInventory_epb", + "game_conveyorOutLargeItem_epb", + "game_conveyorSlot", + "game_conveyorSlotInput_epb" + ] + }, + "island_testIsland": { + "recipes": [ + { + "conveyor": "game_conveyor_baseInventoryToLarge", + "pos": [ + 0.0, + 0.1, + 0.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_resourceMetal_t1", + "amount": 1 + }, + { + "itemId": "item_c4Dynamite", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_explosiveBig", + "amount": 1 + } + ], + "craftTimeSeconds": 15.0 + } + }, + { + "conveyor": "game_conveyor_baseInventoryToInventory", + "pos": [ + -0.0, + 0.0, + -6.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_resourceMetal_t1", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_c4Dynamite", + "amount": 1 + }, + { + "itemId": "item_resourceMetal_t1", + "amount": 2 + } + ], + "craftTimeSeconds": 5.0 + } + }, + { + "conveyor": "game_conveyor_baseLargeToLarge", + "pos": [ + 7.0, + 0.0, + 0.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_wineBox", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_explosiveBig", + "amount": 1 + } + ], + "craftTimeSeconds": 15.0 + } + }, + { + "conveyor": "game_conveyor_baseLargeToInventory", + "pos": [ + 5.0, + 7.5, + -0.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_wineBox", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_coinCrown", + "amount": 1000 + } + ], + "craftTimeSeconds": 15.0 + } + } + ], + "product_conveyors_without_matching_recipe": [], + "infrastructure": [ + "game_conveyorInSwitch_epb", + "game_conveyorOutInventory_epb", + "game_conveyorOutLargeItem_epb", + "game_conveyorSlot", + "game_conveyorSlotInput_epb" + ] + }, + "island_testIslandTramplers": { + "recipes": [ + { + "conveyor": "game_conveyor_baseInventoryToLarge", + "pos": [ + 0.0, + 0.1, + 0.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_resourceMetal_t1", + "amount": 1 + }, + { + "itemId": "item_c4Dynamite", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_explosiveBig", + "amount": 1 + } + ], + "craftTimeSeconds": 15.0 + } + }, + { + "conveyor": "game_conveyor_baseInventoryToInventory", + "pos": [ + -0.0, + 0.0, + -6.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_resourceMetal_t1", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_c4Dynamite", + "amount": 1 + }, + { + "itemId": "item_resourceMetal_t1", + "amount": 2 + } + ], + "craftTimeSeconds": 5.0 + } + }, + { + "conveyor": "game_conveyor_baseLargeToLarge", + "pos": [ + 7.0, + 0.0, + 0.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_wineBox", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_explosiveBig", + "amount": 1 + } + ], + "craftTimeSeconds": 15.0 + } + }, + { + "conveyor": "game_conveyor_baseLargeToInventory", + "pos": [ + 5.0, + 7.5, + -0.0 + ], + "recipe": { + "inputs": [ + { + "itemId": "item_wineBox", + "amount": 1 + } + ], + "outputs": [ + { + "itemId": "item_coinCrown", + "amount": 1000 + } + ], + "craftTimeSeconds": 15.0 + } + } + ], + "product_conveyors_without_matching_recipe": [], + "infrastructure": [ + "game_conveyorInSwitch_epb", + "game_conveyorOutInventory_epb", + "game_conveyorOutLargeItem_epb", + "game_conveyorSlot", + "game_conveyorSlotInput_epb" + ] + } + } +} \ No newline at end of file