#!/usr/bin/env python3 """ TALL GRASS ANIMATED GENERATOR Creates individual animated tall grass tufts for Stardew-style harvesting. Features: - Transparent background (prosojen backdrop) - Wind sway animation (4 frames) - Harvestable property (isHarvestable: true) - No LSD glitching (smooth, subtle animation) Output: Tall_Grass_Animated.tsx + PNG """ import os from PIL import Image, ImageDraw, ImageFilter, ImageEnhance import xml.etree.ElementTree as ET def generate_grass_tuft(width, height, grass_color, sway_offset=0): """ Generate a single grass tuft sprite with wind sway. Args: width: Tuft width in pixels height: Tuft height in pixels grass_color: RGB tuple for grass color sway_offset: Horizontal offset for wind animation (-3 to +3 pixels) Returns: PIL Image with transparent background """ img = Image.new('RGBA', (width, height), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Grass blade parameters num_blades = 8 blade_width = 3 blade_height = height - 4 # Draw individual grass blades for i in range(num_blades): x_base = (i / num_blades) * width # Add sway offset (wind effect) x_top = x_base + sway_offset # Blade color variation (darker at base, lighter at tip) r, g, b = grass_color shade_factor = 0.7 + (i / num_blades) * 0.3 blade_color = ( int(r * shade_factor), int(g * shade_factor), int(b * shade_factor), 255 ) # Draw blade as tapered polygon blade_points = [ (x_base, height), # Base left (x_base + blade_width, height), # Base right (x_top + blade_width/2, 2) # Top (tapered) ] draw.polygon(blade_points, fill=blade_color) # Apply slight blur for softer edges (anti-aliasing) img = img.filter(ImageFilter.SMOOTH) return img def generate_tall_grass_animated(): """Generate full Tall_Grass_Animated tileset with 4-frame wind animation.""" base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) out_dir = os.path.join(base_dir, 'assets', 'maps', 'tilesets') os.makedirs(out_dir, exist_ok=True) # Tile params tile_size = 32 tuft_width = 24 tuft_height = 28 # Grass color (vibrant green) grass_color = (60, 180, 50) # 4 animation frames (wind sway) sway_offsets = [0, -2, 0, 2] # Gentle sway (NO LSD!) # Output tileset: 4 columns x 1 row (4 frames) output_width = 4 * tile_size output_height = 1 * tile_size output = Image.new('RGBA', (output_width, output_height), (0, 0, 0, 0)) print("🌾 Generating Tall_Grass_Animated (4 frames)...") for frame_idx, sway in enumerate(sway_offsets): # Generate grass tuft tuft = generate_grass_tuft(tuft_width, tuft_height, grass_color, sway) # Create tile-sized container with centered tuft tile = Image.new('RGBA', (tile_size, tile_size), (0, 0, 0, 0)) # Center tuft in tile offset_x = (tile_size - tuft_width) // 2 offset_y = tile_size - tuft_height tile.paste(tuft, (offset_x, offset_y), tuft) # Place in output output.paste(tile, (frame_idx * tile_size, 0)) print(f" āœ… Frame {frame_idx + 1}: Sway offset {sway}px") # Save PNG out_png = os.path.join(out_dir, 'Tall_Grass_Animated.png') output.save(out_png) print(f" āœ… Saved: {out_png}") # Generate TSX with animation + harvestable property generate_tall_grass_tsx(out_dir) print(f" āœ… TSX created: Tall_Grass_Animated.tsx\n") def generate_tall_grass_tsx(out_dir): """Generate .tsx with animation definitions and harvestable property.""" root = ET.Element('tileset', version="1.10", tiledversion="1.11.0") root.set('name', 'Tall_Grass_Animated') root.set('tilewidth', '32') root.set('tileheight', '32') root.set('tilecount', '4') root.set('columns', '4') img = ET.SubElement(root, 'image') img.set('source', 'Tall_Grass_Animated.png') img.set('width', '128') img.set('height', '32') # Tile 0: Animated with harvestable property tile = ET.SubElement(root, 'tile', id='0') # Properties props = ET.SubElement(tile, 'properties') prop = ET.SubElement(props, 'property', name='isHarvestable', type='bool', value='true') # Animation (4 frames, 250ms each = smooth wind) anim = ET.SubElement(tile, 'animation') for frame_id in range(4): frame = ET.SubElement(anim, 'frame', tileid=str(frame_id), duration='250') tree = ET.ElementTree(root) ET.indent(tree, space=' ', level=0) tsx_path = os.path.join(out_dir, 'Tall_Grass_Animated.tsx') tree.write(tsx_path, encoding='UTF-8', xml_declaration=True) if __name__ == '__main__': print("🌾 GENERATING TALL GRASS ANIMATED TILESET...\n") generate_tall_grass_animated() print("šŸŽ‰ TALL GRASS COMPLETE!") print("\nšŸ“– HOW TO USE IN TILED:") print("1. Add Tall_Grass_Animated.tsx to your map") print("2. Paint grass tufts on a separate layer") print("3. Each tuft has isHarvestable: true") print("4. In game, Kai can harvest with scythe!") print("\nāš ļø REMEMBER: Keep ground layer STATIC (no animation waste)!")