#!/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()