"""Original Saumwacht melee and tank silhouettes, built from named low-poly parts. Coordinates are metres, Z up, facing -Y. The shared exporter owns materials, normalisation, parenting, animation, scene lifetime and export. Every builder is deterministic and deliberately preserves separate limb / weapon meshes. """ from math import cos, sin, pi from common import box, ellipsoid, cylinder, cone, beam, ring, mesh_object, empty def _profile(name, points, y, depth, mat): """Extrude a silhouette with outward normals and a centred editable origin.""" signed_area = sum(points[i][0] * points[(i+1) % len(points)][1] - points[(i+1) % len(points)][0] * points[i][1] for i in range(len(points))) if signed_area < 0: points = list(reversed(points)) center_x = (min(p[0] for p in points) + max(p[0] for p in points)) / 2 center_z = (min(p[1] for p in points) + max(p[1] for p in points)) / 2 n = len(points) vertices = [(x-center_x, -depth/2, z-center_z) for x, z in points] vertices += [(x-center_x, depth/2, z-center_z) for x, z in points] faces = [tuple(range(n)), tuple(range(2 * n - 1, n - 1, -1))] faces += [(i, i + n, (i + 1) % n + n, (i + 1) % n) for i in range(n)] obj = mesh_object(name, vertices, faces, mat) obj.location = (center_x, y, center_z) return obj def _slab(name, loc, size, mat, bevel=0.08): """Eight-sided shield / armour plate with real clipped corners.""" x, y, z = loc w, d, h = size a, b = w / 2, h / 2 c = min(bevel, a * 0.42, b * 0.42) points = [(x-a+c, z-b), (x+a-c, z-b), (x+a, z-b+c), (x+a, z+b-c), (x+a-c, z+b), (x-a+c, z+b), (x-a, z+b-c), (x-a, z-b+c)] return _profile(name, points, y, d, mat) def _blade(name, origin, length, width, angle, mat, depth=0.055, hook=0.12): """A broad bent blade, with an unmistakable edge rather than a thin rod.""" x, y, z = origin local = [(-width*.38, 0), (width*.48, 0), (width*.54, length*.52), (hook, length), (-width*.30, length*.65)] points = [(x + u*cos(angle) + v*sin(angle), z - u*sin(angle) + v*cos(angle)) for u, v in local] return _profile(name, points, y, depth, mat) def _crescent(name, origin, radius, thickness, start, end, mat, depth=0.09, segments=10): """An open polygonal crescent plate in the front plane, angles in radians.""" x, y, z = origin angles = [start + (end-start)*i/segments for i in range(segments+1)] points = [(x + radius*cos(a), z + radius*sin(a)) for a in angles] points += [(x + (radius-thickness)*cos(a), z + (radius-thickness)*sin(a)) for a in reversed(angles)] return _profile(name, points, y, depth, mat) def _eyes(prefix, x, y, z, spacing, mat, width=0.085): for side, s in (("L", -1), ("R", 1)): box(prefix + "Eye" + side, (x+s*spacing, y, z), (width, 0.023, 0.028), mat) def _stone_legs(mat, upgraded=False): for side, s in (("L", -1), ("R", 1)): x = s * (0.27 if upgraded else 0.24) _slab("Leg" + side + "Shin", (x, 0.02, 0.26), (0.30, 0.31, 0.36), mat["primary"]) _slab("Leg" + side + "Foot", (x, -0.10, 0.095), (0.37, 0.46, 0.19), mat["primary"], 0.07) if upgraded: box("Leg" + side + "Cuff", (x, -0.167, 0.27), (0.23, 0.035, 0.07), mat["accent"]) def _coal_body(mat, upgraded): ellipsoid("BodyCoal", (0, 0.01, 0.77), (0.44, 0.35, 0.48), mat["primary"], subdivisions=2) _slab("BodyForehead", (0, -0.16, 1.115), (0.62, 0.36, 0.25), mat["primary"]) mouth = [(-.31,.83), (-.23,.77), (-.12,.80), (0,.735), (.13,.785), (.24,.76), (.31,.83), (.25,.94), (.1,.92), (0,.97), (-.14,.93), (-.27,.955)] _profile("HeadEmberMouth", mouth, -.352, .045, mat["secondary"]) _eyes("Head", 0, -.350, 1.05, .155, mat["secondary"], .065) for i, x in enumerate((-.20, 0, .20)): _profile("HeadTooth%02d" % i, [(x-.035,.94), (x,.865), (x+.035,.94)], -.384, .025, mat["light"]) for side, s in (("L", -1), ("R", 1)): if upgraded: _slab("BodyShoulder" + side, (s*.49, .0, 1.08), (.39, .47, .32), mat["primary"]) _blade("BodyShoulderCrest" + side, (s*.42, .035, 1.15), .29, .16, s*.50, mat["primary"], .17, s*.04) box("BodyShoulderSeam" + side, (s*.50, -.243, 1.05), (.22, .02, .055), mat["secondary"]) else: ellipsoid("BodyShoulder" + side, (s*.41, .02, .87), (.19, .25, .25), mat["primary"]) def _coal_claws(mat, upgraded): for side, s in (("L", -1), ("R", 1)): beam("Arm" + side + "Upper", (s*.37, .0, .86), (s*.55, -.075, .65), .13, mat["primary"]) ellipsoid("Arm" + side + "Fist", (s*.57, -.16, .56), (.19, .24, .19), mat["primary"]) for i, dx in enumerate((-.105, .105)): x = s*.57 + dx # Curved front claws are separate wedges; upgrade adds long tips. points = [(x-.045,.60), (x+.045,.60), (x+.065,.42), (x+.015,.30 if upgraded else .37), (x-.035,.43)] _profile("Weapon" + side + "Claw%02d" % i, points, -.39 if upgraded else -.34, .115, mat["accent"] if upgraded else mat["light"]) if upgraded: _slab("Arm" + side + "WristGuard", (s*.57, -.18, .70), (.33, .40, .13), mat["accent"], .045) def _basalt_body(mat, upgraded): _slab("BodyBastion", (0, .035, .83), (1.02 if upgraded else .87, .57, .95), mat["primary"]) _slab("BodyFrontShield", (0, -.30, .80), (.74, .18, .71), mat["primary"], .14) _slab("HeadFurnaceSlot", (0, -.401, 1.07), (.48, .035, .085), mat["secondary"], .025) for s in (-1, 1): beam("BodyBrace" + ("L" if s < 0 else "R"), (s*.29, -.398, .50), (s*.29, -.398, .95), .033, mat["accent"], vertices=4) if upgraded: for i, (x, z, width) in enumerate(((-.48, 1.20, .31), (0, 1.30, .43), (.48, 1.20, .31))): _slab("BodyBattlement%02d" % i, (x, .035, z), (width, .54, .35), mat["primary"], .05) box("BodyBattlementCap%02d" % i, (x, .035, z+.145), (width+.025, .565, .055), mat["accent"]) _slab("BodyShieldBoss", (0, -.41, .73), (.38, .075, .35), mat["accent"], .095) _slab("BodyShieldCore", (0, -.456, .73), (.23, .025, .22), mat["secondary"], .065) def _basalt_arms(mat, upgraded): for side, s in (("L", -1), ("R", 1)): x = s * (.635 if upgraded else .54) _slab("Arm" + side + "Pillar", (x, .01, .72), (.28, .37, .60), mat["primary"]) _slab("Arm" + side + "Fist", (x, -.075, .48), (.32, .42, .27), mat["primary"]) box("Arm" + side + "Band", (x, -.287, .52), (.23, .028, .07), mat["accent"]) def _machine_legs(mat, upgraded=False, tall=False): for side, s in (("L", -1), ("R", 1)): x = s * (.25 if tall else .24) top = .72 if tall else .46 knee = (x*1.15, .03, .38 if tall else .25) ankle = (x, -.065, .13) beam("Leg" + side + "PistonUpper", (x*.8, .035, top), knee, .07 if tall else .13, mat["accent"]) beam("Leg" + side + "PistonLower", knee, ankle, .055 if tall else .09, mat["accent"]) cylinder("Leg" + side + "Knee", knee, .085 if tall else .13, .16, mat["secondary"], vertices=8, rotation=(pi/2, 0, 0)) _slab("Leg" + side + "Shoe", (x, -.105, .065), (.23 if tall else .33, .36, .13), mat["primary"], .04) if upgraded: _slab("Leg" + side + "Greave", (x, -.115, .25), (.17 if tall else .27, .12, .23), mat["primary"], .04) def _machine_mask(mat, z=1.22): _profile("HeadCeramicMask", [(-.14,z-.11), (.14,z-.11), (.17,z+.06), (0,z+.18), (-.17,z+.06)], -.205, .12, mat["light"]) _eyes("Head", 0, -.272, z+.025, .067, mat["primary"], .060) def _shear_body(mat, upgraded): ellipsoid("BodySpindle", (0, .02, .91), (.285 if upgraded else .21, .235, .32), mat["primary"]) cylinder("BodyOcherHub", (0, -.224, .87), .14, .07, mat["secondary"], vertices=10, rotation=(pi/2, 0, 0)) cylinder("BodyHubPin", (0, -.267, .87), .057, .025, mat["accent"], vertices=8, rotation=(pi/2, 0, 0)) _machine_mask(mat, 1.235) if upgraded: _slab("BodyHubArmor", (0, .15, .98), (.57, .21, .37), mat["primary"]) cone("HeadFin", (0, .055, 1.385), .065, 0, .21, mat["secondary"], vertices=4) def _shear_weapons(mat, upgraded): for side, s in (("L", -1), ("R", 1)): if upgraded: for i, (z, angle) in enumerate(((1.10, .62), (.89, 1.32), (.66, 2.07))): pivot = (s*.47, -.105, z) beam("Arm" + side + "Link%02d" % i, (s*.21, .02, .93), pivot, .049, mat["secondary"]) cylinder("Arm" + side + "Hub%02d" % i, pivot, .076, .10, mat["secondary"], vertices=8, rotation=(pi/2, 0, 0)) _blade("Weapon" + side + "Blade%02d" % i, (pivot[0], -.17, z), .39, .105, s*angle, mat["accent"], .055, s*.07) else: pivot = (s*.43, -.12, .95) beam("Arm" + side + "Upper", (s*.16, .025, 1.0), (s*.34, .04, .82), .065, mat["secondary"]) beam("Arm" + side + "Forearm", (s*.34, .04, .82), pivot, .060, mat["primary"]) for i, angle in enumerate((.39, 1.07)): _blade("Weapon" + side + "Shear%02d" % i, (pivot[0], -.19-i*.045, pivot[2]), .42, .10, s*angle, mat["accent"], .055, s*.035) cylinder("Weapon" + side + "ShearPivot", (s*.43, -.264, .95), .092, .055, mat["secondary"], vertices=10, rotation=(pi/2, 0, 0)) def _boiler_body(mat, upgraded): cylinder("BodyBoiler", (0, .01, .77), .385, .66, mat["primary"], vertices=12) cone("BodyBoilerLid", (0, .01, 1.115), .39, .27, .14, mat["primary"], vertices=12) ring("BodyLowerRim", (0, .01, .475), .38, .035, mat["secondary"], segments=12) _slab("HeadCeramicFace", (0, -.355, .98), (.32, .105, .19), mat["light"], .04) _eyes("Head", 0, -.414, 1.005, .074, mat["primary"], .060) cylinder("BodyGaugeRim", (0, -.402, .695), .117, .07, mat["secondary"], vertices=12, rotation=(pi/2, 0, 0)) cylinder("BodyGaugeFace", (0, -.444, .695), .085, .016, mat["light"], vertices=12, rotation=(pi/2, 0, 0)) beam("BodyGaugeNeedle", (0, -.458, .695), (.047, -.458, .747), .012, mat["primary"], vertices=4) if upgraded: _slab("BodyChestArmor", (0, -.10, .82), (.81, .55, .61), mat["primary"], .12) # Face and gauge sit ahead of the armour, never buried inside it. for side, s in (("L", -1), ("R", 1)): cylinder("BodyPressureCylinder" + side, (s*.48, .06, 1.075), .145, .54, mat["primary"], vertices=10) cylinder("BodyPressureCap" + side, (s*.48, .06, 1.36), .16, .07, mat["secondary"], vertices=10) beam("BodyPressurePipe" + side, (s*.48, .13, .84), (s*.30, .25, .66), .048, mat["accent"]) box("HeadBrow", (0, -.37, 1.12), (.46, .17, .09), mat["light"]) else: cylinder("BodyChimney", (.235, .10, 1.245), .08, .23, mat["accent"], vertices=8) cylinder("BodyChimneyLip", (.235, .10, 1.365), .10, .045, mat["secondary"], vertices=8) def _boiler_arms(mat, upgraded): for side, s in (("L", -1), ("R", 1)): beam("Arm" + side + "Piston", (s*.33, .0, .88), (s*.52, -.02, .63), .09, mat["accent"]) _slab("Arm" + side + "Fist", (s*.53, -.11, .53), (.29, .34, .28), mat["accent"], .045) if upgraded and side == "L": _slab("WeaponLPressureShield", (-.57, -.245, .75), (.37, .20, .69), mat["primary"], .08) _slab("WeaponLShieldStripe", (-.57, -.356, .75), (.085, .025, .50), mat["secondary"], .025) elif upgraded: cylinder("WeaponRPistonCap", (.55, -.302, .59), .165, .08, mat["secondary"], vertices=8, rotation=(pi/2, 0, 0)) def _mist_legs(mat, tall=False, upgraded=False): for side, s in (("L", -1), ("R", 1)): hip = (s*.12, .045, .71 if tall else .48) knee = (s*.25, .06 if side == "L" else -.04, .32) ankle = (s*.20, -.12 if side == "L" else .02, .095) beam("Leg" + side + "FoldUpper", hip, knee, .10 if tall else .13, mat["primary"], vertices=5) beam("Leg" + side + "FoldLower", knee, ankle, .075, mat["secondary"], vertices=5) _profile("Leg" + side + "TendrilFoot", [(s*.20-.09,.035), (s*.20+.11,.035), (s*.20+.07,.14), (s*.20-.045,.20)], ankle[1]-.045, .20, mat["primary"]) def _mist_mask(mat, z, large=False): sx, sz = (.235, .29) if large else (.158, .218) ellipsoid("HeadMistMask", (0, -.17, z), (sx, .105, sz), mat["light"], subdivisions=2) _eyes("Head", 0, -.273, z+.035, sx*.43, mat["accent"], sx*.42) _profile("HeadMaskChin", [(-sx*.50,z-.10), (0,z-sz*.92), (sx*.50,z-.10)], -.252, .022, mat["light"]) def _dancer_body(mat, upgraded): _profile("BodyFoldedRibbon", [(-.16,.56), (.10,.60), (.205,.97), (.105,1.16), (-.06,1.13), (-.195,.86)], .025, .27, mat["primary"]) _profile("BodyVioletFold", [(-.145,.64), (-.04,.67), (.15,1.075), (.05,1.12)], -.13, .038, mat["secondary"]) _mist_mask(mat, 1.23) if upgraded: _crescent("HeadCrescentCrest", (0, .005, 1.37), .20, .065, .08*pi, .92*pi, mat["secondary"], .075, 8) _blade("BodyBackRibbonL", (-.12,.15,.94), .40, .13, -.9, mat["secondary"], .06, -.05) _blade("BodyBackRibbonR", (.12,.15,.94), .40, .13, .9, mat["secondary"], .06, .05) def _dancer_weapons(mat, upgraded): if upgraded: # One large open scissor: two distinct sweeping blades at one mint hub. pivot = (.19, -.32, .79) beam("ArmLUpper", (-.14,.015,1.05), (-.33,-.11,.85), .065, mat["secondary"], vertices=5) beam("ArmLForearm", (-.33,-.11,.85), pivot, .061, mat["primary"], vertices=5) beam("ArmRUpper", (.14,.015,1.05), (.36,-.12,.96), .065, mat["primary"], vertices=5) beam("ArmRForearm", (.36,-.12,.96), pivot, .061, mat["secondary"], vertices=5) _blade("WeaponScissorUpper", pivot, .69, .18, .57, mat["light"], .075, .09) _blade("WeaponScissorLower", (.19,-.405,.79), .66, .18, 1.42, mat["light"], .075, .04) ring("WeaponScissorHandleL", (.04,-.37,.655), .09, .025, mat["secondary"], rotation=(pi/2,0,0), segments=8) ring("WeaponScissorHandleR", (-.005,-.37,.79), .09, .025, mat["secondary"], rotation=(pi/2,0,0), segments=8) cylinder("WeaponScissorMintPivot", (.19,-.463,.79), .087, .028, mat["accent"], vertices=12, rotation=(pi/2,0,0)) else: for side, s, high in (("L", -1, False), ("R", 1, True)): elbow = (s*.31, -.04, .91 if high else .80) wrist = (s*.43, -.12, 1.07 if high else .66) beam("Arm" + side + "Upper", (s*.14,.01,1.02), elbow, .065, mat["primary"], vertices=5) beam("Arm" + side + "Forearm", elbow, wrist, .052, mat["secondary"], vertices=5) _blade("Weapon" + side + "ThreadBlade", wrist, .40, .065, s*(.50 if high else 2.12), mat["light"], .045, s*.045) cylinder("Weapon" + side + "MintGrip", wrist, .060, .09, mat["accent"], vertices=8, rotation=(pi/2,0,0)) def _mist_tank_body(mat, upgraded): ellipsoid("BodyFoldedMist", (0,.035,.73), (.37,.25,.44), mat["primary"], subdivisions=2) _profile("BodyVioletCenterFold", [(-.22,.43), (.035,.39), (.25,.90), (.04,1.09)], -.205, .060, mat["secondary"]) _mist_mask(mat, 1.145, large=True) _crescent("BodyFrontCrescent", (0,-.32,.89), .44, .145, .98*pi, 2.02*pi, mat["secondary"], .15, 12) if upgraded: _crescent("BodyOuterShield", (0,-.245,.99), .655, .17, .89*pi, 2.11*pi, mat["light"], .15, 12) _crescent("BodyRearShield", (0,.11,1.025), .60, .16, .05*pi, .95*pi, mat["secondary"], .17, 12) for side, s in (("L",-1),("R",1)): _blade("BodyShieldWing" + side, (s*.44,.005,.72), .49,.23,s*.39,mat["secondary"],.16,s*.10) _blade("BodyShieldMintEdge" + side, (s*.49,-.09,.79), .31,.047,s*.39,mat["accent"],.025,s*.065) def _mist_tank_arms(mat, upgraded): for side, s in (("L",-1),("R",1)): beam("Arm" + side + "Fold", (s*.31,.005,.92), (s*.43,-.045,.67), .080, mat["primary"], vertices=5) _crescent("Arm" + side + "Crescent", (s*.42,-.14,.72), .24,.075, -.4*pi if s < 0 else .6*pi, .6*pi if s < 0 else 1.6*pi, mat["secondary"],.095,8) def _root_legs(mat, upgraded=False, four=False, spread=.32): positions = [("L", -1, -.035), ("R", 1, -.035)] if four: positions = [("LFront",-1,-.27),("RFront",1,-.27), ("LRear",-1,.28),("RRear",1,.28)] for label, s, y in positions: beam("Leg" + label + "Root", (s*spread*.65,y,.48), (s*spread,y-.035,.13), .11 if four else .13, mat["primary"], vertices=6) _slab("Leg" + label + "Foot", (s*spread,y-.10,.075), (.28,.36,.15), mat["primary"], .065) # One broad toe root is stronger at gameplay scale than many twigs. beam("Leg" + label + "Toe", (s*spread,y-.12,.095), (s*spread+s*.06,y-.31,.055), .045, mat["accent"], vertices=5) def _leaf(name, origin, length, width, angle, mat, depth=.055): x,y,z = origin local = [(0,0),(width*.55,length*.44),(0,length), (-width*.55,length*.44)] points = [(x+u*cos(angle)+v*sin(angle), z-u*sin(angle)+v*cos(angle)) for u,v in local] return _profile(name,points,y,depth,mat) def _thorn_body(mat, upgraded): if upgraded: ellipsoid("BodyCoiledRoot", (0,.08,.63), (.48,.34,.39), mat["primary"],subdivisions=2) ring("BodyRootCoil", (0,.065,.61), .365,.085, mat["primary"],segments=10) ellipsoid("HeadUpperJaw", (0,-.16,1.07), (.55,.34,.26), mat["primary"],subdivisions=2) _slab("HeadLowerJaw", (0,-.265,.66), (.87,.44,.22), mat["primary"],.085) _slab("HeadMouthDepth", (0,-.26,.84), (.78,.20,.28), mat["secondary"],.08) for i,x in enumerate((-.30,-.10,.10,.30)): _profile("WeaponUpperFang%02d" % i, [(x-.065,1.04),(x,.835),(x+.065,1.04)], -.485,.055,mat["accent"]) for i,x in enumerate((-.23,0,.23)): _profile("WeaponLowerFang%02d" % i, [(x-.07,.74),(x+.07,.74),(x,.91)], -.497,.055,mat["accent"]) for side,s in (("L",-1),("R",1)): _leaf("HeadCheekLeaf" + side,(s*.40,-.29,.82),.39,.29, s*.59,mat["secondary"],.11) _leaf("HeadCrownLeaf" + side,(s*.15,.01,1.21),.23,.15, s*.67,mat["accent"]) _eyes("Head",0,-.425,1.155,.24,mat["accent"],.09) else: ellipsoid("BodyThornPod", (0,.03,.76), (.43,.34,.43), mat["primary"],subdivisions=2) _slab("HeadSlit",(0,-.308,.87),(.29,.045,.075),mat["secondary"],.025) _eyes("Head",0,-.337,.96,.105,mat["accent"],.055) for side,s in (("L",-1),("R",1)): _leaf("HeadSprout" + side,(s*.10,.025,1.09),.31,.19, s*.55,mat["secondary"],.07) _blade("WeaponSideThorn" + side,(s*.34,.02,.76), .24,.13,s*1.36,mat["secondary"],.13,s*.015) for i,(x,y,z) in enumerate(((-.22,-.08,1.07),(.22,-.08,1.07), (0,.285,.90))): cone("BodyThorn%02d" % i,(x,y,z),.09,0,.25, mat["accent"],vertices=5) def _bark_head(mat, upgraded): ellipsoid("HeadLowRoot", (0,-.36,.50), (.24,.23,.18), mat["primary"]) _eyes("Head",0,-.559,.54,.097,mat["accent"],.077) if upgraded: _slab("HeadBrowBark",(0,-.43,.68),(.55,.23,.14), mat["primary"],.045) def _bark_shell(mat, upgraded): ellipsoid("BodyCorkShell", (0,.07,.80), (.57,.49,.49), mat["primary"],subdivisions=2) if upgraded: # Three actual layered trunk cross sections, readable from above/front. for index,(x,y,z,r) in enumerate(((0,-.345,.91,.42), (-.42,.06,.99,.32), (.42,.06,.99,.32))): cylinder("BodyGrowthShield%02d" % index,(x,y,z),r,.17, mat["primary"],vertices=12,rotation=(pi/2,0,0)) for band,ratio in enumerate((.80,.50)): ring("BodyGrowthRing%02d_%02d" % (index,band), (x,y-.097,z),r*ratio,.022, mat["accent"],rotation=(pi/2,0,0),segments=12) cylinder("BodyGrowthHeart%02d" % index,(x,y-.099,z),r*.18,.02, mat["secondary"],vertices=8,rotation=(pi/2,0,0)) _leaf("BodyCrownLeafL",(-.09,.16,1.19),.25,.19,-.67, mat["secondary"],.055) _leaf("BodyCrownLeafR",(.09,.16,1.19),.25,.19,.67, mat["accent"],.055) else: # Asymmetric overlapping cork plates make a turtle-like living shield. for i,(x,y,z,w,h) in enumerate(((-.26,-.015,1.04,.39,.36), (.18,-.07,1.11,.42,.31), (0,.245,.93,.53,.39))): _slab("BodyCorkPlate%02d" % i,(x,y,z),(w,.29,h), mat["primary"],.08) _leaf("BodyMossPlateL",(-.39,-.065,.87),.24,.20,-.49, mat["secondary"],.08) _leaf("BodyMossPlateR",(.31,.04,1.06),.20,.19,.57, mat["secondary"],.06) def _faction(spec): faction = str(spec["faction"]).lower() aliases = {"g":"glutpakt", "m":"nietenkonvent", "n":"nebelchor", "w":"wurzelbund"} return aliases.get(faction, faction) def build_melee(spec, mat): """Build a faction-specific melee unit or its visibly evolved upgrade.""" faction = _faction(spec) upgraded = bool(spec.get("upgraded", False)) if faction == "glutpakt": _stone_legs(mat, upgraded) _coal_body(mat, upgraded) _coal_claws(mat, upgraded) elif faction == "nietenkonvent": _machine_legs(mat, upgraded, tall=True) _shear_body(mat, upgraded) _shear_weapons(mat, upgraded) elif faction == "nebelchor": _mist_legs(mat, tall=True, upgraded=upgraded) _dancer_body(mat, upgraded) _dancer_weapons(mat, upgraded) elif faction == "wurzelbund": _root_legs(mat, upgraded, four=upgraded, spread=.34 if upgraded else .27) _thorn_body(mat, upgraded) else: raise ValueError("Unknown Saumwacht melee faction: " + faction) def build_tank(spec, mat): """Build a tank; upgrades add fortress tiers, pressure shields or growth rings.""" faction = _faction(spec) upgraded = bool(spec.get("upgraded", False)) if faction == "glutpakt": _stone_legs(mat, upgraded) _basalt_body(mat, upgraded) _basalt_arms(mat, upgraded) elif faction == "nietenkonvent": _machine_legs(mat, upgraded) _boiler_body(mat, upgraded) _boiler_arms(mat, upgraded) elif faction == "nebelchor": _mist_legs(mat, upgraded=upgraded) _mist_tank_body(mat, upgraded) _mist_tank_arms(mat, upgraded) elif faction == "wurzelbund": _root_legs(mat, upgraded, four=True, spread=.38 if upgraded else .33) _bark_shell(mat, upgraded) _bark_head(mat, upgraded) else: raise ValueError("Unknown Saumwacht tank faction: " + faction)