Cherry-picked from new-newnassets (3 commits): -bdf5a8dAdd new start animation asset for game launch -5f2d09fFix rat animations and restore map tiles -2e2c5bbfeat: add LPC-style rat assets, sprite generation scripts, and grid extraction tools Conflicts resolved: - engine/sdl2.py: kept 'if scale:' wrapper (compatible, default scale=True) - units/rat.py: kept animation frame computation (partial_move * 4) - 12 PNG files (BMP_BABY/MALE/FEMALE_*): kept new-newnassets versions Cleanup applied: removed ~480 working artifacts not appropriate for the production repo: - 25 PNG in repo root (male_rats*.png, spritesheet*.png) - 416 tile extracts in tools/lpc_extract/grid/ - scattered debug PNGs (bushes, preview, montages, etc.) - early iteration scripts (animate_rat v1-v4, restyle v2-v10, etc.) Kept: 32 new LPC-style sprites in assets/Rat_LPC_Style/, 4 animation sprite sheets in assets/Rat_Animated/, 64 updated Rat sprites, 22 final-version generator scripts, plus all .py/.md updates.
216 lines
9.2 KiB
Python
216 lines
9.2 KiB
Python
import glob
|
|
import os
|
|
import random
|
|
import math
|
|
from PIL import Image
|
|
|
|
# Vibrant colors closer to the original 8-bit sprites, but rich and organic
|
|
C_DARK = (15, 110, 35, 255)
|
|
C_MED = (30, 155, 45, 255)
|
|
C_LIGHT = (45, 195, 60, 255)
|
|
C_BRIGHT = (70, 230, 80, 255)
|
|
C_SHADOW = (20, 50, 30, 100) # Softer, greener shadow instead of black/purple
|
|
C_SHADOW_SOFT = (20, 50, 30, 50)
|
|
C_DIRT = (128, 128, 128, 255)
|
|
|
|
def draw_wrapped_circle(pixels, w, h, cx, cy, r, color):
|
|
for y in range(int(cy - r - 1), int(cy + r + 2)):
|
|
for x in range(int(cx - r - 1), int(cx + r + 2)):
|
|
d = math.hypot(x - cx, y - cy)
|
|
if d <= r:
|
|
pixels[x % w, y % h] = color
|
|
|
|
def generate_seamless_hedge(w, h):
|
|
img = Image.new('RGBA', (w, h), C_MED)
|
|
pixels = img.load()
|
|
|
|
# Large clusters of base grass color
|
|
for _ in range((w * h) // 16):
|
|
cx, cy = random.uniform(0, w), random.uniform(0, h)
|
|
draw_wrapped_circle(pixels, w, h, cx, cy, random.uniform(2.0, 3.5), C_LIGHT)
|
|
|
|
# Highlights
|
|
for _ in range((w * h) // 32):
|
|
cx, cy = random.uniform(0, w), random.uniform(0, h)
|
|
draw_wrapped_circle(pixels, w, h, cx, cy, random.uniform(1.0, 2.0), C_BRIGHT)
|
|
|
|
# Tiny dark gaps
|
|
for _ in range((w * h) // 80):
|
|
cx, cy = random.uniform(0, w), random.uniform(0, h)
|
|
draw_wrapped_circle(pixels, w, h, cx, cy, random.uniform(0.5, 1.2), C_DARK)
|
|
|
|
return img
|
|
|
|
def get_class(rgb):
|
|
if rgb in [(0, 128, 0), (0, 255, 0)]:
|
|
return 'GRASS'
|
|
return 'DIRT'
|
|
|
|
def alpha_blend(c1, c2):
|
|
a = c1[3] / 255.0
|
|
r = int(c1[0] * a + c2[0] * (1 - a))
|
|
g = int(c1[1] * a + c2[1] * (1 - a))
|
|
b = int(c1[2] * a + c2[2] * (1 - a))
|
|
return (r, g, b, 255)
|
|
|
|
input_files = glob.glob('/home/enne2/dev/mice/assets/Rat/*BMP_1*.png')
|
|
out_dir = '/home/enne2/dev/mice/assets/Rat_LPC_Style/'
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
|
|
base_hedge_64 = generate_seamless_hedge(64, 64)
|
|
|
|
for f in input_files:
|
|
basename = os.path.basename(f)
|
|
img = Image.open(f).convert('RGBA')
|
|
width, height = img.size
|
|
pixels = img.load()
|
|
|
|
out_img = Image.new('RGBA', (width, height))
|
|
out_pixels = out_img.load()
|
|
|
|
is_fx = 'EXPLOSION' in basename or 'GAS' in basename
|
|
is_flower = 'FLOWER' in basename
|
|
|
|
mask = {}
|
|
for y in range(height):
|
|
for x in range(width):
|
|
px = pixels[x, y][:3]
|
|
cls = get_class(px)
|
|
if is_flower:
|
|
mask[(x, y)] = 'GRASS'
|
|
elif is_fx:
|
|
if cls == 'GRASS':
|
|
mask[(x, y)] = 'GRASS'
|
|
else:
|
|
mask[(x, y)] = 'DIRT'
|
|
else:
|
|
mask[(x, y)] = cls
|
|
|
|
tile_hedge = base_hedge_64.copy()
|
|
tile_pixels = tile_hedge.load()
|
|
|
|
if basename == 'BMP_1_GRASS_2.png':
|
|
for _ in range(8):
|
|
cx, cy = random.randint(16, 48), random.randint(16, 48)
|
|
tile_pixels[cx, cy] = (220, 50, 50, 255)
|
|
elif basename == 'BMP_1_GRASS_3.png':
|
|
for _ in range(12):
|
|
cx, cy = random.randint(16, 48), random.randint(16, 48)
|
|
tile_pixels[cx, cy] = (255, 255, 255, 255)
|
|
elif basename == 'BMP_1_GRASS_4.png':
|
|
for _ in range(20):
|
|
cx, cy = random.randint(20, 44), random.randint(20, 44)
|
|
draw_wrapped_circle(tile_pixels, 64, 64, cx, cy, random.uniform(1.0, 2.5), C_MED)
|
|
|
|
for y in range(height):
|
|
for x in range(width):
|
|
if mask[(x, y)] == 'GRASS':
|
|
dist_to_dirt = 999
|
|
for dy in range(-3, 4):
|
|
for dx in range(-3, 4):
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < width and 0 <= ny < height:
|
|
if mask[(nx, ny)] == 'DIRT':
|
|
d = math.hypot(dx, dy)
|
|
if d < dist_to_dirt:
|
|
dist_to_dirt = d
|
|
|
|
if dist_to_dirt <= 1.5:
|
|
dirt_below = (y+1 < height and mask[(x, y+1)] == 'DIRT') or (y+2 < height and mask[(x, y+2)] == 'DIRT')
|
|
if dirt_below:
|
|
# Solid dark green outline on the bottom (no more dashes!)
|
|
out_pixels[x, y] = C_DARK
|
|
else:
|
|
out_pixels[x, y] = C_MED
|
|
elif dist_to_dirt <= 2.5:
|
|
out_pixels[x, y] = C_MED
|
|
else:
|
|
out_pixels[x, y] = tile_pixels[x % 64, y % 64]
|
|
else:
|
|
orig_color = pixels[x, y][:3]
|
|
if orig_color == (0, 0, 0) and not is_fx:
|
|
# Replace debug dashed lines with dirt
|
|
orig_color = C_DIRT[:3]
|
|
|
|
if orig_color == (0, 0, 0):
|
|
out_pixels[x, y] = (0, 0, 0, 255)
|
|
elif orig_color in [(128,128,128), (192,192,192)]:
|
|
dist_to_grass = 999
|
|
for dy in range(-2, 3):
|
|
for dx in range(-2, 3):
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < width and 0 <= ny < height:
|
|
if mask[(nx, ny)] == 'GRASS':
|
|
d = math.hypot(dx, dy)
|
|
if d < dist_to_grass:
|
|
dist_to_grass = d
|
|
base_c = orig_color
|
|
if dist_to_grass <= 1.5:
|
|
out_pixels[x, y] = alpha_blend(C_SHADOW, base_c)
|
|
elif dist_to_grass <= 2.5:
|
|
out_pixels[x, y] = alpha_blend(C_SHADOW_SOFT, base_c)
|
|
else:
|
|
out_pixels[x, y] = base_c + (255,)
|
|
else:
|
|
out_pixels[x, y] = orig_color + (255,)
|
|
|
|
# Flowers logic
|
|
if is_flower:
|
|
def draw_circle(cx, cy, r, color, outline=None):
|
|
for fy in range(cy - r - 1, cy + r + 2):
|
|
for fx in range(cx - r - 1, cx + r + 2):
|
|
if 0 <= fx < width and 0 <= fy < height:
|
|
d = math.hypot(fx - cx, fy - cy)
|
|
if d <= r:
|
|
out_pixels[fx, fy] = color
|
|
elif outline and r < d <= r + 1.2:
|
|
if out_pixels[fx, fy] != color:
|
|
out_pixels[fx, fy] = outline
|
|
|
|
def draw_petal(cx, cy, angle, length, pw, color, outline):
|
|
cos_a, sin_a = math.cos(angle), math.sin(angle)
|
|
size = int(length + pw)
|
|
for fy in range(cy - size, cy + size + 1):
|
|
for fx in range(cx - size, cx + size + 1):
|
|
if 0 <= fx < width and 0 <= fy < height:
|
|
dx, dy = fx - cx, fy - cy
|
|
rx = dx * cos_a + dy * sin_a
|
|
ry = -dx * sin_a + dy * cos_a
|
|
if length > 0 and pw > 0:
|
|
val = (rx / length)**2 + (ry / pw)**2
|
|
if val <= 1.0:
|
|
out_pixels[fx, fy] = color
|
|
elif val <= 1.5:
|
|
if out_pixels[fx, fy] != color:
|
|
out_pixels[fx, fy] = outline
|
|
|
|
def draw_large_flower(cx, cy, petal_color, outline_color, center_color, num_petals, petal_length, petal_width):
|
|
draw_circle(cx, cy + 3, petal_length + 1, (20, 50, 20, 200))
|
|
for i in range(num_petals):
|
|
angle = i * (2 * math.pi / num_petals)
|
|
pcolor = (int(petal_color[0]*0.8), int(petal_color[1]*0.8), int(petal_color[2]*0.8), 255) if math.sin(angle) > 0 else petal_color
|
|
draw_petal(cx, cy, angle, petal_length, petal_width, pcolor, outline_color)
|
|
draw_circle(cx, cy, petal_width + 1, center_color, (int(center_color[0]*0.6), int(center_color[1]*0.6), int(center_color[2]*0.6), 255))
|
|
|
|
if 'FLOWER_1' in basename:
|
|
draw_large_flower(32, 32, (240, 240, 240, 255), (100, 100, 110, 255), (240, 200, 50, 255), 10, 14, 5)
|
|
elif 'FLOWER_2' in basename:
|
|
draw_large_flower(16, 16, (200, 40, 40, 255), (80, 20, 20, 255), (240, 200, 50, 255), 5, 7, 4)
|
|
draw_large_flower(48, 20, (200, 40, 40, 255), (80, 20, 20, 255), (240, 200, 50, 255), 5, 7, 4)
|
|
draw_large_flower(32, 48, (200, 40, 40, 255), (80, 20, 20, 255), (240, 200, 50, 255), 5, 7, 4)
|
|
elif 'FLOWER_3' in basename:
|
|
draw_large_flower(32, 32, (220, 80, 180, 255), (100, 30, 80, 255), (240, 200, 50, 255), 8, 12, 6)
|
|
elif 'FLOWER_4' in basename:
|
|
draw_large_flower(32, 32, (80, 150, 230, 255), (30, 70, 120, 255), (240, 200, 50, 255), 8, 12, 6)
|
|
|
|
if is_fx:
|
|
for y in range(height):
|
|
for x in range(width):
|
|
px = pixels[x, y][:3]
|
|
if get_class(px) != 'GRASS' and px not in [(128,128,128), (192,192,192), (0,0,0)]:
|
|
out_pixels[x, y] = px + (255,)
|
|
|
|
out_img.save(os.path.join(out_dir, basename))
|
|
|
|
print("V11 - Vibrant 8-bit-like colors and NO dashed lines!")
|