442 lines
19 KiB
Python
442 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
"""Paint the sixteen faction fighters into assets/images/ships-enemy.png.
|
|
|
|
Frames 30-37 (row 3) are the APEX (the predators — angular, red-hot);
|
|
frames 40-47 (row 4) are the MINDWEAVE (the weavers — floating, violet-cold).
|
|
Each cell is 128x128 inside the 10x10 sheet. Every ship is drawn nose-UP
|
|
(the game rotates the frame at runtime).
|
|
|
|
Drawn at 4x supersample (512) and downscaled for clean edges, in the sheet's
|
|
existing idiom: dark warm hulls, bright accent lines, engine glow at the
|
|
bottom. The other cells (the pirates, the neutrals) are left untouched.
|
|
"""
|
|
import math
|
|
from PIL import Image, ImageDraw, ImageFilter
|
|
|
|
SHEET = 'assets/images/ships-enemy.png'
|
|
CELL = 128
|
|
SS = 4 # supersample
|
|
W = CELL * SS # 512 draw space
|
|
C = W / 2 # center
|
|
|
|
|
|
def lerp(a, b, t):
|
|
return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(3))
|
|
|
|
|
|
def vgrad(shape_pts, top, bottom):
|
|
"""A vertical two-tone fill: paint the polygon, then shade the lower
|
|
half toward `bottom` by compositing a semi-transparent band clipped to
|
|
the polygon. `shape_pts` in 512-space."""
|
|
return top, bottom
|
|
|
|
|
|
class Ship:
|
|
"""A 512-space canvas with helpers. All coords in 512-space."""
|
|
|
|
def __init__(self):
|
|
self.im = Image.new('RGBA', (W, W), (0, 0, 0, 0))
|
|
self.d = ImageDraw.Draw(self.im)
|
|
|
|
def poly(self, pts, fill):
|
|
self.d.polygon(pts, fill=fill)
|
|
|
|
def oval(self, box, fill=None, outline=None, width=0):
|
|
self.d.ellipse(box, fill=fill, outline=outline, width=width)
|
|
|
|
def line(self, a, b, fill, width=4):
|
|
self.d.line([a, b], fill=fill, width=width)
|
|
|
|
def arc(self, box, a0, a1, fill, width=6):
|
|
self.d.arc(box, a0, a1, fill=fill, width=width)
|
|
|
|
def sline(self, a, b, fill, w1, w2):
|
|
"""A tapered stripe (two-segment fake taper)."""
|
|
mx = (a[0] + b[0]) / 2
|
|
my = (a[1] + b[1]) / 2
|
|
self.line(a, (mx, my), fill, int((w1 + w2) / 2))
|
|
self.line((mx, my), b, fill, w2)
|
|
|
|
def glow_dot(self, x, y, r, color):
|
|
"""A radial glow (engine / orb), drawn as concentred fading rings."""
|
|
rgb = color[:3] if len(color) >= 3 else (255, 255, 255)
|
|
for i in range(r, 0, -1):
|
|
a = int(255 * (i / r) ** 2 * 0.9)
|
|
self.oval((x - i, y - i, x + i, y + i), fill=(rgb[0], rgb[1], rgb[2], a))
|
|
|
|
def finish(self):
|
|
return self.im.resize((CELL, CELL), Image.LANCZOS)
|
|
|
|
|
|
# ---------------------------------------------------------------- the APEX
|
|
# Angular, predatory, nose-up. Dark warm hulls; red-hot accents.
|
|
APEX_HULL = (74, 66, 70, 255)
|
|
APEX_DARK = (40, 34, 40, 255)
|
|
APEX_LITE = (110, 98, 104, 255)
|
|
APEX_EDGE = (150, 132, 140, 255)
|
|
APEX_RED = (216, 62, 44, 255)
|
|
APEX_ORANGE = (236, 128, 52, 255)
|
|
APEX_CANY = (26, 30, 38, 255)
|
|
APEX_ENGINE = (255, 150, 60)
|
|
|
|
|
|
def raptor():
|
|
"""30 — a narrow dart with a forward spine cannon and thin intakes."""
|
|
s = Ship()
|
|
s.poly([(C, 40), (C + 46, 300), (C + 70, 430), (C + 34, 470), (C - 34, 470), (C - 70, 430), (C - 46, 300)], APEX_HULL)
|
|
s.poly([(C, 40), (C + 30, 300), (C + 22, 430), (C - 22, 430), (C - 30, 300)], APEX_DARK)
|
|
s.poly([(C, 40), (C + 12, 220), (C - 12, 220)], APEX_ORANGE) # the spine
|
|
s.oval((C - 18, 150, C + 18, 190), fill=APEX_CANY) # the canopy
|
|
s.sline((C - 60, 330), (C - 84, 420), APEX_EDGE, 10, 16) # the intakes
|
|
s.sline((C + 60, 330), (C + 84, 420), APEX_EDGE, 10, 16)
|
|
s.poly([(C - 16, 452), (C - 40, 486), (C - 8, 486)], APEX_DARK) # the split tail
|
|
s.poly([(C + 16, 452), (C + 40, 486), (C + 8, 486)], APEX_DARK)
|
|
s.glow_dot(C - 22, 480, 14, APEX_ENGINE)
|
|
s.glow_dot(C + 22, 480, 14, APEX_ENGINE)
|
|
s.poly([(C, 40), (C + 10, 90), (C - 10, 90)], APEX_RED) # the nose tip
|
|
return s
|
|
|
|
|
|
def mauler():
|
|
"""31 — a broad hammer hull with the big forward cannon."""
|
|
s = Ship()
|
|
s.poly([(C, 70), (C + 78, 150), (C + 96, 330), (C + 60, 450), (C - 60, 450), (C - 96, 330), (C - 78, 150)], APEX_HULL)
|
|
s.poly([(C, 120), (C + 40, 180), (C + 46, 340), (C - 46, 340), (C - 40, 180)], APEX_DARK)
|
|
s.poly([(C - 26, 30), (C + 26, 30), (C + 20, 170), (C - 20, 170)], APEX_EDGE) # the cannon tube
|
|
s.oval((C - 34, 18, C + 34, 54), fill=APEX_DARK, outline=APEX_RED, width=8) # the muzzle
|
|
s.oval((C - 16, 190, C + 16, 230), fill=APEX_CANY)
|
|
s.poly([(C - 96, 330), (C - 130, 400), (C - 84, 420)], APEX_LITE) # the shoulder blades
|
|
s.poly([(C + 96, 330), (C + 130, 400), (C + 84, 420)], APEX_LITE)
|
|
s.line((C - 60, 250), (C - 60, 380), APEX_RED, 10)
|
|
s.line((C + 60, 250), (C + 60, 380), APEX_RED, 10)
|
|
s.poly([(C - 20, 430), (C - 34, 470), (C, 460)], APEX_DARK)
|
|
s.poly([(C + 20, 430), (C + 34, 470), (C, 460)], APEX_DARK)
|
|
s.glow_dot(C, 466, 16, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
def stalker():
|
|
"""32 — the cloaker: a long thin blade with four stealth fins."""
|
|
s = Ship()
|
|
s.poly([(C, 24), (C + 26, 220), (C + 40, 420), (C - 40, 420), (C - 26, 220)], APEX_HULL)
|
|
s.poly([(C, 24), (C + 12, 240), (C + 16, 400), (C - 16, 400), (C - 12, 240)], APEX_DARK)
|
|
for sx in (-1, 1):
|
|
s.poly([(C + sx * 30, 180), (C + sx * 120, 240), (C + sx * 96, 300), (C + sx * 34, 300)], APEX_LITE)
|
|
s.poly([(C + sx * 34, 320), (C + sx * 110, 380), (C + sx * 88, 420), (C + sx * 38, 420)], APEX_DARK)
|
|
s.line((C + sx * 36, 196), (C + sx * 108, 252), APEX_RED, 6)
|
|
s.oval((C - 10, 140, C + 10, 176), fill=APEX_CANY)
|
|
s.line((C, 60), (C, 200), APEX_ORANGE, 8)
|
|
s.poly([(C - 14, 410), (C, 470), (C + 14, 410)], APEX_DARK)
|
|
s.glow_dot(C, 462, 12, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
def screamer():
|
|
"""33 — the wide V wings with the spray nozzles along each edge."""
|
|
s = Ship()
|
|
s.poly([(C, 60), (C + 30, 160), (C + 44, 380), (C - 44, 380), (C - 30, 160)], APEX_HULL)
|
|
s.poly([(C - 26, 150), (C - 150, 330), (C - 120, 380), (C - 30, 300)], APEX_LITE)
|
|
s.poly([(C + 26, 150), (C + 150, 330), (C + 120, 380), (C + 30, 300)], APEX_LITE)
|
|
for sx in (-1, 1):
|
|
for i, t in enumerate((0.15, 0.38, 0.6, 0.82)):
|
|
x = C + sx * (30 + t * 115)
|
|
y = 150 + t * 185
|
|
s.oval((x - 9, y - 9, x + 9, y + 9), fill=APEX_ORANGE)
|
|
s.poly([(C - 18, 60), (C + 18, 60), (C + 12, 140), (C - 12, 140)], APEX_EDGE)
|
|
s.oval((C - 12, 180, C + 12, 220), fill=APEX_CANY)
|
|
s.line((C, 240), (C, 360), APEX_RED, 8)
|
|
s.poly([(C - 20, 370), (C - 44, 440), (C, 430)], APEX_DARK)
|
|
s.poly([(C + 20, 370), (C + 44, 440), (C, 430)], APEX_DARK)
|
|
s.glow_dot(C - 24, 430, 12, APEX_ENGINE)
|
|
s.glow_dot(C + 24, 430, 12, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
def reaver():
|
|
"""34 — twin rapid-fire pods flanking a lean hull."""
|
|
s = Ship()
|
|
s.poly([(C, 50), (C + 44, 160), (C + 56, 400), (C - 56, 400), (C - 44, 160)], APEX_HULL)
|
|
s.poly([(C, 100), (C + 24, 180), (C + 30, 380), (C - 30, 380), (C - 24, 180)], APEX_DARK)
|
|
for sx in (-1, 1):
|
|
s.oval((C + sx * 96 - 34, 190, C + sx * 96 + 34, 258), fill=APEX_LITE)
|
|
s.oval((C + sx * 96 - 24, 120, C + sx * 96 + 24, 172), fill=APEX_EDGE)
|
|
s.oval((C + sx * 96 - 14, 96, C + sx * 96 + 14, 130), fill=APEX_RED)
|
|
s.poly([(C + sx * 40, 220), (C + sx * 72, 214), (C + sx * 72, 252), (C + sx * 44, 262)], APEX_EDGE)
|
|
s.oval((C - 14, 150, C + 14, 196), fill=APEX_CANY)
|
|
s.poly([(C, 50), (C + 12, 110), (C - 12, 110)], APEX_RED)
|
|
s.line((C - 34, 280), (C + 34, 280), APEX_RED, 8)
|
|
s.line((C - 38, 320), (C + 38, 320), APEX_RED, 6)
|
|
s.poly([(C - 18, 390), (C - 30, 452), (C, 444)], APEX_DARK)
|
|
s.poly([(C + 18, 390), (C + 30, 452), (C, 444)], APEX_DARK)
|
|
s.glow_dot(C - 18, 444, 12, APEX_ENGINE)
|
|
s.glow_dot(C + 18, 444, 12, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
def fang():
|
|
"""35 — the burst striker: two curved fang blades sweeping up."""
|
|
s = Ship()
|
|
for sx in (-1, 1):
|
|
pts = []
|
|
for i in range(13):
|
|
t = i / 12
|
|
x = C + sx * (28 + math.sin(t * 1.9) * 92 * (1 - t * 0.25))
|
|
y = 400 - t * 360
|
|
pts.append((x, y))
|
|
for i in range(12, -1, -1):
|
|
t = i / 12
|
|
x = C + sx * (24 + math.sin(t * 1.9) * 66 * (1 - t * 0.3))
|
|
y = 430 - t * 370
|
|
pts.append((x, y))
|
|
s.poly(pts, APEX_LITE)
|
|
s.line((C + sx * 40, 380), (C + sx * (40 + math.sin(0.85 * 1.9) * 80), 380 - 0.7 * 360), APEX_RED, 6)
|
|
s.poly([(C, 120), (C + 34, 220), (C + 40, 430), (C - 40, 430), (C - 34, 220)], APEX_HULL)
|
|
s.poly([(C, 160), (C + 18, 240), (C + 22, 400), (C - 22, 400), (C - 18, 240)], APEX_DARK)
|
|
s.oval((C - 12, 240, C + 12, 280), fill=APEX_CANY)
|
|
s.line((C, 130), (C, 220), APEX_ORANGE, 8)
|
|
s.glow_dot(C - 16, 428, 12, APEX_ENGINE)
|
|
s.glow_dot(C + 16, 428, 12, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
def tyrant():
|
|
"""36 — the broad hex hull with the two heavy missile bays."""
|
|
s = Ship()
|
|
s.poly([(C, 60), (C + 84, 120), (C + 108, 280), (C + 64, 440), (C - 64, 440), (C - 108, 280), (C - 84, 120)], APEX_HULL)
|
|
s.poly([(C, 110), (C + 44, 160), (C + 52, 340), (C - 52, 340), (C - 44, 160)], APEX_DARK)
|
|
for sx in (-1, 1):
|
|
s.poly([(C + sx * 58, 150), (C + sx * 96, 160), (C + sx * 96, 260), (C + sx * 60, 250)], APEX_LITE)
|
|
s.oval((C + sx * 88 - 16, 128, C + sx * 88 + 16, 164), fill=APEX_DARK, outline=APEX_RED, width=6)
|
|
s.oval((C - 16, 170, C + 16, 214), fill=APEX_CANY)
|
|
s.line((C - 70, 300), (C + 70, 300), APEX_RED, 12)
|
|
s.poly([(C, 60), (C + 14, 120), (C - 14, 120)], APEX_ORANGE)
|
|
s.poly([(C - 24, 420), (C - 40, 470), (C, 458)], APEX_DARK)
|
|
s.poly([(C + 24, 420), (C + 40, 470), (C, 458)], APEX_DARK)
|
|
s.glow_dot(C, 462, 16, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
def alpha():
|
|
"""37 — the flagship: layered wings, the crown fins, gold-hot trim."""
|
|
s = Ship()
|
|
s.poly([(C, 30), (C + 60, 140), (C + 76, 330), (C + 44, 460), (C - 44, 460), (C - 76, 330), (C - 60, 140)], APEX_HULL)
|
|
s.poly([(C - 30, 150), (C - 140, 340), (C - 104, 396), (C - 34, 310)], APEX_LITE)
|
|
s.poly([(C + 30, 150), (C + 140, 340), (C + 104, 396), (C + 34, 310)], APEX_LITE)
|
|
s.poly([(C - 40, 320), (C - 96, 420), (C - 60, 448), (C - 40, 390)], APEX_EDGE)
|
|
s.poly([(C + 40, 320), (C + 96, 420), (C + 60, 448), (C + 40, 390)], APEX_EDGE)
|
|
s.poly([(C, 30), (C + 26, 160), (C + 30, 360), (C - 30, 360), (C - 26, 160)], APEX_DARK)
|
|
s.poly([(C, 30), (C + 10, 120), (C - 10, 120)], (232, 196, 84, 255)) # the gold tip
|
|
for sx in (-1, 1):
|
|
s.poly([(C + sx * 18, 120), (C + sx * 54, 60), (C + sx * 40, 150)], (232, 196, 84, 255)) # the crown
|
|
s.oval((C - 14, 180, C + 14, 224), fill=APEX_CANY)
|
|
s.line((C - 50, 260), (C + 50, 260), APEX_RED, 10)
|
|
s.oval((C - 40, 350, C + 40, 400), outline=(232, 196, 84, 255), width=8)
|
|
s.poly([(C - 16, 440), (C - 30, 484), (C, 474)], APEX_DARK)
|
|
s.poly([(C + 16, 440), (C + 30, 484), (C, 474)], APEX_DARK)
|
|
s.glow_dot(C - 18, 476, 13, APEX_ENGINE)
|
|
s.glow_dot(C + 18, 476, 13, APEX_ENGINE)
|
|
return s
|
|
|
|
|
|
# --------------------------------------------------------------- the WEAVE
|
|
# Floating, organic, calm. Cool violet hulls; violet-cyan lights.
|
|
WE_HULL = (70, 62, 88, 255)
|
|
WE_DARK = (42, 36, 58, 255)
|
|
WE_LITE = (108, 98, 136, 255)
|
|
WE_EDGE = (148, 138, 176, 255)
|
|
WE_VIOLET = (156, 106, 224, 255)
|
|
WE_CYAN = (92, 208, 216, 255)
|
|
WE_PEARL = (214, 190, 255, 255)
|
|
WE_ENGINE = (170, 120, 255)
|
|
WE_CANY = (24, 26, 44, 255)
|
|
|
|
|
|
def mindblade():
|
|
"""40 — the ringed blade: the annulus around a dark core."""
|
|
s = Ship()
|
|
s.oval((C - 104, 128, C + 104, 384), outline=WE_CYAN, width=14)
|
|
s.oval((C - 104, 128, C + 104, 384), outline=WE_VIOLET, width=4)
|
|
for a in range(0, 360, 45):
|
|
x = C + 98 * math.cos(math.radians(a))
|
|
y = 256 + 98 * math.sin(math.radians(a))
|
|
s.oval((x - 12, y - 12, x + 12, y + 12), fill=WE_PEARL)
|
|
s.poly([(C, 90), (C + 44, 190), (C + 44, 330), (C, 422), (C - 44, 330), (C - 44, 190)], WE_HULL)
|
|
s.poly([(C, 130), (C + 26, 200), (C + 26, 320), (C, 380), (C - 26, 320), (C - 26, 200)], WE_DARK)
|
|
s.oval((C - 16, 220, C + 16, 268), fill=WE_CANY)
|
|
s.glow_dot(C, 256, 22, WE_VIOLET)
|
|
s.glow_dot(C, 60, 14, WE_CYAN)
|
|
s.glow_dot(C, 452, 14, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def telekine():
|
|
"""41 — the floating orb above the hull; two small orb-hands."""
|
|
s = Ship()
|
|
s.glow_dot(C, 78, 26, WE_CYAN)
|
|
s.oval((C - 18, 60, C + 18, 96), fill=WE_PEARL)
|
|
s.line((C, 104), (C, 140), WE_VIOLET, 6)
|
|
s.poly([(C, 140), (C + 52, 220), (C + 60, 380), (C + 30, 452), (C - 30, 452), (C - 60, 380), (C - 52, 220)], WE_HULL)
|
|
s.poly([(C, 180), (C + 32, 240), (C + 36, 380), (C - 36, 380), (C - 32, 240)], WE_DARK)
|
|
for sx in (-1, 1):
|
|
s.oval((C + sx * 118 - 22, 250, C + sx * 118 + 22, 294), fill=WE_LITE)
|
|
s.glow_dot(C + sx * 118, 272, 12, WE_CYAN)
|
|
s.line((C + sx * 56, 280), (C + sx * 98, 272), WE_EDGE, 6)
|
|
s.oval((C - 14, 240, C + 14, 286), fill=WE_CANY)
|
|
s.arc((C - 60, 340, C + 60, 420), 150, 30, WE_VIOLET, 8)
|
|
s.glow_dot(C - 20, 446, 12, WE_ENGINE)
|
|
s.glow_dot(C + 20, 446, 12, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def phantasm():
|
|
"""42 — the ghost: layered translucent shrouds and a wavy tail."""
|
|
s = Ship()
|
|
s.poly([(C, 80), (C + 70, 200), (C + 84, 340), (C + 52, 400), (C - 52, 400), (C - 84, 340), (C - 70, 200)], WE_HULL)
|
|
for i, (w, a) in enumerate(((110, 150), (96, 190), (82, 230))):
|
|
pts = []
|
|
for k in range(24):
|
|
t = k / 23
|
|
x = C - w + t * 2 * w
|
|
y = 300 + i * 26 + math.sin(t * math.pi * 2 + i) * 12
|
|
pts.append((x, y))
|
|
s.poly(pts, (WE_LITE[0], WE_LITE[1], WE_LITE[2], a))
|
|
for sx in (-1, 1):
|
|
pts = [(C + sx * 40, 160)]
|
|
for k in range(9):
|
|
t = k / 8
|
|
pts.append((C + sx * (44 + math.sin(t * 2.2) * 46), 380 - t * 160))
|
|
for k in range(8, -1, -1):
|
|
t = k / 8
|
|
pts.append((C + sx * (34 + math.sin(t * 2.2) * 30), 400 - t * 170))
|
|
s.poly(pts, (120, 110, 156, 200))
|
|
s.oval((C - 20, 180, C + 20, 236), fill=WE_DARK)
|
|
s.oval((C - 14, 196, C + 14, 222), fill=WE_CANY)
|
|
s.arc((C - 26, 150, C + 26, 200), 200, 340, WE_PEARL, 6)
|
|
s.glow_dot(C, 60, 14, WE_VIOLET)
|
|
s.glow_dot(C - 18, 420, 10, WE_ENGINE)
|
|
s.glow_dot(C + 18, 420, 10, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def weaver():
|
|
"""43 — the diamond hull in the web between two halo rings."""
|
|
s = Ship()
|
|
for r in (118, 82):
|
|
s.oval((C - r, 256 - r, C + r, 256 + r), outline=WE_EDGE, width=6)
|
|
for a in range(0, 360, 30):
|
|
x1 = C + 82 * math.cos(math.radians(a))
|
|
y1 = 256 + 82 * math.sin(math.radians(a))
|
|
x2 = C + 118 * math.cos(math.radians(a))
|
|
y2 = 256 + 118 * math.sin(math.radians(a))
|
|
s.line((x1, y1), (x2, y2), (148, 138, 176, 160), 3)
|
|
s.poly([(C, 120), (C + 54, 256), (C, 392), (C - 54, 256)], WE_HULL)
|
|
s.poly([(C, 158), (C + 34, 256), (C, 354), (C - 34, 256)], WE_DARK)
|
|
s.oval((C - 12, 236, C + 12, 276), fill=WE_CANY)
|
|
s.glow_dot(C, 256, 18, WE_CYAN)
|
|
s.glow_dot(C, 96, 12, WE_VIOLET)
|
|
s.glow_dot(C, 416, 12, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def lure():
|
|
"""44 — the hook: the crescent ring around the nose, the bait dots."""
|
|
s = Ship()
|
|
s.arc((C - 96, 96, C + 96, 304), 300, 240, WE_CYAN, 12)
|
|
for a in (300, 330, 270, 240):
|
|
x = C + 96 * math.cos(math.radians(a))
|
|
y = 200 + 96 * math.sin(math.radians(a))
|
|
s.glow_dot(x, y, 8, WE_PEARL)
|
|
s.poly([(C, 150), (C + 56, 230), (C + 64, 380), (C + 34, 450), (C - 34, 450), (C - 64, 380), (C - 56, 230)], WE_HULL)
|
|
s.poly([(C, 190), (C + 34, 250), (C + 38, 380), (C - 38, 380), (C - 34, 250)], WE_DARK)
|
|
for i, y in enumerate((230, 270, 310)):
|
|
s.line((C - 30 + i * 4, y), (C + 30 - i * 4, y), WE_VIOLET, 6)
|
|
s.oval((C - 14, 230, C + 14, 276), fill=WE_CANY)
|
|
s.glow_dot(C, 176, 12, WE_PEARL)
|
|
s.glow_dot(C - 20, 444, 11, WE_ENGINE)
|
|
s.glow_dot(C + 20, 444, 11, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def dreamer():
|
|
"""45 — the soft dome shell, the inner glow, the closed eyes."""
|
|
s = Ship()
|
|
s.oval((C - 84, 120, C + 84, 404), fill=WE_HULL)
|
|
s.oval((C - 84, 120, C + 84, 404), outline=WE_EDGE, width=8)
|
|
s.oval((C - 58, 156, C + 58, 380), fill=WE_DARK)
|
|
s.oval((C - 40, 190, C + 40, 330), fill=(120, 104, 160, 120))
|
|
s.glow_dot(C, 260, 34, WE_VIOLET)
|
|
s.arc((C - 30, 210, C + 30, 260), 200, 340, WE_PEARL, 6) # the closed eyes
|
|
for sx in (-1, 1):
|
|
s.poly([(C + sx * 60, 350), (C + sx * 110, 400), (C + sx * 70, 430)], WE_LITE)
|
|
s.oval((C - 10, 120, C + 10, 150), fill=WE_CYAN)
|
|
s.glow_dot(C - 22, 440, 11, WE_ENGINE)
|
|
s.glow_dot(C + 22, 440, 11, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def serpent():
|
|
"""46 — the winding tail, the three eye-orbs."""
|
|
s = Ship()
|
|
pts_top, pts_bot = [], []
|
|
for k in range(17):
|
|
t = k / 16
|
|
x = C + math.sin(t * math.pi * 2.4) * 58
|
|
y = 60 + t * 380
|
|
wdt = 26 * (1 - t * 0.55)
|
|
pts_top.append((x - wdt, y))
|
|
pts_bot.append((x + wdt, y))
|
|
s.poly(pts_top + pts_bot[::-1], WE_HULL)
|
|
for k in range(17):
|
|
t = k / 16
|
|
x = C + math.sin(t * math.pi * 2.4) * 58
|
|
y = 60 + t * 380
|
|
wdt = 14 * (1 - t * 0.55)
|
|
s.oval((x - wdt, y - wdt, x + wdt, y + wdt), fill=(120, 104, 160, 90) if k % 2 else (WE_DARK[0], WE_DARK[1], WE_DARK[2], 255))
|
|
s.oval((C - 44, 40, C + 44, 124), fill=WE_LITE)
|
|
s.oval((C - 44, 40, C + 44, 124), outline=WE_EDGE, width=6)
|
|
for dx in (-18, 0, 18):
|
|
s.glow_dot(C + dx, 82, 9, WE_CYAN)
|
|
s.line((C - 30, 130), (C + 30, 130), WE_VIOLET, 6)
|
|
s.glow_dot(C, 470, 10, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
def overmind():
|
|
"""47 — the crown of six orbs, the triple halo, the great core."""
|
|
s = Ship()
|
|
for r in (124, 96, 68):
|
|
s.oval((C - r, 256 - r, C + r, 256 + r), outline=WE_EDGE, width=5)
|
|
for a in range(0, 360, 60):
|
|
x = C + 124 * math.cos(math.radians(a))
|
|
y = 256 + 124 * math.sin(math.radians(a))
|
|
s.glow_dot(x, y, 12, WE_PEARL)
|
|
s.oval((C - 46, 210, C + 46, 302), fill=WE_DARK)
|
|
s.oval((C - 46, 210, C + 46, 302), outline=WE_VIOLET, width=8)
|
|
s.glow_dot(C, 256, 30, WE_CYAN)
|
|
s.oval((C - 12, 240, C + 12, 272), fill=WE_CANY)
|
|
s.oval((C - 26, 108, C + 26, 150), fill=WE_HULL)
|
|
s.oval((C - 26, 362, C + 26, 404), fill=WE_HULL)
|
|
s.glow_dot(C, 96, 12, WE_VIOLET)
|
|
s.glow_dot(C, 416, 12, WE_VIOLET)
|
|
s.glow_dot(C - 60, 452, 9, WE_ENGINE)
|
|
s.glow_dot(C + 60, 452, 9, WE_ENGINE)
|
|
return s
|
|
|
|
|
|
APEX_SHIPS = [raptor, mauler, stalker, screamer, reaver, fang, tyrant, alpha]
|
|
WEAVE_SHIPS = [mindblade, telekine, phantasm, weaver, lure, dreamer, serpent, overmind]
|
|
|
|
|
|
def main():
|
|
im = Image.open(SHEET).convert('RGBA')
|
|
for i, maker in enumerate(APEX_SHIPS):
|
|
im.paste(maker().finish(), (i * CELL, 3 * CELL)) # frames 30-37 (row 3)
|
|
for i, maker in enumerate(WEAVE_SHIPS):
|
|
im.paste(maker().finish(), (i * CELL, 4 * CELL)) # frames 40-47 (row 4)
|
|
im.save(SHEET)
|
|
print('painted 16 frames into', SHEET)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|