330 lines
9.5 KiB
Python
330 lines
9.5 KiB
Python
"""
|
|
🗺️ TILED BATCH TILESET ORGANIZER
|
|
==================================
|
|
Organizes 122 sprite sheets into categorized TSX files for Tiled Map Editor
|
|
|
|
Author: Antigravity AI
|
|
Date: 2025-12-22
|
|
Project: Krvava Žetev / NovaFarma
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
import xml.etree.ElementTree as ET
|
|
from xml.dom import minidom
|
|
|
|
# Base paths
|
|
BASE_DIR = Path(r"c:\novafarma\assets")
|
|
KRVAVA_SPRITES_DIR = BASE_DIR / "krvava_zetev_sprites"
|
|
TILESETS_OUTPUT_DIR = BASE_DIR / "maps" / "organized_tilesets"
|
|
|
|
# Ensure output directory exists
|
|
TILESETS_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Category definitions with sprite sheet mappings
|
|
CATEGORIES = {
|
|
"01_Characters_NPCs": [
|
|
"kai_character_2x2_grid",
|
|
"ana_character_2x2_grid",
|
|
"lena_farmer_2x2_grid",
|
|
"marija_baker_2x2_grid",
|
|
"ivan_blacksmith_2x2_grid",
|
|
"jakob_trader_2x2_grid",
|
|
"dr_chen_doctor_2x2_grid",
|
|
"dr_krnic_villain_2x2_grid"
|
|
],
|
|
"02_Animals_Pets": [
|
|
"farm_animals_family_grid",
|
|
"dog_companions_5_breeds",
|
|
"rare_livestock_animals",
|
|
"delivery_creatures_bat_owl"
|
|
],
|
|
"03_Buildings_Upgrades": [
|
|
"house_progression_5_stages",
|
|
"barn_progression_4_stages",
|
|
"storage_upgrades_4_stages",
|
|
"greenhouse_upgrades_stages",
|
|
"buildings_ruins_states"
|
|
],
|
|
"04_Environment_Terrain": [
|
|
"biome_terrain_tiles",
|
|
"trees_topdown_pack",
|
|
"grass_soil_tileset",
|
|
"seasonal_vegetation_4_seasons",
|
|
"tree_growth_oak",
|
|
"tree_growth_pine"
|
|
],
|
|
"05_Crops_Farming": [
|
|
"crop_growth_all",
|
|
"wheat_growth",
|
|
"seasonal_crops_grid",
|
|
"seasonal_seed_packets",
|
|
"fruit_trees_growth"
|
|
],
|
|
"06_Weapons_Combat": [
|
|
"weapons_melee_grid",
|
|
"weapons_firearms_modern",
|
|
"bows_5_types",
|
|
"arrows_9_types_elemental"
|
|
],
|
|
"07_Crafting_Blueprints": [
|
|
"blueprints_building_unlocks",
|
|
"crafting_recipes_ui_display",
|
|
"legendary_blueprint_golden"
|
|
],
|
|
"08_Transport_Systems": [
|
|
"train_repairs_3_states",
|
|
"horses_breeds_5_types",
|
|
"cart_wagon_system",
|
|
"water_transport_pack"
|
|
],
|
|
"09_Magic_System": [
|
|
"magic_staffs_6_types",
|
|
"spell_effects_animations",
|
|
"potions_elixirs_grid"
|
|
],
|
|
"10_DLC_Dino_World": [
|
|
"dinosaurs_animation_strips",
|
|
"dino_world_clothing",
|
|
"dino_world_food",
|
|
"dino_world_items"
|
|
],
|
|
"11_DLC_Mythical_Highlands": [
|
|
"mythical_creatures_pack",
|
|
"highland_clothing",
|
|
"highland_food",
|
|
"highland_items"
|
|
],
|
|
"12_DLC_Amazon": [
|
|
"amazon_creatures_pack",
|
|
"amazon_clothing",
|
|
"amazon_food",
|
|
"amazon_items"
|
|
],
|
|
"13_DLC_Egypt": [
|
|
"egyptian_structures_pack",
|
|
"egyptian_pyramids_sphinx",
|
|
"egypt_clothing",
|
|
"egypt_food"
|
|
],
|
|
"14_DLC_Atlantis": [
|
|
"atlantis_objects_pack",
|
|
"atlantis_clothing",
|
|
"atlantis_food",
|
|
"atlantis_items"
|
|
],
|
|
"15_DLC_Chernobyl": [
|
|
"chernobyl_structures_pack",
|
|
"chernobyl_clothing",
|
|
"chernobyl_food",
|
|
"anomalous_creatures_detailed"
|
|
],
|
|
"16_DLC_Paris": [
|
|
"paris_catacombs_pack",
|
|
"paris_clothing",
|
|
"paris_food"
|
|
],
|
|
"17_DLC_Loch_Ness": [
|
|
"loch_ness_creatures",
|
|
"scotland_clothing",
|
|
"scotland_food"
|
|
],
|
|
"18_Monsters_Bosses": [
|
|
"slimes_8_types_pack",
|
|
"giant_troll_king_boss",
|
|
"grok_fabulous_complete_sprite",
|
|
"werewolf_moon_phases"
|
|
],
|
|
"19_Furniture_Interior": [
|
|
"starter_house_interior",
|
|
"bedroom_furniture_pack",
|
|
"kitchen_furniture_pack",
|
|
"living_room_pack"
|
|
],
|
|
"20_Misc_Items": [
|
|
"backpack_upgrades_6_tiers",
|
|
"tools_farming_grid",
|
|
"fishing_equipment_pack",
|
|
"anas_story_clues_items"
|
|
]
|
|
}
|
|
|
|
|
|
def create_tsx_from_png(png_path, category_name, output_dir):
|
|
"""
|
|
Creates a Tiled TSX tileset file from a PNG sprite sheet
|
|
|
|
Args:
|
|
png_path: Path to the PNG file
|
|
category_name: Category folder name
|
|
output_dir: Output directory for TSX file
|
|
"""
|
|
if not png_path.exists():
|
|
print(f"⚠️ PNG not found: {png_path}")
|
|
return None
|
|
|
|
# Get image dimensions (approx - Tiled will correct on load)
|
|
from PIL import Image
|
|
try:
|
|
img = Image.open(png_path)
|
|
width, height = img.size
|
|
img.close()
|
|
except Exception as e:
|
|
print(f"❌ Error reading image {png_path}: {e}")
|
|
return None
|
|
|
|
# Determine tile size based on category
|
|
if "character" in png_path.stem.lower() or "npc" in png_path.stem.lower():
|
|
tile_width, tile_height = 96, 96 # 2x2 grid on 48px base
|
|
elif "building" in png_path.stem.lower() or "house" in png_path.stem.lower():
|
|
tile_width, tile_height = 192, 192 # Larger buildings
|
|
else:
|
|
tile_width, tile_height = 48, 48 # Default tile size
|
|
|
|
# Calculate columns and tile count
|
|
columns = width // tile_width
|
|
rows = height // tile_height
|
|
tilecount = columns * rows
|
|
|
|
if tilecount == 0:
|
|
tilecount = 1 # Single tile
|
|
columns = 1
|
|
|
|
# Create TSX structure
|
|
tileset = ET.Element("tileset", {
|
|
"version": "1.10",
|
|
"tiledversion": "1.11.1",
|
|
"name": png_path.stem.replace("_", " ").title(),
|
|
"tilewidth": str(tile_width),
|
|
"tileheight": str(tile_height),
|
|
"tilecount": str(tilecount),
|
|
"columns": str(columns)
|
|
})
|
|
|
|
# Relative path from TSX to PNG
|
|
rel_path = os.path.relpath(png_path, output_dir).replace("\\", "/")
|
|
|
|
image = ET.SubElement(tileset, "image", {
|
|
"source": f"../../{rel_path}",
|
|
"width": str(width),
|
|
"height": str(height)
|
|
})
|
|
|
|
# Pretty print XML
|
|
rough_string = ET.tostring(tileset, encoding='unicode')
|
|
reparsed = minidom.parseString(rough_string)
|
|
pretty_xml = reparsed.toprettyxml(indent=" ", encoding="UTF-8")
|
|
|
|
# Save TSX file
|
|
category_output = output_dir / category_name
|
|
category_output.mkdir(parents=True, exist_ok=True)
|
|
|
|
tsx_file = category_output / f"{png_path.stem}.tsx"
|
|
with open(tsx_file, 'wb') as f:
|
|
f.write(pretty_xml)
|
|
|
|
print(f"✅ Created: {tsx_file.relative_to(BASE_DIR)}")
|
|
return tsx_file
|
|
|
|
|
|
def find_matching_sprite(sprite_pattern, sprite_dir):
|
|
"""
|
|
Finds PNG files matching a pattern in the sprite directory
|
|
|
|
Args:
|
|
sprite_pattern: Pattern to search for (e.g., "kai_character_2x2")
|
|
sprite_dir: Directory to search in
|
|
|
|
Returns:
|
|
List of matching PNG files
|
|
"""
|
|
matches = []
|
|
for png_file in sprite_dir.glob("*.png"):
|
|
if sprite_pattern.lower() in png_file.stem.lower():
|
|
matches.append(png_file)
|
|
return matches
|
|
|
|
|
|
def organize_tilesets():
|
|
"""
|
|
Main function to organize all sprite sheets into categorized TSX files
|
|
"""
|
|
print("🗺️ TILED BATCH TILESET ORGANIZER")
|
|
print("=" * 60)
|
|
print(f"📂 Source: {KRVAVA_SPRITES_DIR}")
|
|
print(f"📂 Output: {TILESETS_OUTPUT_DIR}")
|
|
print("=" * 60)
|
|
print()
|
|
|
|
total_processed = 0
|
|
total_found = 0
|
|
|
|
for category, sprite_patterns in CATEGORIES.items():
|
|
print(f"\n📁 {category}")
|
|
print("-" * 60)
|
|
|
|
for pattern in sprite_patterns:
|
|
# Find matching sprites
|
|
matches = find_matching_sprite(pattern, KRVAVA_SPRITES_DIR)
|
|
|
|
if not matches:
|
|
print(f" ⚠️ No match for: {pattern}")
|
|
continue
|
|
|
|
total_found += len(matches)
|
|
|
|
# Create TSX for each match
|
|
for png_path in matches:
|
|
tsx_created = create_tsx_from_png(png_path, category, TILESETS_OUTPUT_DIR)
|
|
if tsx_created:
|
|
total_processed += 1
|
|
|
|
print("\n" + "=" * 60)
|
|
print(f"✅ COMPLETE!")
|
|
print(f"📊 Found: {total_found} sprite sheets")
|
|
print(f"📊 Processed: {total_processed} TSX files")
|
|
print(f"📂 Output: {TILESETS_OUTPUT_DIR}")
|
|
print("=" * 60)
|
|
print()
|
|
print("🎯 NEXT STEPS:")
|
|
print("1. Open Tiled Map Editor")
|
|
print("2. Open your map (e.g., micro_farm_128x128.tmx)")
|
|
print("3. Map → Add External Tileset...")
|
|
print("4. Navigate to assets/maps/organized_tilesets/")
|
|
print("5. Select category folder and import TSX files")
|
|
print("=" * 60)
|
|
|
|
|
|
def create_category_readme():
|
|
"""
|
|
Creates a README in each category folder explaining contents
|
|
"""
|
|
for category, patterns in CATEGORIES.items():
|
|
category_dir = TILESETS_OUTPUT_DIR / category
|
|
readme_path = category_dir / "README.md"
|
|
|
|
content = f"# {category.replace('_', ' ').title()}\n\n"
|
|
content += "## Sprite Sheets Included:\n\n"
|
|
for pattern in patterns:
|
|
content += f"- `{pattern}`\n"
|
|
|
|
content += "\n## Usage in Tiled:\n\n"
|
|
content += "1. Map → Add External Tileset...\n"
|
|
content += "2. Select .tsx files from this folder\n"
|
|
content += "3. Tilesets will appear in your Tilesets panel\n"
|
|
|
|
if category_dir.exists():
|
|
with open(readme_path, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
organize_tilesets()
|
|
create_category_readme()
|
|
except Exception as e:
|
|
print(f"❌ ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|