Top windet sich über das Hochland, Mid quert die Kluft über die Mittelplattform, Bottom führt durch das Tiefland über die Deltabrücke; Top und Bottom sind exakt gleich lang, alles bleibt spiegelsymmetrisch. LuneTerrain liefert ein exakt gespiegeltes Höhenfeld, auf dem Figuren stehen. Bossgrotte als Terrasse auf der Achse zwischen Top und Mid, Aufmarschbereiche je Lane, perspektivische Kamera mit Sprungtasten (F2–F4, B, G, H, O, R, Q/E). Bestandsassets wiederverwendet, neue Formen als Blockout für G2/G4. Geprüft: verify_g1 443/443, Regeln 91/91, M2 73/73, verify_m1 38/38, verify_m2 14/14, Netz 43/43, Loopback 12/12, Balance 40 Partien (Seitentausch 20/20), Legacy 209/463. Vorher/Nachher-Aufnahmen und Messungen in G1-KARTE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
4.1 KiB
GDScript
80 lines
4.1 KiB
GDScript
extends Control
|
|
## Minimap drawn from the authoritative match state. Click or drag to move the camera.
|
|
|
|
signal focus_requested(point: Vector3)
|
|
const TEAM_COLORS: Array[Color] = [Color("5aa8ff"), Color("ff6a55"), Color("b37cff")]
|
|
var match_state: LuneMatch
|
|
var camera_focus := Vector3.ZERO
|
|
var camera_span: float = 34.0
|
|
## Ground outline of the game camera's view (perspective trapezoid); empty = rectangle.
|
|
var camera_footprint := PackedVector2Array()
|
|
|
|
func _ready() -> void:
|
|
custom_minimum_size = Vector2(236, 236)
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
focus_mode = Control.FOCUS_NONE
|
|
tooltip_text = "Minimap · Klicken oder ziehen, um die Kamera zu bewegen"
|
|
|
|
func to_map(v: Vector2) -> Vector2:
|
|
var extent: float = LuneMap.HALF_EXTENT + 4.0
|
|
return (v + Vector2(extent, extent)) / (extent * 2.0) * size
|
|
|
|
func to_world(p: Vector2) -> Vector3:
|
|
var extent: float = LuneMap.HALF_EXTENT + 4.0
|
|
var v: Vector2 = p / size * extent * 2.0 - Vector2(extent, extent)
|
|
return Vector3(v.x, 0, v.y)
|
|
|
|
func _gui_input(event: InputEvent) -> void:
|
|
if (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT) or (event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_LEFT):
|
|
focus_requested.emit(to_world(event.position))
|
|
accept_event()
|
|
|
|
func _draw() -> void:
|
|
draw_rect(Rect2(Vector2.ZERO, size), Color(.04, .06, .06, .92))
|
|
draw_rect(Rect2(Vector2.ZERO, size), Color("5c4a33"), false, 1.5)
|
|
if match_state == null:
|
|
return
|
|
# The Kluft: from the grotto spring along the symmetry axis into the south-eastern delta.
|
|
var spring: Vector2 = LuneMap.GROTTO + LuneMap.AXIS * LuneTerrain.ARENA_RADIUS
|
|
draw_line(to_map(spring), to_map(Vector2(40, 40)), Color(.12, .38, .42, .8), 7.0)
|
|
draw_circle(to_map(Vector2(33, 33)), 11.0, Color(.12, .38, .42, .8))
|
|
for lane: String in LuneMap.LANES:
|
|
var path: LuneMap.LanePath = match_state.map.lanes[lane]
|
|
var points := PackedVector2Array()
|
|
for p: Vector2 in path.points:
|
|
points.append(to_map(p))
|
|
draw_polyline(points, Color(.62, .56, .42), 4.0, true)
|
|
var grotto: Vector2 = to_map(LuneMap.GROTTO)
|
|
var claim: Dictionary = match_state.boss.claim
|
|
var grotto_color: Color = TEAM_COLORS[int(claim.team)] if not claim.is_empty() else (Color(.62, .42, .95) if match_state.grotto_available() else Color(.35, .32, .4))
|
|
draw_circle(grotto, 6.0, grotto_color)
|
|
draw_arc(grotto, 6.0, 0, TAU, 16, Color(1, 1, 1, .6), 1.0)
|
|
var hp: Dictionary = {}
|
|
if match_state.phase == LuneMatch.Phase.COMBAT and match_state.combat != null:
|
|
for unit: LuneUnit in match_state.combat.units:
|
|
if unit.is_mobile() and unit.alive:
|
|
var radius: float = 4.0 if unit.kind == "boss" else (3.2 if unit.kind == "hero" else (1.8 if unit.kind == "monster" else 2.4))
|
|
draw_circle(to_map(unit.position), radius, Color("f2c96b") if unit.kind == "hero" else TEAM_COLORS[unit.team].lightened(.2 if unit.kind == "monster" else 0.0))
|
|
for definition: Dictionary in match_state.map.buildings:
|
|
var alive: bool = match_state.building_alive(definition.id)
|
|
if match_state.phase == LuneMatch.Phase.COMBAT and match_state.combat != null and match_state.combat.buildings.has(definition.id):
|
|
alive = (match_state.combat.buildings[definition.id] as LuneUnit).alive
|
|
var at: Vector2 = to_map(definition.position)
|
|
var radius: float = 6.5 if definition.tier == "core" else 4.0
|
|
var color: Color = TEAM_COLORS[int(definition.team)]
|
|
if alive:
|
|
draw_circle(at, radius, color)
|
|
draw_arc(at, radius, 0, TAU, 16, Color(1, 1, 1, .6), 1.0)
|
|
else:
|
|
draw_arc(at, radius, 0, TAU, 16, color.darkened(.3), 1.5)
|
|
draw_line(at - Vector2(radius, radius) * .6, at + Vector2(radius, radius) * .6, color.darkened(.3), 1.5)
|
|
if camera_footprint.size() == 4:
|
|
var outline := PackedVector2Array()
|
|
for corner: Vector2 in camera_footprint:
|
|
outline.append(to_map(corner).clamp(Vector2.ZERO, size))
|
|
outline.append(outline[0])
|
|
draw_polyline(outline, Color(1, 1, 1, .6), 1.2, true)
|
|
else:
|
|
var center: Vector2 = to_map(Vector2(camera_focus.x, camera_focus.z))
|
|
var span: float = camera_span / ((LuneMap.HALF_EXTENT + 4.0) * 2.0) * size.x
|
|
draw_rect(Rect2(center - Vector2(span * .9, span * .55), Vector2(span * 1.8, span * 1.1)), Color(1, 1, 1, .55), false, 1.2)
|