Files
novafarma/scripts/generate_tall_grass.py

165 lines
5.3 KiB
Python

#!/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)!")