Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
352 lines
19 KiB
Python
352 lines
19 KiB
Python
"""Modular terrain, team cores and neutral figures for the Saumwacht asset set.
|
|
|
|
Blender coordinates: Z up, forward -Y. This module only constructs meshes;
|
|
the caller owns collection cleanup, material creation, export and validation.
|
|
"""
|
|
|
|
from math import cos, pi, sin
|
|
|
|
from common import box, ellipsoid, cylinder, cone, beam, ring, mesh_object, empty
|
|
|
|
|
|
def _prism(name, outline, front_y, back_y, material):
|
|
"""Extrude an X/Z outline along Y, useful for readable masks and wedges."""
|
|
area = sum(outline[i][0] * outline[(i + 1) % len(outline)][1]
|
|
- outline[(i + 1) % len(outline)][0] * outline[i][1]
|
|
for i in range(len(outline)))
|
|
if area < 0:
|
|
outline = list(reversed(outline))
|
|
count = len(outline)
|
|
vertices = [(x, front_y, z) for x, z in outline]
|
|
vertices += [(x, back_y, z) for x, z in outline]
|
|
faces = [tuple(range(count)), tuple(range(2 * count - 1, count - 1, -1))]
|
|
for i in range(count):
|
|
following = (i + 1) % count
|
|
faces.append((i, i + count, following + count, following))
|
|
return mesh_object(name, vertices, faces, material)
|
|
|
|
|
|
def _figure(spec, nominal_height):
|
|
"""Scaled primitive wrappers keep species proportions stable at any height."""
|
|
scale = float(spec.get("height", nominal_height)) / nominal_height
|
|
|
|
def point(value):
|
|
return tuple(component * scale for component in value)
|
|
|
|
class Figure:
|
|
def box(self, name, loc, size, material, rotation=None):
|
|
return box(name, point(loc), point(size), material, rotation=rotation)
|
|
|
|
def ellipsoid(self, name, loc, size, material, subdivisions=1):
|
|
return ellipsoid(name, point(loc), point(size), material, subdivisions=subdivisions)
|
|
|
|
def cylinder(self, name, loc, radius, depth, material, vertices=8, rotation=None):
|
|
return cylinder(name, point(loc), radius * scale, depth * scale,
|
|
material, vertices=vertices, rotation=rotation)
|
|
|
|
def cone(self, name, loc, radius1, radius2, depth, material, vertices=8, rotation=None):
|
|
return cone(name, point(loc), radius1 * scale, radius2 * scale, depth * scale,
|
|
material, vertices=vertices, rotation=rotation)
|
|
|
|
def beam(self, name, start, end, radius, material, vertices=6):
|
|
return beam(name, point(start), point(end), radius * scale,
|
|
material, vertices=vertices)
|
|
|
|
def ring(self, name, loc, major_radius, minor_radius, material,
|
|
rotation=None, segments=10):
|
|
return ring(name, point(loc), major_radius * scale, minor_radius * scale,
|
|
material, rotation=rotation, segments=segments)
|
|
|
|
def prism(self, name, outline, front_y, back_y, material):
|
|
return _prism(name, [(x * scale, z * scale) for x, z in outline],
|
|
front_y * scale, back_y * scale, material)
|
|
|
|
return Figure()
|
|
|
|
|
|
def _boots(f, material, x=0.22, width=0.26, height=0.18, depth=0.42):
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.box("Leg" + side + "_Boot", (sign * x, -0.075, height / 2),
|
|
(width, depth, height), material)
|
|
|
|
|
|
def _eyes(f, material, z, y, separation=0.10):
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.box("Head_Eye" + side, (sign * separation, y, z),
|
|
(0.065, 0.025, 0.034), material)
|
|
|
|
|
|
def build_lane(spec, mat):
|
|
"""12 m traversable width, 4 m repeat length, deck at Z=0.
|
|
|
|
Side decoration remains strictly outside X=[-6,+6], and nothing extends
|
|
past Y=[-2,+2], so consecutive copies meet without a transverse obstacle.
|
|
"""
|
|
box("Body_LaneFoundation", (0, 0, -0.195), (12, 4, 0.31), mat["primary"])
|
|
for column in range(6):
|
|
box("Body_LaneSlab_%02d" % column, (-5 + column * 2, 0, -0.02),
|
|
(2, 4, 0.04), mat["primary"])
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
box("Body_" + side + "RetainingLip", (sign * 6.15, 0, -0.10),
|
|
(0.30, 4, 0.50), mat["secondary"])
|
|
for index, y in enumerate((-1.42, 0, 1.42)):
|
|
ellipsoid("Body_" + side + "Basalt_%02d" % index,
|
|
(sign * 6.46, y, -0.05),
|
|
(0.26, 0.42, 0.26 if index != 1 else 0.30), mat["secondary"])
|
|
for index, y in enumerate((-1.15, 1.15)):
|
|
box("Body_" + side + "BrassMarker_%02d" % index,
|
|
(sign * 6.15, y, 0.165), (0.19, 0.20, 0.03), mat["accent"])
|
|
ellipsoid("Body_" + side + "Moss", (sign * 6.39, 0.65, 0.02),
|
|
(0.17, 0.34, 0.13), mat["light"])
|
|
empty("Anchor_Deck", (0, 0, 0))
|
|
empty("Anchor_JoinNorth", (0, 2, 0))
|
|
empty("Anchor_JoinSouth", (0, -2, 0))
|
|
|
|
|
|
def build_core(spec, mat):
|
|
"""A grounded crystal cradle with a physical team circle or triangle."""
|
|
triangular = int(spec.get("team", 0)) == 1
|
|
sides = 3 if triangular else 12
|
|
cylinder("Body_CorePlinth", (0, 0, 0.13), 1.26, 0.26,
|
|
mat["primary"], vertices=sides)
|
|
cylinder("Body_BrassPlinthRim", (0, 0, 0.285), 1.18, 0.05,
|
|
mat["secondary"], vertices=sides)
|
|
cylinder("Body_CradleStep", (0, 0, 0.37), 0.85, 0.12,
|
|
mat["primary"], vertices=8)
|
|
if triangular:
|
|
points = [(1.06 * sin(i * 2 * pi / 3), -1.06 * cos(i * 2 * pi / 3), 0.34)
|
|
for i in range(3)]
|
|
for i in range(3):
|
|
beam("Team_Triangle_%d" % i, points[i], points[(i + 1) % 3],
|
|
0.045, mat["accent"], vertices=6)
|
|
else:
|
|
ring("Team_Circle", (0, 0, 0.345), 1.025, 0.045, mat["accent"], segments=20)
|
|
# Multiple low-poly rings create a recognizable mineral, not a sphere.
|
|
vertices = [(0, 0, 0.51)]
|
|
for radius, z in ((0.37, 0.70), (0.58, 1.16), (0.36, 1.72)):
|
|
vertices.extend((radius * cos(i * pi / 3), radius * sin(i * pi / 3), z)
|
|
for i in range(6))
|
|
vertices.append((0, 0, 2.20))
|
|
faces = [(0, 1 + (i + 1) % 6, 1 + i) for i in range(6)]
|
|
for row in range(2):
|
|
start = 1 + 6 * row
|
|
for i in range(6):
|
|
following = (i + 1) % 6
|
|
faces.append((start + i, start + following,
|
|
start + 6 + following, start + 6 + i))
|
|
faces.extend((13 + i, 13 + (i + 1) % 6, 19) for i in range(6))
|
|
mesh_object("Body_CoreCrystal", vertices, faces, mat["accent"])
|
|
for index in range(4):
|
|
angle = pi / 4 + index * pi / 2
|
|
x, y = sin(angle), cos(angle)
|
|
beam("Arm_Cradle_%d" % index, (x * 0.72, y * 0.72, 0.42),
|
|
(x * 0.47, y * 0.47, 0.88), 0.075, mat["secondary"])
|
|
ellipsoid("Arm_CradleClasp_%d" % index,
|
|
(x * 0.48, y * 0.48, 0.85), (0.12, 0.12, 0.09), mat["light"])
|
|
empty("Anchor_CoreEffect", (0, 0, 1.25))
|
|
|
|
|
|
def build_worker(spec, mat):
|
|
"""Small clay Funkenleser with a fork and a framed energy container."""
|
|
f = _figure(spec, 1.1)
|
|
_boots(f, mat["primary"], x=0.18, width=0.23, height=0.14, depth=0.31)
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.beam("Leg" + side, (sign * 0.16, 0, 0.14),
|
|
(sign * 0.15, 0, 0.36), 0.10, mat["primary"])
|
|
f.ellipsoid("Body_Clay", (0, 0, 0.49), (0.30, 0.21, 0.30), mat["primary"])
|
|
f.prism("Body_Apron", [(-0.20, 0.68), (-0.24, 0.30), (0.24, 0.30), (0.20, 0.68)],
|
|
-0.23, -0.19, mat["secondary"])
|
|
f.box("Body_Buckle", (0, -0.248, 0.56), (0.10, 0.035, 0.07), mat["accent"])
|
|
f.ellipsoid("Head_Hood", (0, 0.005, 0.88), (0.26, 0.235, 0.22), mat["secondary"])
|
|
f.ellipsoid("Head_ClayFace", (0, -0.175, 0.88), (0.195, 0.10, 0.17), mat["primary"])
|
|
_eyes(f, mat["secondary"], 0.905, -0.274, separation=0.075)
|
|
f.beam("ArmL", (-0.23, 0, 0.64), (-0.40, -0.19, 0.52), 0.085, mat["primary"])
|
|
f.beam("ArmR", (0.23, 0, 0.64), (0.39, -0.23, 0.54), 0.085, mat["primary"])
|
|
f.ellipsoid("ArmL_Hand", (-0.40, -0.19, 0.52), (0.105, 0.09, 0.09), mat["primary"])
|
|
f.ellipsoid("ArmR_Hand", (0.39, -0.23, 0.54), (0.105, 0.09, 0.09), mat["primary"])
|
|
f.beam("Weapon_SparkForkHandle", (-0.43, -0.20, 0.13), (-0.43, -0.20, 0.78),
|
|
0.032, mat["accent"])
|
|
for sign in (-1, 1):
|
|
f.beam("Weapon_SparkForkProng_%d" % sign, (-0.43, -0.20, 0.78),
|
|
(-0.43 + sign * 0.10, -0.20, 0.99), 0.026, mat["accent"])
|
|
f.cylinder("Weapon_ContainerBase", (0.44, -0.24, 0.32), 0.11, 0.045, mat["accent"])
|
|
f.cylinder("Weapon_ContainerLid", (0.44, -0.24, 0.61), 0.11, 0.045, mat["accent"])
|
|
# An open brass cage gives glass-like legibility without alpha sorting.
|
|
for index in range(4):
|
|
angle = index * pi / 2
|
|
f.beam("Weapon_ContainerRib_%d" % index,
|
|
(0.44 + cos(angle) * 0.085, -0.24 + sin(angle) * 0.085, 0.33),
|
|
(0.44 + cos(angle) * 0.085, -0.24 + sin(angle) * 0.085, 0.60),
|
|
0.016, mat["accent"], vertices=4)
|
|
f.ellipsoid("Weapon_CapturedSpark", (0.44, -0.24, 0.47),
|
|
(0.058, 0.058, 0.105), mat["light"])
|
|
|
|
|
|
def _scout(spec, mat):
|
|
f = _figure(spec, 1.70)
|
|
_boots(f, mat["primary"], x=0.18, width=0.23, height=0.18)
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.beam("Leg" + side, (sign * 0.18, 0, 0.17),
|
|
(sign * 0.15, 0, 0.78), 0.087, mat["primary"])
|
|
f.box("Leg" + side + "_ShinPlate", (sign * 0.18, -0.095, 0.35),
|
|
(0.13, 0.05, 0.24), mat["secondary"])
|
|
f.cone("Body_Armor", (0, 0, 0.97), 0.22, 0.28, 0.49, mat["primary"], vertices=6)
|
|
f.box("Body_Belt", (0, 0, 0.77), (0.44, 0.30, 0.10), mat["secondary"])
|
|
f.ellipsoid("Head_Hood", (0, 0.015, 1.42), (0.23, 0.21, 0.25), mat["primary"])
|
|
f.cone("Head_HoodPoint", (0, 0.055, 1.60), 0.145, 0, 0.20, mat["primary"], vertices=5)
|
|
f.prism("Head_Mask", [(-0.15, 1.54), (-0.13, 1.32), (0, 1.26), (0.13, 1.32), (0.15, 1.54)],
|
|
-0.21, -0.165, mat["light"])
|
|
_eyes(f, mat["primary"], 1.46, -0.225, separation=0.075)
|
|
f.beam("ArmL_Upper", (-0.24, 0, 1.12), (-0.34, -0.17, 0.96), 0.078, mat["primary"])
|
|
f.beam("ArmL_Fore", (-0.34, -0.17, 0.96), (-0.06, -0.37, 1.02), 0.071, mat["primary"])
|
|
f.beam("ArmR_Upper", (0.24, 0, 1.12), (0.35, -0.15, 0.97), 0.078, mat["primary"])
|
|
f.beam("ArmR_Fore", (0.35, -0.15, 0.97), (0.25, -0.43, 1.07), 0.071, mat["primary"])
|
|
f.box("Weapon_DartLauncherStock", (0.19, -0.34, 1.07), (0.19, 0.44, 0.15), mat["secondary"])
|
|
f.cylinder("Weapon_DartLauncherBarrel", (0.19, -0.66, 1.09),
|
|
0.065, 0.34, mat["light"], rotation=(pi / 2, 0, 0))
|
|
f.cylinder("Weapon_DartLauncherBore", (0.19, -0.836, 1.09),
|
|
0.04, 0.008, mat["primary"], rotation=(pi / 2, 0, 0))
|
|
f.box("Body_DartQuiver", (-0.14, 0.23, 1.02), (0.23, 0.17, 0.45), mat["secondary"],
|
|
rotation=(0, -0.16, 0))
|
|
for index in range(3):
|
|
x = -0.23 + index * 0.085
|
|
f.beam("Weapon_SpareDart_%d" % index, (x, 0.24, 1.10), (x - 0.06, 0.24, 1.47),
|
|
0.024, mat["light"], vertices=4)
|
|
|
|
|
|
def _ram(spec, mat):
|
|
f = _figure(spec, 1.48)
|
|
_boots(f, mat["primary"], x=0.27, width=0.35, height=0.20, depth=0.45)
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.box("Leg" + side, (sign * 0.27, 0, 0.35), (0.25, 0.30, 0.35), mat["primary"])
|
|
f.ellipsoid("Arm" + side + "_Pauldron", (sign * 0.43, 0, 1.05),
|
|
(0.27, 0.25, 0.25), mat["secondary"])
|
|
f.box("Body_RustArmor", (0, 0, 0.83), (0.74, 0.48, 0.67), mat["secondary"])
|
|
f.box("Body_UpperLamella", (0, -0.256, 1.01), (0.71, 0.06, 0.18), mat["primary"])
|
|
f.box("Body_LowerLamella", (0, -0.261, 0.80), (0.71, 0.06, 0.16), mat["primary"])
|
|
f.ellipsoid("Head_Helmet", (0, 0, 1.29), (0.27, 0.23, 0.19), mat["primary"])
|
|
f.box("Head_BrowGuard", (0, -0.23, 1.35), (0.45, 0.085, 0.10), mat["light"])
|
|
_eyes(f, mat["accent"], 1.275, -0.235)
|
|
f.beam("ArmL", (-0.43, 0, 1.03), (-0.43, -0.40, 0.81), 0.14, mat["primary"])
|
|
f.beam("ArmR", (0.43, 0, 1.03), (0.61, -0.18, 0.72), 0.14, mat["primary"])
|
|
f.box("ArmR_HammerFist", (0.61, -0.19, 0.66), (0.30, 0.28, 0.29), mat["secondary"])
|
|
outline = [(-0.63, 1.09), (-0.68, 0.34), (-0.48, 0.21), (0.45, 0.21),
|
|
(0.65, 0.34), (0.60, 1.09)]
|
|
f.prism("Weapon_RamShield", outline, -0.54, -0.39, mat["secondary"])
|
|
f.box("Weapon_ShieldTopRim", (-0.015, -0.575, 1.05), (1.18, 0.07, 0.095), mat["light"])
|
|
f.prism("Weapon_CentralWedge", [(-0.13, 0.94), (-0.18, 0.37), (0, 0.28), (0.18, 0.37), (0.13, 0.94)],
|
|
-0.74, -0.53, mat["primary"])
|
|
|
|
|
|
def _singer(spec, mat):
|
|
f = _figure(spec, 1.82)
|
|
_boots(f, mat["primary"], x=0.18, width=0.23, height=0.12, depth=0.32)
|
|
f.cone("Body_BellRobe", (0, 0, 0.63), 0.43, 0.20, 1.10, mat["primary"], vertices=8)
|
|
for index, x in enumerate((-0.22, 0, 0.22)):
|
|
f.prism("Body_RobeFold_%d" % index,
|
|
[(x - 0.055, 0.15), (x + 0.055, 0.15), (x * 0.45, 1.09)],
|
|
-0.32, -0.24, mat["accent"])
|
|
f.ellipsoid("Body_Mantle", (0, 0, 1.24), (0.37, 0.27, 0.21), mat["accent"])
|
|
f.ellipsoid("Head_DeepHood", (0, 0, 1.57), (0.26, 0.25, 0.25), mat["primary"])
|
|
f.prism("Head_SingerMask", [(-0.13, 1.68), (-0.12, 1.47), (0, 1.41), (0.12, 1.47), (0.13, 1.68)],
|
|
-0.246, -0.21, mat["light"])
|
|
_eyes(f, mat["primary"], 1.60, -0.257, separation=0.065)
|
|
f.beam("ArmL", (-0.29, 0, 1.27), (-0.34, -0.37, 1.05), 0.085, mat["primary"])
|
|
f.beam("ArmR", (0.29, 0, 1.27), (0.36, -0.43, 1.10), 0.085, mat["primary"])
|
|
f.ellipsoid("ArmL_Hand", (-0.34, -0.37, 1.05), (0.095, 0.075, 0.075), mat["light"])
|
|
f.ellipsoid("ArmR_Hand", (0.36, -0.43, 1.10), (0.095, 0.075, 0.075), mat["light"])
|
|
f.beam("Weapon_HornPipe", (-0.30, -0.37, 1.08), (0.36, -0.43, 1.13), 0.075, mat["secondary"])
|
|
# A true open bell: two rings connected by a tapered shell, no filled mouth.
|
|
vertices = []
|
|
for y, radius in ((-0.43, 0.075), (-0.84, 0.28), (-0.81, 0.235), (-0.47, 0.042)):
|
|
for index in range(10):
|
|
angle = index * pi / 5
|
|
vertices.append((0.36 + cos(angle) * radius, y, 1.13 + sin(angle) * radius))
|
|
faces = []
|
|
for row in range(3):
|
|
for i in range(10):
|
|
j = (i + 1) % 10
|
|
faces.append((row * 10 + i, row * 10 + j, (row + 1) * 10 + j, (row + 1) * 10 + i))
|
|
scale = float(spec.get("height", 1.82)) / 1.82
|
|
mesh_object("Weapon_ResonanceHorn", [tuple(v * scale for v in point) for point in vertices],
|
|
faces, mat["secondary"])
|
|
f.ring("Weapon_HornRim", (0.36, -0.84, 1.13), 0.258, 0.023, mat["light"],
|
|
rotation=(pi / 2, 0, 0))
|
|
|
|
|
|
def _breaker(spec, mat):
|
|
f = _figure(spec, 1.82)
|
|
_boots(f, mat["primary"], x=0.28, width=0.36, height=0.22, depth=0.49)
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.box("Leg" + side, (sign * 0.26, 0, 0.47), (0.28, 0.31, 0.54), mat["primary"])
|
|
f.box("Leg" + side + "_Knee", (sign * 0.27, -0.19, 0.51),
|
|
(0.27, 0.13, 0.24), mat["secondary"])
|
|
f.box("Arm" + side + "_Pauldron", (sign * 0.48, 0, 1.29),
|
|
(0.42, 0.52, 0.34), mat["primary"], rotation=(0, sign * 0.14, 0))
|
|
f.cone("Body_SteelTorso", (0, 0, 1.01), 0.35, 0.47, 0.70, mat["primary"], vertices=6)
|
|
f.prism("Body_RustChestInset", [(-0.28, 1.29), (0, 0.95), (0.28, 1.29)],
|
|
-0.397, -0.35, mat["secondary"])
|
|
f.box("Head_Helmet", (0, 0, 1.61), (0.43, 0.40, 0.42), mat["primary"])
|
|
f.prism("Head_Faceplate", [(-0.17, 1.76), (-0.17, 1.50), (0, 1.44), (0.17, 1.50), (0.17, 1.76)],
|
|
-0.23, -0.19, mat["light"])
|
|
f.box("Head_Visor", (0, -0.246, 1.66), (0.25, 0.026, 0.045), mat["primary"])
|
|
f.beam("ArmL", (-0.47, 0, 1.25), (-0.31, -0.50, 0.92), 0.12, mat["primary"])
|
|
f.beam("ArmR", (0.47, 0, 1.25), (0.30, -0.50, 1.20), 0.12, mat["primary"])
|
|
f.ellipsoid("ArmL_Hand", (-0.31, -0.50, 0.92), (0.145, 0.12, 0.12), mat["secondary"])
|
|
f.ellipsoid("ArmR_Hand", (0.30, -0.50, 1.20), (0.145, 0.12, 0.12), mat["secondary"])
|
|
f.beam("Weapon_HammerShaft", (-0.65, -0.51, 0.77), (0.66, -0.51, 1.36),
|
|
0.055, mat["secondary"])
|
|
f.prism("Weapon_KeilhammerHead", [(0.40, 1.26), (0.43, 1.58), (0.82, 1.80),
|
|
(1.02, 1.61), (0.90, 1.18)],
|
|
-0.79, -0.24, mat["primary"])
|
|
# Offset along the sloped edge normal; coplanar cap faces would flicker.
|
|
f.prism("Weapon_ChiselEdge", [(0.832, 1.814), (1.032, 1.624), (0.992, 1.354), (0.932, 1.644)],
|
|
-0.80, -0.23, mat["light"])
|
|
|
|
|
|
def _rift(spec, mat):
|
|
f = _figure(spec, 2.08)
|
|
_boots(f, mat["primary"], x=0.22, width=0.21, height=0.12, depth=0.35)
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.beam("Leg" + side + "_Shin", (sign * 0.22, 0, 0.12),
|
|
(sign * 0.30, 0.12, 0.45), 0.069, mat["primary"])
|
|
f.beam("Leg" + side + "_Thigh", (sign * 0.30, 0.12, 0.45),
|
|
(sign * 0.17, 0, 0.81), 0.083, mat["primary"])
|
|
f.ellipsoid("Body_Pelvis", (0, 0, 0.83), (0.25, 0.16, 0.16), mat["primary"])
|
|
# Separate ribs bound real negative space; there is deliberately no chest fill.
|
|
for side, sign in (("L", -1), ("R", 1)):
|
|
f.beam("Body_" + side + "LowerRib", (sign * 0.15, 0, 0.89),
|
|
(sign * 0.31, 0, 1.18), 0.095, mat["primary"])
|
|
f.beam("Body_" + side + "UpperRib", (sign * 0.31, 0, 1.18),
|
|
(sign * 0.21, 0, 1.52), 0.10, mat["primary"])
|
|
f.beam("Body_" + side + "RiftSeam", (sign * 0.17, -0.080, 0.95),
|
|
(sign * 0.245, -0.080, 1.18), 0.019, mat["accent"], vertices=4)
|
|
f.beam("Body_" + side + "RiftUpperSeam", (sign * 0.245, -0.080, 1.18),
|
|
(sign * 0.16, -0.080, 1.46), 0.019, mat["accent"], vertices=4)
|
|
f.beam("Body_Collar", (-0.21, 0, 1.52), (0.21, 0, 1.52), 0.095, mat["primary"])
|
|
f.ellipsoid("Head_HollowShell", (0, 0, 1.73), (0.20, 0.155, 0.23), mat["primary"])
|
|
f.box("Head_RiftMouth", (0, -0.153, 1.69), (0.17, 0.025, 0.043), mat["accent"])
|
|
for index, (x, z) in enumerate(((-0.19, 1.90), (-0.10, 2.02), (0.10, 2.02), (0.19, 1.90))):
|
|
f.beam("Head_BrokenCrown_%d" % index, (x, 0, z - 0.13), (x * 1.18, 0, z + 0.06),
|
|
0.038, mat["light"], vertices=5)
|
|
f.beam("ArmL_Upper", (-0.30, 0, 1.47), (-0.48, 0.02, 1.13), 0.068, mat["primary"])
|
|
f.beam("ArmL_Fore", (-0.48, 0.02, 1.13), (-0.46, -0.18, 0.77), 0.056, mat["primary"])
|
|
for index in range(3):
|
|
f.beam("ArmL_Claw_%d" % index, (-0.50 + index * 0.043, -0.18, 0.79),
|
|
(-0.51 + index * 0.049, -0.25, 0.64), 0.020, mat["light"], vertices=4)
|
|
f.beam("ArmR_Upper", (0.30, 0, 1.47), (0.49, 0, 1.10), 0.068, mat["primary"])
|
|
f.beam("ArmR_Fore", (0.49, 0, 1.10), (0.37, -0.33, 1.23), 0.056, mat["primary"])
|
|
f.ellipsoid("ArmR_CradlingHand", (0.37, -0.33, 1.23), (0.12, 0.09, 0.052), mat["primary"])
|
|
f.ellipsoid("Weapon_DrainOrb", (0.37, -0.34, 1.40), (0.13, 0.13, 0.13), mat["accent"])
|
|
f.ring("Weapon_DrainOrbit", (0.37, -0.34, 1.40), 0.175, 0.015,
|
|
mat["light"], rotation=(0.35, 0.20, 0), segments=10)
|
|
|
|
|
|
def build_mercenary(spec, mat):
|
|
"""Dispatch five neutral mercenaries, retaining recognizable role silhouettes."""
|
|
builders = {"s01": _scout, "s02": _ram, "s03": _singer,
|
|
"s04": _breaker, "s05": _rift}
|
|
identifier = str(spec["id"])
|
|
if identifier not in builders:
|
|
raise ValueError("Unknown Saumwacht mercenary: " + identifier)
|
|
builders[identifier](spec, mat)
|