saumwacht-godot/scripts/luneraria/ui/lune_minimap.gd
Martin Stickelbroeck 8b17563814 G1: Drei-Lane-Layout mit Höhenfeld, Grotte, Aufmarschbereichen und MOBA-Kamera
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>
2026-09-29 00:09:48 +02:00

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)