import os import json import shutil import uuid # Configuration PROJECT_ROOT = '/Users/davidkotnik/repos/novafarma' GODOT_ROOT = os.path.join(PROJECT_ROOT, 'godot') LDTK_FILE = os.path.join(PROJECT_ROOT, 'AutoLayers_2_stamps.ldtk') # Ensure destination for missing assets exists dest_ground = os.path.join(GODOT_ROOT, 'world', 'GROUND') os.makedirs(dest_ground, exist_ok=True) # 1. Fix missing grass_placeholder if needed src_grass = os.path.join(PROJECT_ROOT, 'assets', 'terrain', 'grass_placeholder.png') dst_grass = os.path.join(dest_ground, 'grass_placeholder.png') if os.path.exists(src_grass) and not os.path.exists(dst_grass): shutil.copy2(src_grass, dst_grass) print(f"Copied grass_placeholder.png to {dst_grass}") # User requested assets mapping requested_assets = { "Ground": [ "grass_placeholder.png", "stone.png", "dirt_path.png", "dirt_path_corner_bottomleft.png", "dirt_path_corner_bottomright.png" ], "Crops": [ "cannabis_s32_stage1_seeds.png", "cannabis_s32_stage2_sprout.png", "cannabis_s32_stage3_young.png", "cannabis_s32_stage4_growing.png", "cannabis_s32_stage5_ready.png", "cannabis_s32_stage6_harvested.png" ], "Tools": [ "shovel.png", "watering_can.png", "stone_hoe.png", "hoe.png" ], "Props": [ "blue_patch.png", "mixed_patch.png", "red_patch.png", "white_patch.png", "yellow_patch.png", "bush_flowering.png", "bush_green.png", "bushes_set2.png", "fallen_log.png", "mushroom_small.png", "mushrooms_large.png", "rock_large.png", "rock_medium.png", "rock_small.png", "rocks_set2.png", "tall_grass.png", "tall_grass_set1.png", "tall_grass_set2.png", "tree_stump.png", "oak_summer.png", "pine.png", "willow.png" ], "Entities": [ # Partial matches allow finding these even if named differently? # Reverting to "contains" for Kai/Anim files because filenames might be complex timestamps "kai_idle_down_v2", "kai_idle_right_v2", "kai_idle_up_v2", "kai_walk_down_01_v2", "kai_walk_down_02_v2", "kai_walk_down_03_v2", "kai_walk_down_04_v2", "kai_walk_right_01_v2", "kai_walk_right_02_v2", "kai_walk_right_03_v2", "kai_walk_right_04_v2", "kai_walk_up_01_v2", "kai_walk_up_02_v2", "kai_walk_up_03_v2", "kai_walk_up_04_v2", "kai_harvest_frame1", "kai_harvest_frame2", "kai_harvest_frame3", "kai_harvest_frame4", "kai_plant_frame1", "kai_plant_frame2", "kai_plant_frame3", "kai_plant_frame4", "kai_water_frame1", "kai_water_frame2", "kai_water_frame3", "kai_water_frame4", "kai_shelter_wooden_hut", "kai_shelter_sleeping_bag" ] } def find_file(filename_part, strict=True): for root, dirs, files in os.walk(GODOT_ROOT): for file in files: if strict: if file == filename_part: return os.path.join(root, file) else: if filename_part in file and file.endswith('.png'): return os.path.join(root, file) return None # Load LDtk with open(LDTK_FILE, 'r') as f: ldtk_data = json.load(f) defs_layers = ldtk_data['defs']['layers'] defs_entities = ldtk_data['defs']['entities'] defs_tilesets = ldtk_data['defs']['tilesets'] # Setup Layers next_layer_uid = 600 desired_layers = ["Entities", "Props", "Crops", "Ground"] # Top to Bottom order # Note: Entities layer usually ID 201 (Entities) and 200 (Terrain_Control) exist. # We will rename 201 if it exists or use it. # Check existing "Entities" (201) ex_ent = next((l for l in defs_layers if l['identifier'] == 'Entities'), None) if not ex_ent: # Create it ex_ent = { "__type": "Entities", "identifier": "Entities", "type": "Entities", "uid": 201, # Keep standard ID "gridSize": 32, "displayOpacity": 1, "pxOffsetX": 0, "pxOffsetY": 0, "requiredTags": [], "excludedTags": [], "intGridValues": [], "autoRuleGroups": [], "autoSourceLayerDefUid": None, "tilesetDefUid": None, "tilePivotX": 0, "tilePivotY": 0 } defs_layers.append(ex_ent) # Ensure others exist for lname in ["Props", "Crops", "Ground"]: if not any(l['identifier'] == lname for l in defs_layers): new_l = ex_ent.copy() new_l['identifier'] = lname new_l['uid'] = next_layer_uid next_layer_uid += 1 defs_layers.append(new_l) # Reorder definition list: Entities, Props, Crops, Ground, Terrain_Visuals, Terrain_Control final_layers = [] for name in ["Entities", "Props", "Crops", "Ground", "Terrain_Visuals", "Terrain_Control"]: l = next((x for x in defs_layers if x['identifier'] == name), None) if l: final_layers.append(l) ldtk_data['defs']['layers'] = final_layers # UIDs ent_start_uid = 1000 ts_start_uid = 2000 existing_ent_uids = {e['uid'] for e in defs_entities} existing_ts_uids = {t['uid'] for t in defs_tilesets} def get_new_uid(existing_uids, start): while start in existing_uids: start += 1 return start # Process Assets for category, items in requested_assets.items(): strict_search = (category != "Entities") # Entities have complex partial names tag = category if category == "Tools": tag = "Props" for item_name in items: full_path = find_file(item_name, strict=strict_search) if not full_path: # Fallback for Entities: try finding simple name if complex fails if category == "Entities": # Maybe "kai_walk_down_01" -> search "kai_walk_down_01" full_path = find_file(item_name.rsplit('_', 1)[0], strict=False) if not full_path: print(f"Skipping {item_name}: Not found.") continue rel_path = os.path.relpath(full_path, PROJECT_ROOT) # Check tileset existing_ts = next((t for t in defs_tilesets if t['relPath'] == rel_path), None) if existing_ts: ts_uid = existing_ts['uid'] w, h = existing_ts['pxWid'], existing_ts['pxHei'] else: # Size try: with open(full_path, 'rb') as img_f: head = img_f.read(24) import struct w, h = struct.unpack('>ii', head[16:24]) except: w, h = 32, 32 ts_uid = get_new_uid(existing_ts_uids, ts_start_uid) existing_ts_uids.add(ts_uid) new_ts = { "__cWid": 1, "__cHei": 1, "identifier": "TS_" + os.path.basename(full_path).replace('.png','').replace(' ','_'), "uid": ts_uid, "relPath": rel_path, "embedAtlas": None, "pxWid": w, "pxHei": h, "tileGridSize": max(w,h), "spacing": 0, "padding": 0, "tags": [], "tagsSourceEnumUid": None, "enumTags": [], "customData": [], "savedSelections": [], "cachedPixelData": None } defs_tilesets.append(new_ts) # Entity ent_identifier = os.path.basename(full_path).replace('.png','').replace(' ','_').capitalize() if len(ent_identifier) > 30: # truncate nice ent_identifier = ent_identifier[:30] # Remove old definition with same ID to update defs_entities = [e for e in defs_entities if e['identifier'] != ent_identifier] ent_uid = get_new_uid(existing_ent_uids, ent_start_uid) existing_ent_uids.add(ent_uid) new_ent = { "identifier": ent_identifier, "uid": ent_uid, "tags": [tag], "exportToToc": False, "allowOutOfBounds": False, "doc": None, "width": w, "height": h, "resizableX": False, "resizableY": False, "minWidth": None, "maxWidth": None, "minHeight": None, "maxHeight": None, "keepAspectRatio": True, "tileOpacity": 1, "fillOpacity": 0.08, "lineOpacity": 0, "hollow": False, "color": "#94D0FF", "renderMode": "Tile", "showName": True, "tilesetId": ts_uid, "tileRenderMode": "FitInside", "tileRect": { "tilesetUid": ts_uid, "x": 0, "y": 0, "w": w, "h": h }, "uiTileRect": None, "nineSliceBorders": [], "maxCount": 0, "limitScope": "PerLevel", "limitBehavior": "MoveLastOne", "pivotX": 0.5, "pivotY": 1, "fieldDefs": [] } defs_entities.append(new_ent) ldtk_data['defs']['entities'] = defs_entities ldtk_data['defs']['tilesets'] = defs_tilesets # Update Levels Layer Instances for level in ldtk_data['levels']: old_insts = level['layerInstances'] new_insts = [] for ldef in final_layers: existing = next((i for i in old_insts if i['layerDefUid'] == ldef['uid']), None) if existing: new_insts.append(existing) else: new_insts.append({ "__identifier": ldef['identifier'], "__type": "Entities", "__cWid": level['pxWid'] // 32, "__cHei": level['pxHei'] // 32, "__gridSize": 32, "__opacity": 1, "__pxTotalOffsetX": 0, "__pxTotalOffsetY": 0, "__tilesetDefUid": None, "__tilesetRelPath": None, "iid": str(uuid.uuid4()), "levelId": level['uid'], "layerDefUid": ldef['uid'], "pxOffsetX": 0, "pxOffsetY": 0, "visible": True, "optionalRules": [], "intGridCsv": [], "autoLayerTiles": [], "seed": 0, "overrideTilesetUid": None, "gridTiles": [], "entityInstances": [] }) level['layerInstances'] = new_insts with open(LDTK_FILE, 'w') as f: json.dump(ldtk_data, f, indent=2) print("LDtk updated with new assets and sorted layers.")