349 lines
12 KiB
Python
349 lines
12 KiB
Python
import os
|
|
import json
|
|
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')
|
|
|
|
# User requested assets mapping (approximate filenames to search for)
|
|
# We will search for these filenames in the godot directory.
|
|
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": [
|
|
"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"
|
|
]
|
|
}
|
|
|
|
# Helper to find file
|
|
def find_file(filename_part):
|
|
for root, dirs, files in os.walk(GODOT_ROOT):
|
|
for file in files:
|
|
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)
|
|
|
|
# Ensure layers exist
|
|
defs_layers = ldtk_data['defs']['layers']
|
|
existing_layer_ids = [l['identifier'] for l in defs_layers]
|
|
|
|
# Helper to create layer definition
|
|
def create_layer_def(ident, uid):
|
|
return {
|
|
"__type": "Entities",
|
|
"identifier": ident,
|
|
"type": "Entities",
|
|
"uid": uid,
|
|
"gridSize": 32,
|
|
"displayOpacity": 1,
|
|
"pxOffsetX": 0,
|
|
"pxOffsetY": 0,
|
|
"requiredTags": [ident], # Restrict to entities with this tag
|
|
"excludedTags": [],
|
|
"intGridValues": [],
|
|
"autoRuleGroups": [],
|
|
"autoSourceLayerDefUid": null,
|
|
"tilesetDefUid": null,
|
|
"tilePivotX": 0,
|
|
"tilePivotY": 0
|
|
}
|
|
|
|
# NOTE: json does not support 'null', use None
|
|
null = None
|
|
|
|
# We need UIDs. Let's start from 600 for layers
|
|
next_layer_uid = 600
|
|
desired_layers = ["Ground", "Crops", "Props", "Entities"] # Entities already exists usually, but we check.
|
|
|
|
layer_uids = {}
|
|
|
|
for layer_name in desired_layers:
|
|
existing = next((l for l in defs_layers if l['identifier'] == layer_name), None)
|
|
if existing:
|
|
layer_uids[layer_name] = existing['uid']
|
|
# Update tags to enforce cleanliness?
|
|
# For now, let's allow overlapping or just set it up.
|
|
# existing['requiredTags'] = [layer_name] # Enforce strict layering?
|
|
# Maybe safer to not enforce strictly if logic already exists.
|
|
# But user asked for layers setup.
|
|
pass
|
|
else:
|
|
new_layer = {
|
|
"__type": "Entities",
|
|
"identifier": layer_name,
|
|
"type": "Entities",
|
|
"uid": next_layer_uid,
|
|
"gridSize": 32,
|
|
"displayOpacity": 1,
|
|
"pxOffsetX": 0,
|
|
"pxOffsetY": 0,
|
|
"requiredTags": [layer_name] if layer_name != "Entities" else [], # Entities is generic fallback
|
|
"excludedTags": [],
|
|
"intGridValues": [],
|
|
"autoRuleGroups": [],
|
|
"autoSourceLayerDefUid": None,
|
|
"tilesetDefUid": None,
|
|
"tilePivotX": 0,
|
|
"tilePivotY": 0
|
|
}
|
|
defs_layers.insert(0, new_layer) # proper order: Top to Bottom?
|
|
# LDtk renders bottom to top in list usually? Or top is top?
|
|
# "layers": [...] order is: 0 is TOP, N is BOTTOM.
|
|
# User wants: Ground (Bottom), Crops (Middle), Props (Top), Entities (Player?).
|
|
# So List order: Entities, Props, Crops, Ground, (Terrain_Visuals, Terrain_Control)
|
|
layer_uids[layer_name] = next_layer_uid
|
|
next_layer_uid += 1
|
|
|
|
# Re-order layers
|
|
# Desired visual order (Draw order): Ground -> Crops -> Props -> Entities
|
|
# In LDtk JSON "layers" array: The *first* element is the *top-most* layer.
|
|
# So we want: Entities, Props, Crops, Ground, Terrain_Visuals, Terrain_Control.
|
|
ordered_layers = []
|
|
|
|
def get_layer(name):
|
|
return next((l for l in defs_layers if l['identifier'] == name), None)
|
|
|
|
# Collect them
|
|
l_entities = get_layer("Entities")
|
|
l_props = get_layer("Props")
|
|
l_crops = get_layer("Crops")
|
|
l_ground = get_layer("Ground")
|
|
l_vis = get_layer("Terrain_Visuals")
|
|
l_ctrl = get_layer("Terrain_Control")
|
|
|
|
final_list = []
|
|
if l_entities: final_list.append(l_entities)
|
|
if l_props: final_list.append(l_props)
|
|
if l_crops: final_list.append(l_crops)
|
|
if l_ground: final_list.append(l_ground)
|
|
if l_vis: final_list.append(l_vis)
|
|
if l_ctrl: final_list.append(l_ctrl)
|
|
|
|
# Add any others that might exist (backups)
|
|
for l in defs_layers:
|
|
if l not in final_list:
|
|
final_list.append(l)
|
|
|
|
ldtk_data['defs']['layers'] = final_list
|
|
|
|
# Setup Entities
|
|
defs_entities = ldtk_data['defs']['entities']
|
|
defs_tilesets = ldtk_data['defs']['tilesets']
|
|
|
|
# Generate new UIDs
|
|
ent_start_uid = 1000
|
|
ts_start_uid = 2000
|
|
|
|
# Helper to find valid UID
|
|
def get_new_uid(existing_uids, start):
|
|
while start in existing_uids:
|
|
start += 1
|
|
return start
|
|
|
|
existing_ent_uids = {e['uid'] for e in defs_entities}
|
|
existing_ts_uids = {t['uid'] for t in defs_tilesets}
|
|
|
|
# Process requested assets
|
|
for category, items in requested_assets.items():
|
|
tag = category
|
|
if category == "Tools": tag = "Props" # Tools go to prop layer? Or Player? Let's say Props.
|
|
|
|
for item_name in items:
|
|
# 1. Find file
|
|
full_path = find_file(item_name)
|
|
if not full_path:
|
|
print(f"Warning: Could not find file for {item_name}")
|
|
continue
|
|
|
|
rel_path = os.path.relpath(full_path, PROJECT_ROOT)
|
|
|
|
# 2. Create Tileset for this image (LDtk needs tileset for visual entity)
|
|
# Check if tileset exists
|
|
existing_ts = next((t for t in defs_tilesets if t['relPath'] == rel_path), None)
|
|
if existing_ts:
|
|
ts_uid = existing_ts['uid']
|
|
px_wid = existing_ts['pxWid']
|
|
px_hei = existing_ts['pxHei']
|
|
else:
|
|
# Need image dimensions. Hack: LDtk might auto-detect if we save?
|
|
# No, we need to provide valid JSON.
|
|
# We can try to read png header or just assume 32x32 if fails, but correct is better.
|
|
# Let's use a quick png size reader or godot import.
|
|
# For this script simplicity, I'll check if I can import Pillow.
|
|
# If not, I'll rely on a basic hardcoded guess or try to read bytes.
|
|
|
|
# Simple PNG size parser
|
|
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_" + item_name.replace('.png','').replace(' ','_'),
|
|
"uid": ts_uid,
|
|
"relPath": rel_path,
|
|
"embedAtlas": None,
|
|
"pxWid": w,
|
|
"pxHei": h,
|
|
"tileGridSize": max(w, h), # Full image
|
|
"spacing": 0,
|
|
"padding": 0,
|
|
"tags": [],
|
|
"tagsSourceEnumUid": null,
|
|
"enumTags": [],
|
|
"customData": [],
|
|
"savedSelections": [],
|
|
"cachedPixelData": None
|
|
}
|
|
defs_tilesets.append(new_ts)
|
|
|
|
# 3. Create Entity Definition
|
|
# Clean identifier
|
|
ent_id = item_name.replace('.png','').replace(' ','_').capitalize()
|
|
# Ensure unique identifier
|
|
# existing_ent = next((e for e in defs_entities if e['identifier'] == ent_id), None)
|
|
# Actually user wants these specific ones.
|
|
# If it exists, update it?
|
|
|
|
# Remove existing if present to refresh
|
|
defs_entities = [e for e in defs_entities if e['identifier'] != ent_id]
|
|
|
|
ent_uid = get_new_uid(existing_ent_uids, ent_start_uid)
|
|
existing_ent_uids.add(ent_uid)
|
|
|
|
# Determine layer tag
|
|
layer_tag = tag # Default
|
|
if category == "Entities": layer_tag = "Entities" # Keep generic
|
|
|
|
new_ent = {
|
|
"identifier": ent_id,
|
|
"uid": ent_uid,
|
|
"tags": [layer_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
|
|
|
|
# Ensure level instances have the new layers
|
|
for level in ldtk_data['levels']:
|
|
# Existing layer instances
|
|
existing_insts = level['layerInstances']
|
|
new_insts = []
|
|
|
|
# We must match the order of defs.layers
|
|
for layer_def in ldtk_data['defs']['layers']:
|
|
existing_inst = next((i for i in existing_insts if i['layerDefUid'] == layer_def['uid']), None)
|
|
if existing_inst:
|
|
new_insts.append(existing_inst)
|
|
else:
|
|
# Create new instance
|
|
new_insts.append({
|
|
"__identifier": layer_def['identifier'],
|
|
"__type": "Entities",
|
|
"__cWid": level['pxWid'] // 32, # Grid size 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": layer_def['uid'],
|
|
"pxOffsetX": 0,
|
|
"pxOffsetY": 0,
|
|
"visible": True,
|
|
"optionalRules": [],
|
|
"intGridCsv": [],
|
|
"autoLayerTiles": [],
|
|
"seed": 0,
|
|
"overrideTilesetUid": None,
|
|
"gridTiles": [],
|
|
"entityInstances": []
|
|
})
|
|
|
|
level['layerInstances'] = new_insts
|
|
|
|
# Save
|
|
with open(LDTK_FILE, 'w') as f:
|
|
json.dump(ldtk_data, f, indent=2)
|
|
|
|
print("LDtk project updated successfully!")
|