class_name LuneGlyphs extends RefCounted ## Vector symbols for the HUD, drawn from a unit square so they stay sharp at every UI scale ## and resolution. Team emblems follow the G2 world: Blau = Mondsichel (round shapes), ## Rot = Sonnenscheibe (rayed, angular shapes), the grotto = hexagon. Symbols are drawn ## with draw_* calls into any CanvasItem; nothing here allocates nodes. const NAMES: Array[String] = ["coin", "emblem_0", "emblem_1", "emblem_2", "shape_0", "shape_1", "shape_2", "tank", "melee", "ranged", "splash", "support", "siege", "crown", "champion", "hourglass", "tower", "ancient", "warning", "check", "close", "bot", "offline", "clock", "heart", "tempo", "range", "armor", "move", "star", "lock", "up", "lane_top", "lane_mid", "lane_bot", "grotto", "pause", "play", "menu", "gear", "human", "sword", "basaltschild", "glutklinge", "rankenmantel", "funkenfokus", "quellamulett", "kristallsplitter", "hero", "skull"] static var _cache: Dictionary = {} ## Fits the unit square into rect (centred, aspect kept). static func _frame(rect: Rect2) -> Transform2D: var s: float = minf(rect.size.x, rect.size.y) var origin: Vector2 = rect.position + (rect.size - Vector2(s, s)) * .5 return Transform2D(0.0, Vector2(s, s), 0.0, origin) static func _pts(frame: Transform2D, points: Array) -> PackedVector2Array: var result := PackedVector2Array() for p: Vector2 in points: result.append(frame * p) return result static func _rot(points: Array, angle: float, pivot: Vector2 = Vector2(.5, .5)) -> Array: var result: Array = [] for p: Vector2 in points: result.append(pivot + (p - pivot).rotated(angle)) return result static func _fill(ci: CanvasItem, points: PackedVector2Array, color: Color) -> void: if points.size() < 3: return ci.draw_colored_polygon(points, color) var outline := points.duplicate() outline.append(points[0]) ci.draw_polyline(outline, color, 1.0, true) static func _line(ci: CanvasItem, frame: Transform2D, points: Array, color: Color, width: float) -> void: ci.draw_polyline(_pts(frame, points), color, maxf(1.0, width * frame.x.x), true) static func _circle(points: int, center: Vector2, radius: float, start: float = 0.0) -> Array: var result: Array = [] for index: int in range(points): var angle: float = start + TAU * float(index) / float(points) result.append(center + Vector2(cos(angle), sin(angle)) * radius) return result static func _star(points: int, outer: float, inner: float, start: float = -PI * .5) -> Array: var result: Array = [] for index: int in range(points * 2): var angle: float = start + PI * float(index) / float(points) result.append(Vector2(.5, .5) + Vector2(cos(angle), sin(angle)) * (outer if index % 2 == 0 else inner)) return result ## Crescent (Blau): outer disc minus an offset disc, computed once. static func _crescent() -> Array: if not _cache.has("crescent"): var outer := PackedVector2Array(_circle(48, Vector2(.5, .5), .42)) var inner := PackedVector2Array(_circle(48, Vector2(.66, .4), .33)) var clipped: Array = Geometry2D.clip_polygons(outer, inner) var result: Array = [] if not clipped.is_empty(): for p: Vector2 in clipped[0]: result.append(p) _cache["crescent"] = result return _cache["crescent"] static func _heart() -> Array: if not _cache.has("heart"): var result: Array = [] for index: int in range(40): var t: float = TAU * float(index) / 40.0 var x: float = 16.0 * pow(sin(t), 3) var y: float = 13.0 * cos(t) - 5.0 * cos(2.0 * t) - 2.0 * cos(3.0 * t) - cos(4.0 * t) result.append(Vector2(.5 + x / 38.0, .47 - y / 38.0)) _cache["heart"] = result return _cache["heart"] static func _drop() -> Array: var result: Array = [Vector2(.5, .16)] for index: int in range(17): var angle: float = -PI * .15 + PI * 1.3 * float(index) / 16.0 result.append(Vector2(.5, .64) + Vector2(cos(angle), sin(angle)) * .22) return result ## Draws one symbol. `accent` is an optional second colour (inner details); by default a ## darkened version of `color`. static func draw(ci: CanvasItem, glyph: String, rect: Rect2, color: Color, accent: Color = Color(0, 0, 0, 0)) -> void: var f: Transform2D = _frame(rect) var dark: Color = accent if accent.a > 0.0 else Color(color.darkened(.55), color.a) match glyph: "coin": var c: Vector2 = f * Vector2(.5, .5) var r: float = f.x.x * .42 ci.draw_circle(c, r, color) ci.draw_arc(c, r, 0, TAU, 32, color.darkened(.35), maxf(1.0, r * .12), true) ci.draw_arc(c, r * .62, 0, TAU, 24, color.darkened(.28), maxf(1.0, r * .1), true) ci.draw_arc(c, r * .8, PI * 1.1, PI * 1.45, 8, Color(1, 1, 1, .55 * color.a), maxf(1.0, r * .1), true) "emblem_0": _fill(ci, _pts(f, _crescent()), color) "emblem_1": ci.draw_circle(f * Vector2(.5, .5), f.x.x * .2, color) for index: int in range(8): var angle: float = TAU * float(index) / 8.0 - PI * .5 var tip := Vector2(.5, .5) + Vector2(cos(angle), sin(angle)) * .47 var left := Vector2(.5, .5) + Vector2(cos(angle - .22), sin(angle - .22)) * .27 var right := Vector2(.5, .5) + Vector2(cos(angle + .22), sin(angle + .22)) * .27 _fill(ci, _pts(f, [left, tip, right]), color) "emblem_2": var hexagon: Array = _circle(6, Vector2(.5, .5), .42, -PI * .5) hexagon.append(hexagon[0]) _line(ci, f, hexagon, color, .08) _fill(ci, _pts(f, [Vector2(.5, .3), Vector2(.64, .5), Vector2(.5, .7), Vector2(.36, .5)]), color) "shape_0": ci.draw_circle(f * Vector2(.5, .5), f.x.x * .4, color) "shape_1": _fill(ci, _pts(f, [Vector2(.5, .06), Vector2(.94, .5), Vector2(.5, .94), Vector2(.06, .5)]), color) "shape_2": _fill(ci, _pts(f, _circle(6, Vector2(.5, .5), .44, -PI * .5)), color) "tank", "armor": var shield: Array = [Vector2(.2, .16), Vector2(.8, .16), Vector2(.8, .48), Vector2(.72, .66), Vector2(.5, .87), Vector2(.28, .66), Vector2(.2, .48)] if glyph == "tank": _fill(ci, _pts(f, shield), color) _line(ci, f, [Vector2(.5, .24), Vector2(.5, .74)], dark, .07) else: shield.append(shield[0]) _line(ci, f, shield, color, .09) "melee", "sword": var blade: Array = [Vector2(.5, .06), Vector2(.56, .14), Vector2(.56, .62), Vector2(.44, .62), Vector2(.44, .14)] _fill(ci, _pts(f, _rot(blade, PI * .25)), color) _fill(ci, _pts(f, _rot([Vector2(.3, .62), Vector2(.7, .62), Vector2(.7, .69), Vector2(.3, .69)], PI * .25)), color) _fill(ci, _pts(f, _rot([Vector2(.46, .69), Vector2(.54, .69), Vector2(.54, .88), Vector2(.46, .88)], PI * .25)), color) "ranged": var bow: Array = [] for index: int in range(13): var angle: float = PI * .62 + PI * .76 * float(index) / 12.0 bow.append(Vector2(.78, .5) + Vector2(cos(angle), sin(angle)) * .5) _line(ci, f, _rot(bow, -PI * .25), color, .08) _line(ci, f, _rot([bow[0], bow[12]], -PI * .25), color, .03) _line(ci, f, _rot([Vector2(.14, .5), Vector2(.82, .5)], -PI * .25), color, .06) _fill(ci, _pts(f, _rot([Vector2(.9, .5), Vector2(.76, .41), Vector2(.76, .59)], -PI * .25)), color) "splash": _fill(ci, _pts(f, _star(8, .46, .2)), color) ci.draw_circle(f * Vector2(.5, .5), f.x.x * .1, dark) "support": _fill(ci, _pts(f, [Vector2(.4, .14), Vector2(.6, .14), Vector2(.6, .4), Vector2(.86, .4), Vector2(.86, .6), Vector2(.6, .6), Vector2(.6, .86), Vector2(.4, .86), Vector2(.4, .6), Vector2(.14, .6), Vector2(.14, .4), Vector2(.4, .4)]), color) "siege": _fill(ci, _pts(f, _rot([Vector2(.18, .18), Vector2(.74, .18), Vector2(.74, .42), Vector2(.18, .42)], -PI * .2)), color) _fill(ci, _pts(f, _rot([Vector2(.42, .4), Vector2(.52, .4), Vector2(.52, .92), Vector2(.42, .92)], -PI * .2)), color) "crown", "hero": _fill(ci, _pts(f, [Vector2(.12, .74), Vector2(.12, .3), Vector2(.32, .5), Vector2(.5, .2), Vector2(.68, .5), Vector2(.88, .3), Vector2(.88, .74)]), color) _fill(ci, _pts(f, [Vector2(.12, .78), Vector2(.88, .78), Vector2(.88, .88), Vector2(.12, .88)]), color) if glyph == "crown": ci.draw_circle(f * Vector2(.5, .6), f.x.x * .06, dark) "skull": _fill(ci, _pts(f, _circle(20, Vector2(.5, .44), .34)), color) _fill(ci, _pts(f, [Vector2(.32, .6), Vector2(.68, .6), Vector2(.64, .86), Vector2(.36, .86)]), color) ci.draw_circle(f * Vector2(.37, .45), f.x.x * .09, dark) ci.draw_circle(f * Vector2(.63, .45), f.x.x * .09, dark) "champion": _fill(ci, _pts(f, [Vector2(.5, .06), Vector2(.82, .46), Vector2(.61, .46), Vector2(.61, .9), Vector2(.39, .9), Vector2(.39, .46), Vector2(.18, .46)]), color) "hourglass": _fill(ci, _pts(f, [Vector2(.24, .12), Vector2(.76, .12), Vector2(.56, .5), Vector2(.44, .5)]), color) _fill(ci, _pts(f, [Vector2(.44, .5), Vector2(.56, .5), Vector2(.76, .88), Vector2(.24, .88)]), color) _line(ci, f, [Vector2(.18, .1), Vector2(.82, .1)], color, .06) _line(ci, f, [Vector2(.18, .9), Vector2(.82, .9)], color, .06) "tower": _fill(ci, _pts(f, [Vector2(.28, .92), Vector2(.28, .36), Vector2(.22, .36), Vector2(.22, .14), Vector2(.33, .14), Vector2(.33, .23), Vector2(.44, .23), Vector2(.44, .14), Vector2(.56, .14), Vector2(.56, .23), Vector2(.67, .23), Vector2(.67, .14), Vector2(.78, .14), Vector2(.78, .36), Vector2(.72, .36), Vector2(.72, .92)]), color) _fill(ci, _pts(f, [Vector2(.44, .92), Vector2(.44, .72), Vector2(.5, .66), Vector2(.56, .72), Vector2(.56, .92)]), dark) "ancient": _fill(ci, _pts(f, [Vector2(.5, .05), Vector2(.76, .4), Vector2(.6, .95), Vector2(.4, .95), Vector2(.24, .4)]), color) _line(ci, f, [Vector2(.5, .05), Vector2(.5, .95)], dark, .04) _line(ci, f, [Vector2(.24, .4), Vector2(.5, .52), Vector2(.76, .4)], dark, .04) "warning": _fill(ci, _pts(f, [Vector2(.5, .08), Vector2(.94, .88), Vector2(.06, .88)]), color) _fill(ci, _pts(f, [Vector2(.46, .34), Vector2(.54, .34), Vector2(.53, .64), Vector2(.47, .64)]), dark) ci.draw_circle(f * Vector2(.5, .75), f.x.x * .05, dark) "check": _line(ci, f, [Vector2(.16, .52), Vector2(.4, .76), Vector2(.86, .24)], color, .13) "close": _line(ci, f, [Vector2(.2, .2), Vector2(.8, .8)], color, .12) _line(ci, f, [Vector2(.8, .2), Vector2(.2, .8)], color, .12) "bot", "gear": var c: Vector2 = f * Vector2(.5, .5) for index: int in range(8): var angle: float = TAU * float(index) / 8.0 _fill(ci, _pts(f, _rot([Vector2(.43, .06), Vector2(.57, .06), Vector2(.57, .24), Vector2(.43, .24)], angle)), color) ci.draw_circle(c, f.x.x * .3, color) ci.draw_circle(c, f.x.x * .12, dark) "human": ci.draw_circle(f * Vector2(.5, .3), f.x.x * .17, color) _fill(ci, _pts(f, [Vector2(.2, .9), Vector2(.24, .62), Vector2(.38, .52), Vector2(.62, .52), Vector2(.76, .62), Vector2(.8, .9)]), color) "offline": ci.draw_arc(f * Vector2(.5, .5), f.x.x * .38, 0, TAU, 32, color, maxf(1.0, f.x.x * .1), true) _line(ci, f, [Vector2(.22, .78), Vector2(.78, .22)], color, .1) "clock": ci.draw_arc(f * Vector2(.5, .5), f.x.x * .4, 0, TAU, 32, color, maxf(1.0, f.x.x * .09), true) _line(ci, f, [Vector2(.5, .24), Vector2(.5, .5), Vector2(.68, .62)], color, .08) "heart": _fill(ci, _pts(f, _heart()), color) "tempo": _fill(ci, _pts(f, [Vector2(.6, .04), Vector2(.22, .56), Vector2(.46, .56), Vector2(.38, .96), Vector2(.78, .42), Vector2(.54, .42), Vector2(.66, .04)]), color) "range": var c: Vector2 = f * Vector2(.5, .5) ci.draw_arc(c, f.x.x * .38, 0, TAU, 32, color, maxf(1.0, f.x.x * .08), true) ci.draw_arc(c, f.x.x * .18, 0, TAU, 24, color, maxf(1.0, f.x.x * .08), true) ci.draw_circle(c, f.x.x * .06, color) for tick: Array in [[Vector2(.5, .02), Vector2(.5, .16)], [Vector2(.5, .84), Vector2(.5, .98)], [Vector2(.02, .5), Vector2(.16, .5)], [Vector2(.84, .5), Vector2(.98, .5)]]: _line(ci, f, tick, color, .07) "move": _line(ci, f, [Vector2(.18, .2), Vector2(.46, .5), Vector2(.18, .8)], color, .11) _line(ci, f, [Vector2(.52, .2), Vector2(.8, .5), Vector2(.52, .8)], color, .11) "star": _fill(ci, _pts(f, _star(5, .47, .2)), color) "lock": _fill(ci, _pts(f, [Vector2(.22, .46), Vector2(.78, .46), Vector2(.78, .9), Vector2(.22, .9)]), color) ci.draw_arc(f * Vector2(.5, .46), f.x.x * .19, PI, TAU, 16, color, maxf(1.0, f.x.x * .09), true) ci.draw_circle(f * Vector2(.5, .64), f.x.x * .06, dark) "up": _fill(ci, _pts(f, [Vector2(.5, .08), Vector2(.86, .48), Vector2(.62, .48), Vector2(.62, .9), Vector2(.38, .9), Vector2(.38, .48), Vector2(.14, .48)]), color) "lane_top", "lane_mid", "lane_bot": var box: Array = [Vector2(.12, .12), Vector2(.88, .12), Vector2(.88, .88), Vector2(.12, .88), Vector2(.12, .12)] _line(ci, f, box, Color(color, color.a * .3), .05) var path: Array = [Vector2(.2, .8), Vector2(.2, .2), Vector2(.8, .2)] if glyph == "lane_top" else ([Vector2(.2, .8), Vector2(.8, .2)] if glyph == "lane_mid" else [Vector2(.2, .8), Vector2(.8, .8), Vector2(.8, .2)]) _line(ci, f, path, color, .12) "grotto": var arch: Array = [Vector2(.08, .9)] for index: int in range(13): var angle: float = PI + PI * float(index) / 12.0 arch.append(Vector2(.5, .62) + Vector2(cos(angle) * .42, sin(angle) * .5)) arch.append(Vector2(.92, .9)) _fill(ci, _pts(f, arch), color) var mouth: Array = [Vector2(.3, .9)] for index: int in range(9): var angle: float = PI + PI * float(index) / 8.0 mouth.append(Vector2(.5, .72) + Vector2(cos(angle) * .2, sin(angle) * .26)) mouth.append(Vector2(.7, .9)) _fill(ci, _pts(f, mouth), dark) "pause": _fill(ci, _pts(f, [Vector2(.24, .16), Vector2(.42, .16), Vector2(.42, .84), Vector2(.24, .84)]), color) _fill(ci, _pts(f, [Vector2(.58, .16), Vector2(.76, .16), Vector2(.76, .84), Vector2(.58, .84)]), color) "play": _fill(ci, _pts(f, [Vector2(.26, .14), Vector2(.84, .5), Vector2(.26, .86)]), color) "menu": for y: float in [.24, .5, .76]: _line(ci, f, [Vector2(.16, y), Vector2(.84, y)], color, .1) "basaltschild": _fill(ci, _pts(f, _circle(6, Vector2(.5, .5), .44, -PI * .5)), color) var inner: Array = _circle(6, Vector2(.5, .5), .27, -PI * .5) inner.append(inner[0]) _line(ci, f, inner, dark, .07) "glutklinge": var blade: Array = [Vector2(.5, .06), Vector2(.57, .15), Vector2(.57, .66), Vector2(.43, .66), Vector2(.43, .15)] _fill(ci, _pts(f, _rot(blade, PI * .25)), color) _fill(ci, _pts(f, _rot([Vector2(.3, .66), Vector2(.7, .66), Vector2(.7, .73), Vector2(.3, .73)], PI * .25)), color) _fill(ci, _pts(f, [Vector2(.2, .5), Vector2(.3, .3), Vector2(.36, .42), Vector2(.42, .22), Vector2(.5, .44), Vector2(.4, .6)]), dark if accent.a > 0.0 else Color("ffb070")) "rankenmantel": _fill(ci, _pts(f, [Vector2(.5, .1), Vector2(.66, .18), Vector2(.8, .9), Vector2(.5, .8), Vector2(.2, .9), Vector2(.34, .18)]), color) _line(ci, f, [Vector2(.5, .22), Vector2(.42, .4), Vector2(.56, .56), Vector2(.46, .74)], dark, .05) "funkenfokus": ci.draw_arc(f * Vector2(.5, .5), f.x.x * .42, 0, TAU, 32, color, maxf(1.0, f.x.x * .08), true) _fill(ci, _pts(f, [Vector2(.56, .16), Vector2(.3, .54), Vector2(.47, .54), Vector2(.42, .84), Vector2(.7, .44), Vector2(.53, .44), Vector2(.62, .16)]), color) "quellamulett": ci.draw_arc(f * Vector2(.5, .2), f.x.x * .14, PI * .9, PI * 2.1, 12, color, maxf(1.0, f.x.x * .06), true) _fill(ci, _pts(f, _drop()), color) ci.draw_circle(f * Vector2(.44, .66), f.x.x * .06, Color(1, 1, 1, .45 * color.a)) "kristallsplitter": for spec: Array in [[Vector2(.5, .5), 0.0, .44], [Vector2(.3, .62), -.55, .3], [Vector2(.7, .62), .55, .3]]: var center: Vector2 = spec[0] var length: float = spec[2] var shard: Array = [center + Vector2(0, -length), center + Vector2(.08, 0), center + Vector2(0, length * .45), center + Vector2(-.08, 0)] _fill(ci, _pts(f, _rot(shard, spec[1], center)), color) _: ci.draw_circle(f * Vector2(.5, .5), f.x.x * .3, color)