Initial commit — Godot space roguelite source
- Touch controls: direct InputEventScreenTouch in shop_ui (bypass relay) - ItemDB: static preload list instead of DirAccess scan (export fix) - All 18 items with EN localisation (name_en, desc_en, category_en) - Ship playstyles: NOVA-1 shield, INFERNO ram, AURORA agile/tank - Quasar: SMBH visual, jet boost, merge, push, BH-eating - Atlas & UI text updated EN+DE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,673 @@
|
||||
extends Node2D
|
||||
|
||||
# ─── Atlas UI ────────────────────────────────────────────────────────────────
|
||||
# Beschreibt alle kosmischen Objekte, Schiffe und Events mit Live-Preview.
|
||||
# Previews verwenden die echten Spielobjekte (draw-Methoden identisch zum Spiel).
|
||||
# Sichtbar über das Hauptmenü (ATLAS). Sendet `closed` wenn beendet.
|
||||
|
||||
signal closed
|
||||
|
||||
const CosmicObjects = preload("res://scripts/cosmic_objects.gd")
|
||||
const BlackHoleClass = preload("res://scripts/black_hole.gd")
|
||||
const PlanetClass = preload("res://scripts/planet.gd")
|
||||
|
||||
# NOVA-1 Palette — exakt aus main.gd SHIPS[0]
|
||||
const PAL_NOVA1 := {
|
||||
"nose": Color("#ffffff"), "bright": Color("#dddddd"), "mid": Color("#cccccc"),
|
||||
"dim": Color("#aaaaaa"), "accent": Color("#88aaff"), "edge": Color("#888888"),
|
||||
"shadow": Color("#666688"), "trail": Color(0.533, 0.667, 1.0, 0.251),
|
||||
"thrustHot": Color("#ffcc44"), "thrustCool": Color(1.0, 0.533, 0.267, 0.533)
|
||||
}
|
||||
|
||||
const COL_BG := Color(0.0, 0.0, 0.04, 0.90)
|
||||
const COL_PRIMARY := Color(0.0, 1.0, 0.533, 1.0)
|
||||
const COL_ACCENT := Color(0.27, 1.0, 0.8, 1.0)
|
||||
const COL_DIM := Color(0.0, 0.27, 0.13, 0.55)
|
||||
const COL_WHITE := Color(1.0, 1.0, 1.0, 0.90)
|
||||
const COL_WARN := Color(1.0, 0.67, 0.0, 0.90)
|
||||
|
||||
var W: float = 960.0
|
||||
var H: float = 600.0
|
||||
var _blink: float = 0.0
|
||||
var _cursor: int = 0
|
||||
var _scroll: int = 0
|
||||
var _time: float = 0.0
|
||||
|
||||
# Preview box center (fixed for 960×600 viewport)
|
||||
# right_x=300, pbox_x=318, pbox_y=116, pbox_sz=180
|
||||
const PREV_CX := 408.0
|
||||
const PREV_CY := 206.0
|
||||
|
||||
# Preview objects — real game instances
|
||||
var _preview_obj = null # single object (most kinds)
|
||||
var _preview_list: Array = [] # multiple objects (antimatter)
|
||||
var _preview_kind: String = ""
|
||||
|
||||
# Entry schema:
|
||||
# kind: id used by preview dispatcher
|
||||
# cat: category key (atlas_cat_*)
|
||||
# name_key: translation key for name
|
||||
# desc_key: translation key for description
|
||||
# props: Array of [label_key, value_string] (value is NOT translated)
|
||||
const ENTRIES: Array = [
|
||||
{"kind": "star", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_star",
|
||||
"desc_key": "atlas_d_star", "props": [
|
||||
["atlas_prop_size", "1–7 px"],
|
||||
["atlas_prop_speed", "0.05–0.3 px/f"],
|
||||
["atlas_prop_spawn", "~150–300"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "planet_terr", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_planet_terr",
|
||||
"desc_key": "atlas_d_planet_terr", "props": [
|
||||
["atlas_prop_size", "5–10 px"],
|
||||
["atlas_prop_orbit", "19–72 px"],
|
||||
["atlas_prop_effect", "Wolken/Polkappen"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "planet_desert", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_planet_desert",
|
||||
"desc_key": "atlas_d_planet_desert", "props": [
|
||||
["atlas_prop_size", "5–10 px"],
|
||||
["atlas_prop_orbit", "19–72 px"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "planet_gas", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_planet_gas",
|
||||
"desc_key": "atlas_d_planet_gas", "props": [
|
||||
["atlas_prop_size", "12–19 px"],
|
||||
["atlas_prop_orbit", "19–72 px"],
|
||||
["atlas_prop_effect", "Ringe, Monde, Sturm"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "planet_ice", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_planet_ice",
|
||||
"desc_key": "atlas_d_planet_ice", "props": [
|
||||
["atlas_prop_size", "5–10 px"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "planet_lava", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_planet_lava",
|
||||
"desc_key": "atlas_d_planet_lava", "props": [
|
||||
["atlas_prop_size", "5–10 px"],
|
||||
["atlas_prop_effect", "Halo + Glut"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "planet_toxic", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_planet_toxic",
|
||||
"desc_key": "atlas_d_planet_toxic", "props": [
|
||||
["atlas_prop_size", "5–10 px"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "nebula", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_nebula",
|
||||
"desc_key": "atlas_d_nebula", "props": [
|
||||
["atlas_prop_size", "120–220 px"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "comet", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_comet",
|
||||
"desc_key": "atlas_d_comet", "props": [
|
||||
["atlas_prop_speed", "1–4 px/f"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "galaxy", "cat": "atlas_cat_cosmic", "name_key": "atlas_n_galaxy",
|
||||
"desc_key": "atlas_d_galaxy", "props": [
|
||||
["atlas_prop_size", "~80 px"],
|
||||
["atlas_prop_effect", "SMBH-Trigger"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "blackhole", "cat": "atlas_cat_exotic", "name_key": "atlas_n_blackhole",
|
||||
"desc_key": "atlas_d_blackhole", "props": [
|
||||
["atlas_prop_size", "14–40 px"],
|
||||
["atlas_prop_effect", "Pull 160 px"],
|
||||
["atlas_prop_hazard", "atlas_hazard_deadly"],
|
||||
]},
|
||||
{"kind": "whitehole", "cat": "atlas_cat_exotic", "name_key": "atlas_n_whitehole",
|
||||
"desc_key": "atlas_d_whitehole", "props": [
|
||||
["atlas_prop_size", "~16 px"],
|
||||
["atlas_prop_effect", "Push 340 px"],
|
||||
["atlas_prop_life", "~60 s"],
|
||||
["atlas_prop_hazard", "atlas_hazard_low"],
|
||||
]},
|
||||
{"kind": "neutron", "cat": "atlas_cat_exotic", "name_key": "atlas_n_neutron",
|
||||
"desc_key": "atlas_d_neutron", "props": [
|
||||
["atlas_prop_size", "~6 px"],
|
||||
["atlas_prop_effect", "Pulsar-Beam"],
|
||||
["atlas_prop_hazard", "atlas_hazard_low"],
|
||||
]},
|
||||
{"kind": "quasar", "cat": "atlas_cat_exotic", "name_key": "atlas_n_quasar",
|
||||
"desc_key": "atlas_d_quasar", "props": [
|
||||
["atlas_prop_life", "~30 s"],
|
||||
["atlas_prop_hazard", "atlas_hazard_none"],
|
||||
]},
|
||||
{"kind": "antimatter", "cat": "atlas_cat_anti", "name_key": "atlas_n_antimatter",
|
||||
"desc_key": "atlas_d_antimatter", "props": [
|
||||
["atlas_prop_size", "2 px"],
|
||||
["atlas_prop_hazard", "atlas_hazard_deadly"],
|
||||
]},
|
||||
{"kind": "antistar", "cat": "atlas_cat_anti", "name_key": "atlas_n_antistar",
|
||||
"desc_key": "atlas_d_antistar", "props": [
|
||||
["atlas_prop_life", "~50 s"],
|
||||
["atlas_prop_effect", "Push"],
|
||||
["atlas_prop_hazard", "atlas_hazard_deadly"],
|
||||
]},
|
||||
{"kind": "player", "cat": "atlas_cat_ships", "name_key": "atlas_n_player",
|
||||
"desc_key": "atlas_d_player", "props": [
|
||||
["atlas_prop_hp", "1 (+Schilde)"],
|
||||
["atlas_prop_speed", "7.5 Max"],
|
||||
["atlas_prop_damage", "1.0 (Pierce ≥2)"],
|
||||
]},
|
||||
{"kind": "enemy", "cat": "atlas_cat_ships", "name_key": "atlas_n_enemy",
|
||||
"desc_key": "atlas_d_enemy", "props": [
|
||||
["atlas_prop_hp", "1"],
|
||||
["atlas_prop_reward", "15 Credits"],
|
||||
["atlas_prop_hazard", "atlas_hazard_mid"],
|
||||
]},
|
||||
{"kind": "wraith", "cat": "atlas_cat_ships", "name_key": "atlas_n_wraith",
|
||||
"desc_key": "atlas_d_wraith", "props": [
|
||||
["atlas_prop_hp", "20"],
|
||||
["atlas_prop_reward", "150 Credits"],
|
||||
["atlas_prop_hazard", "atlas_hazard_high"],
|
||||
]},
|
||||
{"kind": "leviathan", "cat": "atlas_cat_ships", "name_key": "atlas_n_leviathan",
|
||||
"desc_key": "atlas_d_leviathan", "props": [
|
||||
["atlas_prop_hp", "50"],
|
||||
["atlas_prop_reward", "300 Credits"],
|
||||
["atlas_prop_hazard", "atlas_hazard_deadly"],
|
||||
]},
|
||||
{"kind": "bullet", "cat": "atlas_cat_events", "name_key": "atlas_n_bullet",
|
||||
"desc_key": "atlas_d_bullet", "props": [
|
||||
["atlas_prop_speed", "9.6 px/f"],
|
||||
["atlas_prop_life", "240 f"],
|
||||
]},
|
||||
{"kind": "bigwipe", "cat": "atlas_cat_events", "name_key": "atlas_n_bigwipe",
|
||||
"desc_key": "atlas_d_bigwipe", "props": [
|
||||
["atlas_prop_effect", "> 500 Objekte"],
|
||||
["atlas_prop_reward", "25 Credits"],
|
||||
]},
|
||||
]
|
||||
|
||||
|
||||
func open() -> void:
|
||||
visible = true
|
||||
_cursor = 0
|
||||
_scroll = 0
|
||||
_time = 0.0
|
||||
_rebuild_preview()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
_preview_obj = null
|
||||
_preview_list = []
|
||||
_preview_kind = ""
|
||||
closed.emit()
|
||||
|
||||
|
||||
# ─── Loop ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not visible: return
|
||||
_blink += delta
|
||||
_time += delta
|
||||
_update_preview(delta)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _update_preview(delta: float) -> void:
|
||||
match _preview_kind:
|
||||
"star":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta, 99999.0, 99999.0)
|
||||
_preview_obj.x = PREV_CX
|
||||
_preview_obj.y = PREV_CY
|
||||
"galaxy":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta, 99999.0, 99999.0)
|
||||
_preview_obj.x = PREV_CX
|
||||
_preview_obj.y = PREV_CY
|
||||
"blackhole":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta, [], 99999.0, 99999.0)
|
||||
_preview_obj.x = PREV_CX
|
||||
_preview_obj.y = PREV_CY
|
||||
"whitehole":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta)
|
||||
_preview_obj.x = PREV_CX
|
||||
_preview_obj.y = PREV_CY
|
||||
"neutron", "quasar":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta)
|
||||
"antistar":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta)
|
||||
_preview_obj.x = PREV_CX
|
||||
_preview_obj.y = PREV_CY
|
||||
"antimatter":
|
||||
for k: int in _preview_list.size():
|
||||
var am = _preview_list[k]
|
||||
var ang := float(k) * TAU / float(_preview_list.size()) + _time * 0.6
|
||||
var r := 20.0 + sin(_time + float(k)) * 12.0
|
||||
am.x = PREV_CX + cos(ang) * r
|
||||
am.y = PREV_CY + sin(ang) * r
|
||||
am.update(99999.0, 99999.0, delta, [])
|
||||
# reset position after update (velocity might shift it)
|
||||
am.x = PREV_CX + cos(ang) * r
|
||||
am.y = PREV_CY + sin(ang) * r
|
||||
"wraith", "leviathan":
|
||||
if _preview_obj:
|
||||
_preview_obj.update(delta, PREV_CX, PREV_CY)
|
||||
"planet_terr", "planet_desert", "planet_gas", "planet_ice", "planet_lava", "planet_toxic":
|
||||
if _preview_obj and _preview_obj.has_method("update"):
|
||||
_preview_obj.update(delta)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not visible: return
|
||||
if event.is_action_pressed("ui_up"):
|
||||
_cursor = (_cursor - 1 + ENTRIES.size()) % ENTRIES.size()
|
||||
_ensure_cursor_visible()
|
||||
_rebuild_preview()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("ui_down"):
|
||||
_cursor = (_cursor + 1) % ENTRIES.size()
|
||||
_ensure_cursor_visible()
|
||||
_rebuild_preview()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("ui_cancel") or event.is_action_pressed("ui_accept"):
|
||||
close()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _ensure_cursor_visible() -> void:
|
||||
var visible_rows := 14
|
||||
if _cursor < _scroll:
|
||||
_scroll = _cursor
|
||||
elif _cursor >= _scroll + visible_rows:
|
||||
_scroll = _cursor - visible_rows + 1
|
||||
|
||||
|
||||
func _rebuild_preview() -> void:
|
||||
var kind: String = ENTRIES[_cursor]["kind"]
|
||||
_preview_kind = kind
|
||||
_preview_obj = null
|
||||
_preview_list = []
|
||||
_time = 0.0
|
||||
|
||||
match kind:
|
||||
"star":
|
||||
var s := CosmicObjects.Star.new()
|
||||
s.init(PREV_CX, PREV_CY, 99999.0, 99999.0)
|
||||
_preview_obj = s
|
||||
|
||||
"nebula":
|
||||
var n := CosmicObjects.Nebula.new()
|
||||
n.init(99999.0, 99999.0)
|
||||
n.x = PREV_CX
|
||||
n.y = PREV_CY
|
||||
_preview_obj = n
|
||||
|
||||
"comet":
|
||||
var c := CosmicObjects.Comet.new()
|
||||
c.init(99999.0, 99999.0)
|
||||
# Position it so it travels across the preview box area
|
||||
c.x = PREV_CX - 70.0
|
||||
c.y = PREV_CY - 40.0
|
||||
_preview_obj = c
|
||||
|
||||
"galaxy":
|
||||
var g := CosmicObjects.Galaxy.new()
|
||||
g.init(PREV_CX, PREV_CY)
|
||||
_preview_obj = g
|
||||
|
||||
"blackhole":
|
||||
var bh := BlackHoleClass.new()
|
||||
bh.init(PREV_CX, PREV_CY, false)
|
||||
_preview_obj = bh
|
||||
|
||||
"whitehole":
|
||||
var wh := CosmicObjects.WhiteHole.new()
|
||||
wh.init(PREV_CX, PREV_CY)
|
||||
_preview_obj = wh
|
||||
|
||||
"neutron":
|
||||
var ns := CosmicObjects.NeutronStar.new()
|
||||
ns.init(PREV_CX, PREV_CY)
|
||||
_preview_obj = ns
|
||||
|
||||
"quasar":
|
||||
var q := CosmicObjects.Quasar.new()
|
||||
q.init(PREV_CX, PREV_CY)
|
||||
_preview_obj = q
|
||||
|
||||
"antimatter":
|
||||
for k: int in 10:
|
||||
var am := CosmicObjects.Antimatter.new()
|
||||
var ang := float(k) * TAU / 10.0
|
||||
var r := 22.0
|
||||
am.init(PREV_CX + cos(ang) * r, PREV_CY + sin(ang) * r, ang, 0.0)
|
||||
_preview_list.append(am)
|
||||
|
||||
"antistar":
|
||||
var a := CosmicObjects.AntimatterStar.new()
|
||||
a.init(PREV_CX, PREV_CY)
|
||||
_preview_obj = a
|
||||
|
||||
"player":
|
||||
var sp := Spaceship.new()
|
||||
sp.init(PREV_CX, PREV_CY, PAL_NOVA1, 0)
|
||||
sp.heading = -PI * 0.5 # zeigt nach oben
|
||||
_preview_obj = sp
|
||||
|
||||
"enemy":
|
||||
var en := EnemyShip.new()
|
||||
en.init(PREV_CX, PREV_CY, 0) # idx=0 → roter Gegner
|
||||
en.heading = PI * 0.5
|
||||
_preview_obj = en
|
||||
|
||||
"wraith":
|
||||
var boss := BossShip.new()
|
||||
boss.init_miniboss(PREV_CX, PREV_CY)
|
||||
_preview_obj = boss
|
||||
|
||||
"leviathan":
|
||||
var boss := BossShip.new()
|
||||
boss.init_boss(PREV_CX, PREV_CY)
|
||||
_preview_obj = boss
|
||||
|
||||
"planet_terr", "planet_desert", "planet_gas", "planet_ice", "planet_lava", "planet_toxic":
|
||||
var p := PlanetClass.new()
|
||||
p.init(0.0, 0.0, 960.0, 600.0)
|
||||
p.ptype = _ptype_for(kind)
|
||||
p._setup_palette()
|
||||
p._setup_noise()
|
||||
p._setup_animation()
|
||||
if kind == "planet_gas":
|
||||
p.radius = 22.0
|
||||
else:
|
||||
p.radius = 18.0
|
||||
p.initial_radius = p.radius
|
||||
p.color = p.palette[0]
|
||||
if kind == "planet_gas":
|
||||
p.ring = true
|
||||
p.ring_count = 2
|
||||
p.ring_inner = p.radius + 6.0
|
||||
p.ring_outer = p.ring_inner + 8.0
|
||||
p.ring_tilt = 0.28
|
||||
p.ring_colors = [p.palette[0], p.palette[2]]
|
||||
p.ring_gaps = []
|
||||
else:
|
||||
p.ring = false
|
||||
p.moons.clear()
|
||||
_preview_obj = p
|
||||
|
||||
# bullet / bigwipe: no live object, drawn procedurally below
|
||||
|
||||
|
||||
func _ptype_for(kind: String) -> int:
|
||||
match kind:
|
||||
"planet_terr": return PlanetClass.PType.TERRESTRIAL
|
||||
"planet_desert": return PlanetClass.PType.DESERT
|
||||
"planet_gas": return PlanetClass.PType.GAS_GIANT
|
||||
"planet_ice": return PlanetClass.PType.ICE
|
||||
"planet_lava": return PlanetClass.PType.LAVA
|
||||
"planet_toxic": return PlanetClass.PType.TOXIC
|
||||
return 0
|
||||
|
||||
|
||||
# ─── Drawing ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func _draw() -> void:
|
||||
var vs: Vector2 = get_viewport_rect().size
|
||||
W = vs.x; H = vs.y
|
||||
|
||||
# Full-screen darkening
|
||||
draw_rect(Rect2(0, 0, W, H), COL_BG)
|
||||
|
||||
# Outer brackets
|
||||
_draw_brackets(12.0, 12.0, W - 12.0, H - 12.0, COL_DIM, 18.0)
|
||||
|
||||
# Title header
|
||||
_draw_text_c(Tr.t("atlas_title"), W * 0.5, 26.0, 14, COL_PRIMARY)
|
||||
var line_y := 54.0
|
||||
draw_line(Vector2(W * 0.25, line_y), Vector2(W * 0.75, line_y),
|
||||
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.35), 1.0)
|
||||
|
||||
# Layout
|
||||
var top := 70.0
|
||||
var bottom := H - 40.0
|
||||
var list_x := 24.0
|
||||
var list_w := 260.0
|
||||
var right_x := list_x + list_w + 16.0
|
||||
var right_w := W - right_x - 24.0
|
||||
|
||||
_draw_list(list_x, top, list_w, bottom - top)
|
||||
_draw_details(right_x, top, right_w, bottom - top)
|
||||
|
||||
# Footer
|
||||
_draw_text_c(Tr.t("atlas_footer"), W * 0.5, H - 22.0, 8, COL_DIM)
|
||||
|
||||
|
||||
func _draw_list(x: float, y: float, w: float, h: float) -> void:
|
||||
_draw_terminal_box(x, y, w, h)
|
||||
var visible_rows := 14
|
||||
var row_h := 22.0
|
||||
var last_cat := ""
|
||||
var draw_y := y + 14.0
|
||||
var i := _scroll
|
||||
var rows_left := visible_rows
|
||||
while i < ENTRIES.size() and rows_left > 0:
|
||||
var entry: Dictionary = ENTRIES[i]
|
||||
var cat: String = entry["cat"]
|
||||
if cat != last_cat:
|
||||
var ch := Tr.t(cat)
|
||||
_draw_text(ch, x + 14.0, draw_y, 9,
|
||||
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.55))
|
||||
draw_y += 14.0
|
||||
last_cat = cat
|
||||
var is_sel := (i == _cursor)
|
||||
var pulse := 0.6 + 0.4 * sin(_blink * 3.0)
|
||||
var col: Color
|
||||
if is_sel:
|
||||
col = Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, pulse)
|
||||
else:
|
||||
col = Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.60)
|
||||
var prefix := "▶ " if is_sel else " "
|
||||
_draw_text(prefix + Tr.t(entry["name_key"]), x + 20.0, draw_y, 10, col)
|
||||
draw_y += row_h - 2.0
|
||||
i += 1
|
||||
rows_left -= 1
|
||||
|
||||
# Scroll indicators
|
||||
if _scroll > 0:
|
||||
_draw_text(" ▲", x + w - 24.0, y + 6.0, 10, COL_DIM)
|
||||
if _scroll + visible_rows < ENTRIES.size():
|
||||
_draw_text(" ▼", x + w - 24.0, y + h - 22.0, 10, COL_DIM)
|
||||
|
||||
|
||||
func _draw_details(x: float, y: float, w: float, h: float) -> void:
|
||||
_draw_terminal_box(x, y, w, h)
|
||||
var entry: Dictionary = ENTRIES[_cursor]
|
||||
|
||||
# Name header
|
||||
var name_y := y + 18.0
|
||||
_draw_text(Tr.t(entry["name_key"]), x + 18.0, name_y, 14, COL_ACCENT)
|
||||
|
||||
# Preview box (left sub-area)
|
||||
var pbox_sz := 180.0
|
||||
var pbox_x := x + 18.0
|
||||
var pbox_y := name_y + 28.0
|
||||
_draw_preview_box(pbox_x, pbox_y, pbox_sz, pbox_sz, entry["kind"])
|
||||
|
||||
# Props (right of preview)
|
||||
var prop_x := pbox_x + pbox_sz + 24.0
|
||||
var prop_y := pbox_y
|
||||
_draw_text(Tr.t("atlas_props"), prop_x, prop_y, 9,
|
||||
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.65))
|
||||
prop_y += 16.0
|
||||
draw_line(Vector2(prop_x, prop_y), Vector2(x + w - 24.0, prop_y),
|
||||
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.25), 1.0)
|
||||
prop_y += 8.0
|
||||
var props: Array = entry["props"]
|
||||
for pair in props:
|
||||
var lbl: String = Tr.t(pair[0])
|
||||
var val_raw: String = pair[1]
|
||||
var val := val_raw
|
||||
if val_raw.begins_with("atlas_hazard_"):
|
||||
val = Tr.t(val_raw)
|
||||
_draw_text(lbl, prop_x, prop_y, 10, Color(COL_WHITE.r, COL_WHITE.g, COL_WHITE.b, 0.55))
|
||||
_draw_text(val, prop_x + 100.0, prop_y, 10, COL_WHITE)
|
||||
prop_y += 20.0
|
||||
|
||||
# Description (full width below)
|
||||
var desc_y := pbox_y + pbox_sz + 22.0
|
||||
_draw_text(Tr.t("atlas_desc"), x + 18.0, desc_y, 9,
|
||||
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.65))
|
||||
desc_y += 16.0
|
||||
draw_line(Vector2(x + 18.0, desc_y), Vector2(x + w - 18.0, desc_y),
|
||||
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.25), 1.0)
|
||||
desc_y += 8.0
|
||||
var text: String = Tr.t(entry["desc_key"])
|
||||
_draw_wrapped(text, x + 18.0, desc_y, w - 36.0, 11,
|
||||
Color(COL_WHITE.r, COL_WHITE.g, COL_WHITE.b, 0.85))
|
||||
|
||||
|
||||
# ─── Preview dispatcher ───────────────────────────────────────────────────────
|
||||
|
||||
func _draw_preview_box(px: float, py: float, pw: float, ph: float, kind: String) -> void:
|
||||
# Dark frame
|
||||
draw_rect(Rect2(px, py, pw, ph), Color(0.02, 0.02, 0.06, 0.85))
|
||||
draw_rect(Rect2(px, py, pw, ph), Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.25), false, 1.0)
|
||||
var cx := px + pw * 0.5
|
||||
var cy := py + ph * 0.5
|
||||
|
||||
match kind:
|
||||
"antimatter":
|
||||
# Multiple real Antimatter objects
|
||||
for am in _preview_list:
|
||||
am.draw(self)
|
||||
|
||||
"player":
|
||||
if _preview_obj:
|
||||
_preview_obj.x = cx
|
||||
_preview_obj.y = cy
|
||||
_preview_obj.draw(self, 0) # frame=0 → nie blinken
|
||||
|
||||
"comet":
|
||||
# Comet trail needs manual positioning within the box
|
||||
_prev_comet(px, py, pw, ph)
|
||||
|
||||
"bullet":
|
||||
_prev_bullet(cx, cy)
|
||||
|
||||
"bigwipe":
|
||||
_prev_bigwipe(px, py, pw, ph)
|
||||
|
||||
_:
|
||||
# Alle anderen: echtes Spielobjekt an Preview-Mitte positionieren und zeichnen
|
||||
if _preview_obj:
|
||||
_preview_obj.x = cx
|
||||
_preview_obj.y = cy
|
||||
_preview_obj.draw(self)
|
||||
|
||||
|
||||
# ─── Custom previews (keine Entsprechung als einzelnes Spielobjekt) ───────────
|
||||
|
||||
func _prev_comet(px: float, py: float, pw: float, ph: float) -> void:
|
||||
var tip_x := px + 30.0 + fmod(_time * 80.0, pw - 60.0)
|
||||
var tip_y := py + 30.0 + fmod(_time * 40.0, ph - 60.0)
|
||||
var tail := 26
|
||||
for i: int in tail:
|
||||
var t := float(i) / float(tail)
|
||||
var tx := tip_x - cos(0.5) * float(i) * 3.0
|
||||
var ty := tip_y - sin(0.5) * float(i) * 3.0
|
||||
var a := (1.0 - t)
|
||||
draw_rect(Rect2(tx, ty, 2, 2), Color(0.9, 0.95, 1.0, a * 0.85))
|
||||
draw_rect(Rect2(tip_x - 2, tip_y - 2, 4, 4), Color(1, 1, 1, 1.0))
|
||||
|
||||
|
||||
func _prev_bullet(cx: float, cy: float) -> void:
|
||||
# Player bullet
|
||||
draw_rect(Rect2(cx - 24, cy - 12, 4, 4), Color(0.7, 0.9, 1.0, 1.0))
|
||||
draw_rect(Rect2(cx - 26, cy - 13, 6, 2), Color(0.7, 0.9, 1.0, 0.4))
|
||||
# Pierce (white)
|
||||
draw_rect(Rect2(cx + 2, cy - 12, 4, 4), Color(1, 1, 1, 1))
|
||||
draw_rect(Rect2(cx - 8, cy - 8, 6, 2), Color(1, 1, 1, 0.3))
|
||||
# Enemy bullet
|
||||
draw_rect(Rect2(cx - 24, cy + 10, 4, 4), Color(1.0, 0.4, 0.4, 1.0))
|
||||
# Boss bullet
|
||||
draw_rect(Rect2(cx + 2, cy + 10, 4, 4), Color(1.0, 0.6, 0.2, 1.0))
|
||||
_draw_text("player", cx - 52, cy - 18, 8, Color(0.7, 0.9, 1.0, 0.7))
|
||||
_draw_text("pierce", cx + 12, cy - 18, 8, Color(1, 1, 1, 0.7))
|
||||
_draw_text("enemy", cx - 52, cy + 4, 8, Color(1, 0.4, 0.4, 0.7))
|
||||
_draw_text("boss", cx + 12, cy + 4, 8, Color(1, 0.6, 0.2, 0.7))
|
||||
|
||||
|
||||
func _prev_bigwipe(px: float, py: float, pw: float, ph: float) -> void:
|
||||
var flash := 0.5 + 0.5 * sin(_time * 6.0)
|
||||
draw_rect(Rect2(px + 4, py + 4, pw - 8, ph - 8), Color(0.0, 0.0, 0.05, 0.75))
|
||||
for i: int in 18:
|
||||
var ly := py + 6.0 + float(i) * 10.0
|
||||
if ly > py + ph - 6: continue
|
||||
draw_line(Vector2(px + 4, ly), Vector2(px + pw - 4, ly),
|
||||
Color(1, 1, 1, 0.08 + 0.12 * flash), 1.0)
|
||||
var cx := px + pw * 0.5; var cy := py + ph * 0.5
|
||||
draw_circle(Vector2(cx, cy), 22.0 + flash * 8.0, Color(1, 1, 1, 0.25 * flash))
|
||||
_draw_text_c("[ N ]", cx, cy + 48.0, 14, Color(1, 1, 1, 0.7 + 0.3 * flash))
|
||||
_draw_text_c("HOLD", cx, cy + 66.0, 8, Color(1, 1, 1, 0.6))
|
||||
|
||||
|
||||
# ─── Drawing helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
func _draw_terminal_box(bx: float, by: float, bw: float, bh: float) -> void:
|
||||
draw_rect(Rect2(bx, by, bw, bh), Color(0.0, 0.04, 0.02, 0.95))
|
||||
var bc := Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.35)
|
||||
draw_line(Vector2(bx, by), Vector2(bx+bw, by), bc, 1.0)
|
||||
draw_line(Vector2(bx, by+bh), Vector2(bx+bw, by+bh), bc, 1.0)
|
||||
draw_line(Vector2(bx, by), Vector2(bx, by+bh), bc, 1.0)
|
||||
draw_line(Vector2(bx+bw, by), Vector2(bx+bw, by+bh), bc, 1.0)
|
||||
var cl := 12.0
|
||||
for cx: float in [bx, bx+bw]:
|
||||
for cy: float in [by, by+bh]:
|
||||
var sx := 1.0 if cx == bx else -1.0
|
||||
var sy := 1.0 if cy == by else -1.0
|
||||
draw_line(Vector2(cx, cy), Vector2(cx + sx*cl, cy), COL_PRIMARY, 1.5)
|
||||
draw_line(Vector2(cx, cy), Vector2(cx, cy + sy*cl), COL_PRIMARY, 1.5)
|
||||
|
||||
|
||||
func _draw_brackets(x1: float, y1: float, x2: float, y2: float, col: Color, arm: float) -> void:
|
||||
var bw := 1.5
|
||||
draw_line(Vector2(x1, y1), Vector2(x1+arm, y1), col, bw)
|
||||
draw_line(Vector2(x1, y1), Vector2(x1, y1+arm), col, bw)
|
||||
draw_line(Vector2(x2, y1), Vector2(x2-arm, y1), col, bw)
|
||||
draw_line(Vector2(x2, y1), Vector2(x2, y1+arm), col, bw)
|
||||
draw_line(Vector2(x1, y2), Vector2(x1+arm, y2), col, bw)
|
||||
draw_line(Vector2(x1, y2), Vector2(x1, y2-arm), col, bw)
|
||||
draw_line(Vector2(x2, y2), Vector2(x2-arm, y2), col, bw)
|
||||
draw_line(Vector2(x2, y2), Vector2(x2, y2-arm), col, bw)
|
||||
|
||||
|
||||
func _draw_text(text: String, x: float, y: float, sz: int, col: Color) -> void:
|
||||
draw_string(ThemeDB.fallback_font, Vector2(x, y + sz),
|
||||
text, HORIZONTAL_ALIGNMENT_LEFT, -1, sz, col)
|
||||
|
||||
|
||||
func _draw_text_c(text: String, x: float, y: float, sz: int, col: Color) -> void:
|
||||
var tw := _text_w(text, sz)
|
||||
draw_string(ThemeDB.fallback_font, Vector2(x - tw * 0.5, y + sz),
|
||||
text, HORIZONTAL_ALIGNMENT_LEFT, -1, sz, col)
|
||||
|
||||
|
||||
func _text_w(text: String, sz: int) -> float:
|
||||
return ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, sz).x
|
||||
|
||||
|
||||
func _draw_wrapped(text: String, x: float, y: float, w: float, sz: int, col: Color) -> void:
|
||||
var words := text.split(" ", false)
|
||||
var line := ""
|
||||
var row_y := y
|
||||
for word: String in words:
|
||||
var trial := (line + " " + word) if line != "" else word
|
||||
if _text_w(trial, sz) > w and line != "":
|
||||
_draw_text(line, x, row_y, sz, col)
|
||||
row_y += float(sz) + 4.0
|
||||
line = word
|
||||
else:
|
||||
line = trial
|
||||
if line != "":
|
||||
_draw_text(line, x, row_y, sz, col)
|
||||
Reference in New Issue
Block a user