""" πŸ—ΊοΈ 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()