+ ๐ Total Assets: {total} |
+ ๐ Categories: {len([c for c in categorized if categorized[c]])}
+
+"""
+
+ for category in sorted(categorized.keys()):
+ assets = categorized[category]
+ if not assets:
+ continue
+
+ # Sort by name
+ assets.sort(key=lambda x: x['name'])
+
+ html += f"""
+
+
+ {category}
+ ({len(assets)} assets)
+
+
+"""
+
+ for asset in assets:
+ html += f"""
+
+
+
{asset['name']}
+
+
+"""
+
+ html += """
+
+
+"""
+
+ html += """
+
+
+
+"""
+
+ with open('asset_gallery_full.html', 'w', encoding='utf-8') as f:
+ f.write(html)
+
+ print(f"\nโ Gallery created: asset_gallery_full.html")
+ print(f" {total} assets in {len([c for c in categorized if categorized[c]])} categories")
+ print(f"\n๐ฏ Double-click to open (works offline!)")
+
+if __name__ == "__main__":
+ categorized = scan_fast()
+ generate_html(categorized)
diff --git a/generate_static_gallery.py b/generate_static_gallery.py
new file mode 100644
index 000000000..66f73326a
--- /dev/null
+++ b/generate_static_gallery.py
@@ -0,0 +1,297 @@
+#!/usr/bin/env python3
+"""
+Static Asset Gallery Generator
+Generates self-contained HTML galleries for all game assets.
+Works offline (file://) - just double-click to open!
+"""
+
+import os
+import json
+from pathlib import Path
+from collections import defaultdict
+
+# Categories with their detection patterns
+CATEGORIES = {
+ "Ground & Terrain": ["ground", "grass", "dirt", "soil", "water", "teren"],
+ "Crops & Farming": ["crop", "wheat", "potato", "corn", "seed", "harvest", "pridelek"],
+ "Trees & Nature": ["tree", "apple", "cherry", "dead", "bush", "drevo", "narava"],
+ "Props & Objects": ["fence", "chest", "barrel", "scooter", "prop", "objekt"],
+ "Buildings": ["house", "barn", "building", "zgradba", "hisa"],
+ "Characters & NPCs": ["character", "npc", "kai", "ana", "gronk", "zombie", "lik"],
+ "Effects & VFX": ["blood", "fog", "rain", "vfx", "effect", "kri"],
+ "UI & Icons": ["icon", "ui", "button", "ikona"],
+ "Tools & Items": ["tool", "hoe", "axe", "pickaxe", "orodi", "item"],
+ "Other": []
+}
+
+def categorize_file(filepath):
+ """Determine category based on file path and name."""
+ path_lower = str(filepath).lower()
+ filename_lower = filepath.name.lower()
+
+ for category, patterns in CATEGORIES.items():
+ if category == "Other":
+ continue
+ for pattern in patterns:
+ if pattern in path_lower or pattern in filename_lower:
+ return category
+
+ return "Other"
+
+def scan_assets(base_dir="assets"):
+ """Scan all PNG files and categorize them."""
+ categorized = defaultdict(list)
+ base_path = Path(base_dir)
+
+ for png_file in base_path.rglob("*.png"):
+ # Skip backup folders
+ if "BACKUP" in str(png_file):
+ continue
+
+ category = categorize_file(png_file)
+ relative_path = png_file.relative_to(Path.cwd())
+
+ categorized[category].append({
+ "path": str(relative_path),
+ "name": png_file.name,
+ "size": png_file.stat().st_size if png_file.exists() else 0
+ })
+
+ # Sort each category by name
+ for category in categorized:
+ categorized[category].sort(key=lambda x: x["name"])
+
+ return dict(categorized)
+
+def generate_html(categorized_assets, output_file="asset_gallery.html"):
+ """Generate self-contained HTML gallery."""
+
+ html = f"""
+
+
+
+
+ Mrtva Dolina - Asset Gallery
+
+
+
+
+
๐ MRTVA DOLINA ๐
+
Asset Gallery - Static Offline Version
+
+
+
+ Total Assets: {sum(len(assets) for assets in categorized_assets.values())} |
+ Categories: {len([c for c in categorized_assets if categorized_assets[c]])}
+
+"""
+
+ for category, assets in sorted(categorized_assets.items()):
+ if not assets:
+ continue
+
+ html += f"""
+
+
+ {category}
+ ({len(assets)} assets)
+
+
+"""
+
+ for asset in assets:
+ size_kb = asset['size'] / 1024
+ html += f"""
+
+
+
{asset['name']}
+
{size_kb:.1f} KB
+
+
+"""
+
+ html += """
+
+
+"""
+
+ html += """
+
+
+
+"""
+
+ with open(output_file, 'w', encoding='utf-8') as f:
+ f.write(html)
+
+ print(f"โ Gallery generated: {output_file}")
+ print(f" Total assets: {sum(len(assets) for assets in categorized_assets.values())}")
+ print(f" Categories: {len([c for c in categorized_assets if categorized_assets[c]])}")
+ print(f"\n๐ฏ Just double-click the HTML file to open it!")
+
+if __name__ == "__main__":
+ print("๐จ Scanning assets...")
+ categorized = scan_assets()
+
+ print("๐ Generating HTML gallery...")
+ generate_html(categorized)
+
+ print("\nโจ Done! Open 'asset_gallery.html' in your browser.")
diff --git a/inject_tilesets.py b/inject_tilesets.py
new file mode 100644
index 000000000..3a2eb0da9
--- /dev/null
+++ b/inject_tilesets.py
@@ -0,0 +1,99 @@
+
+import xml.etree.ElementTree as ET
+
+tmx_file = 'assets/maps/Faza1_Finalna_v2.tmx'
+tree = ET.parse(tmx_file)
+root = tree.getroot()
+
+# Find the last tileset to calculate the next firstgid
+last_gid = 1
+for tileset in root.findall('tileset'):
+ firstgid = int(tileset.get('firstgid'))
+ # This logic is a bit flawed if tilesets are not in order, but usuall good enough.
+ # Better: finding max(firstgid + tilecount)
+ # We will assume existing ones are okay and append to the end.
+
+ # We need to know the tilecount, which might be in the image dimensions.
+ # Simplified: We will just pick a high starting GID for new ones to avoid collision.
+ # Existing max GID seems to be around 7000 (from previous logs).
+ pass
+
+next_gid = 10000
+
+new_tilesets = [
+ {
+ 'firstgid': next_gid,
+ 'name': 'Ground_Tilled',
+ 'tilewidth': 32,
+ 'tileheight': 32,
+ 'tilecount': 1,
+ 'columns': 1,
+ 'image': 'tilesets_source/ground/soil_tilled.png',
+ 'imagewidth': 32,
+ 'imageheight': 32
+ },
+ {
+ 'firstgid': next_gid + 100,
+ 'name': 'Farming_Potato', // Collection of images approach is better for single sprites, but let's try standard
+ 'tilewidth': 32,
+ 'tileheight': 32,
+ 'tilecount': 5,
+ 'columns': 5,
+ 'image': 'tilesets_source/crops/potato_stage5.png', # Placeholder logic: TMX usually links to one image per tileset or a collection.
+ # Since we have individual files, we should create a "Collection of Images" tileset or merge them.
+ # For simplicity in this script, I will just add single image tilesets for the key items.
+ 'imagewidth': 32,
+ 'imageheight': 32
+ },
+ {
+ 'firstgid': next_gid + 200,
+ 'name': 'Props_Farm',
+ 'tilewidth': 32,
+ 'tileheight': 32,
+ 'tilecount': 1,
+ 'columns': 1,
+ 'image': 'tilesets_source/props/chest_closed.png',
+ 'imagewidth': 32,
+ 'imageheight': 32
+ },
+ {
+ 'firstgid': next_gid + 300,
+ 'name': 'Decals_Blood',
+ 'tilewidth': 32,
+ 'tileheight': 32,
+ 'tilecount': 1,
+ 'columns': 1,
+ 'image': 'tilesets_source/decals/blood_pool.png',
+ 'imagewidth': 32,
+ 'imageheight': 32
+ },
+ {
+ 'firstgid': next_gid + 400,
+ 'name': 'Collision_Red',
+ 'tilewidth': 32,
+ 'tileheight': 32,
+ 'tilecount': 1,
+ 'columns': 1,
+ 'image': 'collision_red.png',
+ 'imagewidth': 32,
+ 'imageheight': 32
+ }
+]
+
+# Append new tilesets
+for ts in new_tilesets:
+ new_node = ET.SubElement(root, 'tileset')
+ new_node.set('firstgid', str(ts['firstgid']))
+ new_node.set('name', ts['name'])
+ new_node.set('tilewidth', str(ts['tilewidth']))
+ new_node.set('tileheight', str(ts['tileheight']))
+ new_node.set('tilecount', str(ts['tilecount']))
+ new_node.set('columns', str(ts['columns']))
+
+ img_node = ET.SubElement(new_node, 'image')
+ img_node.set('source', ts['image'])
+ img_node.set('width', str(ts['imagewidth']))
+ img_node.set('height', str(ts['imageheight']))
+
+tree.write(tmx_file)
+print("Done injecting tilesets.")
diff --git a/novafarma.tiled-project b/novafarma.tiled-project
index cb6bd0d2a..f3321ab8d 100644
--- a/novafarma.tiled-project
+++ b/novafarma.tiled-project
@@ -2,23 +2,29 @@
"automappingRulesFile": "",
"commands": [
],
- "compatibilityVersion": 1.1,
"extensionsPath": "extensions",
"folders": [
"assets/maps",
"assets/tilesets"
],
+ "properties": [
+ ],
"propertyTypes": [
{
+ "color": "#ffa0a0a4",
+ "drawFill": true,
"id": 1,
- "name": "SpawnPoint",
- "type": "class",
"members": [
{
"name": "entityType",
"type": "string",
"value": ""
}
+ ],
+ "name": "SpawnPoint",
+ "type": "class",
+ "useAs": [
+ "property"
]
}
]
diff --git a/novafarma.tiled-session b/novafarma.tiled-session
index e99721382..fba2ecfc1 100644
--- a/novafarma.tiled-session
+++ b/novafarma.tiled-session
@@ -3,7 +3,7 @@
"height": 4300,
"width": 2
},
- "activeFile": "",
+ "activeFile": "assets/maps/Faza1_Finalna.json",
"expandedProjectPaths": [
"assets/maps"
],
@@ -95,6 +95,44 @@
"assets/maps/05_Tools_Items.tsx": {
"scaleInDock": 1
},
+ "assets/maps/Faza1_Finalna.json": {
+ "scale": 0.1619,
+ "selectedLayer": 0,
+ "viewCenter": {
+ "x": 1948.733786287832,
+ "y": 1852.9956763434218
+ }
+ },
+ "assets/maps/Faza1_Finalna.json#Fence_Corner": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#Fence_Horizontal": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#Fence_Vertical": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#House_Gothic": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#Terrain_Dirt": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#Terrain_Grass": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#Terrain_Water": {
+ "scaleInDock": 0.18
+ },
+ "assets/maps/Faza1_Finalna.json#Tree_Apple": {
+ "scaleInDock": 0.4228
+ },
+ "assets/maps/Faza1_Finalna.json#Tree_Cherry": {
+ "scaleInDock": 0.1617
+ },
+ "assets/maps/Faza1_Finalna.json#Tree_Dead": {
+ "scaleInDock": 0.18
+ },
"assets/maps/NovaFarma.json": {
"scale": 0.12779541015624998,
"selectedLayer": 1,
@@ -349,13 +387,15 @@
"last.externalTilesetPath": "/Users/davidkotnik/repos/novafarma/assets/maps",
"map.height": 80,
"map.lastUsedExportFilter": "JSON map files (*.tmj *.json)",
- "map.lastUsedFormat": "tmx",
+ "map.lastUsedFormat": "json",
"map.layerDataFormat": "3",
"map.width": 80,
"openFiles": [
+ "assets/maps/Faza1_Finalna.json"
],
"project": "novafarma.tiled-project",
"recentFiles": [
+ "assets/maps/Faza1_Finalna.json",
"assets/tiled/probna farma numero uno .tmx",
"assets/tiled/faza1_kmetija.json",
"assets/maps ๐ฃ/MINIMAL_TEMPLATE.tmx",
diff --git a/remove_checkerboard.py b/remove_checkerboard.py
new file mode 100644
index 000000000..931600da7
--- /dev/null
+++ b/remove_checkerboard.py
@@ -0,0 +1,97 @@
+
+import os
+import sys
+from PIL import Image
+import numpy as np
+
+def remove_background(image_path, tolerance=30):
+ try:
+ img = Image.open(image_path).convert("RGBA")
+ datas = img.getdata()
+
+ # Sample the corner pixel to guess the "background" theme
+ # But since it's a checkerboard, precise color matching fails.
+ # We'll use a seed-fill (flood fill) approach from the corners.
+
+ # Convert to numpy array for faster processing
+ arr = np.array(img)
+
+ # Create a mask for visited pixels (background)
+ h, w = arr.shape[:2]
+ mask = np.zeros((h, w), dtype=bool)
+
+ # Stack for flood fill: (r, c)
+ stack = [(0, 0), (0, w-1), (h-1, 0), (h-1, w-1)]
+
+ # We need to be careful not to delete the object.
+ # Assumption: The checkerboard is grey-scale or near grey-scale.
+ # And the object (tree) is colorful (green/brown).
+
+ # Helper to check if a pixel is "grey-ish" (background candidate)
+ def is_background_candidate(pixel):
+ r, g, b, a = pixel
+ # Check for low saturation (grey/white/black)
+ # max(r,g,b) - min(r,g,b) should be small for greys
+ saturation = max(r, g, b) - min(r, g, b)
+ return saturation < tolerance
+
+ # New approach: smart flood fill with color tolerance is hard on noisy checkerboard.
+ # Let's try a simpler heuristic first:
+ # Iterate all pixels. If a pixel is GREY-SCALE (within tolerance), make it transparent.
+ # This risks deleting grey parts of the object (e.g. stones, bark).
+
+ newData = []
+ for item in datas:
+ # item is (r, g, b, a)
+ r, g, b, a = item
+
+ # Check if it's a grey/white/black pixel
+ # Checkerboard usually consists of light grey/white and dark grey blocks.
+ # We enforce that R, G, and B are close to each other.
+ if abs(r - g) < tolerance and abs(g - b) < tolerance and abs(r - b) < tolerance:
+ # Also check brightness to avoid deleting dark black outlines if they are pure black
+ # Let's say we only delete "light" greys/whites?
+ # The "dark" squares in the checkerboard from the log were around (77, 77, 77).
+ # This is quite dark.
+
+ # If we just delete all greys, we might lose outlines (usually (0,0,0)).
+ # So we check if it's NOT pure black.
+ if r > 20 and g > 20 and b > 20:
+ newData.append((0, 0, 0, 0)) # Transparent
+ else:
+ newData.append(item)
+ else:
+ newData.append(item)
+
+ img.putdata(newData)
+
+ # Save back
+ img.save(image_path, "PNG")
+ print(f"Processed: {image_path}")
+ return True
+
+ except Exception as e:
+ print(f"Error processing {image_path}: {e}")
+ return False
+
+# List of specific files to fix first (Trees)
+targets = [
+ "assets/references/trees/apple/apple_tree.png"
+]
+
+# Or scan directory
+target_dir = "assets/references"
+
+if __name__ == "__main__":
+ # If arguments provided, use them
+ if len(sys.argv) > 1:
+ files = sys.argv[1:]
+ for f in files:
+ remove_background(f)
+ else:
+ # Recursive scan for testing
+ for root, dirs, files in os.walk(target_dir):
+ for file in files:
+ if file.endswith("apple_tree.png"): # Safety: only target the known bad one first
+ full_path = os.path.join(root, file)
+ remove_background(full_path)
diff --git a/scripts/generate_asset_gallery.py b/scripts/generate_asset_gallery.py
new file mode 100644
index 000000000..eed233466
--- /dev/null
+++ b/scripts/generate_asset_gallery.py
@@ -0,0 +1,454 @@
+#!/usr/bin/env python3
+"""
+Generate complete asset gallery HTML with all images
+"""
+import os
+from pathlib import Path
+from datetime import datetime
+
+def main():
+ assets_dir = Path("assets")
+
+ # Find all images
+ extensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif']
+ all_images = []
+
+ for ext in extensions:
+ all_images.extend(assets_dir.rglob(f"*{ext}"))
+ all_images.extend(assets_dir.rglob(f"*{ext.upper()}"))
+
+ # Remove duplicates and sort
+ all_images = sorted(set(str(p) for p in all_images))
+
+ print(f"Found {len(all_images)} images")
+
+ # Categorize images
+ categories = {}
+
+ def get_category(path):
+ path_lower = path.lower()
+
+ if 'phase_packs/0_demo' in path_lower:
+ if 'crops' in path_lower:
+ return '๐ฎ DEMO - Crops'
+ elif 'tools' in path_lower:
+ return '๐ฎ DEMO - Tools'
+ elif 'animals' in path_lower:
+ return '๐ฎ DEMO - Animals'
+ return '๐ฎ DEMO - Other'
+
+ elif 'phase_packs/1_faza_1' in path_lower:
+ if 'crops' in path_lower:
+ return '๐พ FAZA 1 - Crops'
+ elif 'animals' in path_lower:
+ return '๐พ FAZA 1 - Animals'
+ elif 'tools' in path_lower:
+ return '๐พ FAZA 1 - Tools'
+ elif 'infrastructure' in path_lower:
+ return '๐พ FAZA 1 - Infrastructure'
+ return '๐พ FAZA 1 - Other'
+
+ elif 'phase_packs/2_faza_2' in path_lower:
+ if 'buildings' in path_lower:
+ return '๐๏ธ FAZA 2 - Buildings'
+ elif 'npcs' in path_lower:
+ return '๐๏ธ FAZA 2 - NPCs'
+ elif 'infrastructure' in path_lower:
+ return '๐๏ธ FAZA 2 - Infrastructure'
+ return '๐๏ธ FAZA 2 - Other'
+
+ elif 'references/kai' in path_lower:
+ return '๐ค References - Kai'
+ elif 'references/ana' in path_lower:
+ return '๐ค References - Ana'
+ elif 'references/gronk' in path_lower:
+ return '๐ค References - Gronk'
+ elif 'references/susi' in path_lower:
+ return '๐ค References - Susi'
+ elif 'references/npcs' in path_lower:
+ return '๐ฅ References - NPCs'
+ elif 'references/creatures' in path_lower:
+ return '๐ References - Creatures'
+ elif 'references/buildings' in path_lower:
+ return '๐ References - Buildings'
+ elif 'references/trees' in path_lower:
+ return '๐ณ References - Trees'
+ elif 'references/ui' in path_lower:
+ return '๐จ References - UI'
+ elif 'references/' in path_lower:
+ return '๐ธ References - Other'
+
+ elif 'sprites' in path_lower:
+ return '๐ผ๏ธ Sprites'
+ elif 'crops' in path_lower:
+ return '๐ฑ Crops'
+ elif 'buildings' in path_lower:
+ return '๐ Buildings'
+ elif 'characters' in path_lower:
+ return '๐ค Characters'
+ elif 'grounds' in path_lower:
+ return '๐ Ground Tiles'
+ elif 'terrain' in path_lower:
+ return 'โฐ๏ธ Terrain'
+ elif 'ui' in path_lower:
+ return '๐จ UI'
+ elif 'vfx' in path_lower:
+ return 'โจ VFX'
+ elif 'props' in path_lower:
+ return '๐ช Props'
+ elif 'slike' in path_lower:
+ return '๐ผ๏ธ Slike'
+
+ return '๐ฆ Other'
+
+ for img_path in all_images:
+ cat = get_category(img_path)
+ if cat not in categories:
+ categories[cat] = []
+ categories[cat].append(img_path)
+
+ # Generate HTML
+ html = f'''
+
+
+
+ ๐ฎ NovaFarma - Vse Slike ({len(all_images)})
+
+
+
+
๐ฎ NOVAFARMA - VSE SLIKE ๐
+
+ {len(all_images)} slik v {len(categories)} kategorijah |
+ Generirano: {datetime.now().strftime("%d.%m.%Y %H:%M")}
+
+
+
+
+
+
+
+
+
๐ Kazalo kategorij
+
+'''
+
+ # Add TOC
+ for cat in sorted(categories.keys()):
+ safe_id = cat.replace(' ', '_').replace('-', '_')
+ html += f'