edc40f9008
- 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>
194 lines
6.0 KiB
GDScript
194 lines
6.0 KiB
GDScript
extends RefCounted
|
|
class_name BossShip
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
# BossShip — Mini-Boss "WRAITH" (Welle 5) & Boss "LEVIATHAN" (Welle 8)
|
|
# Orbitet elliptisch um die Bildschirmmitte, feuert Spread-Geschosse.
|
|
# Wird von game_world.gd verwaltet (update/draw/take_hit).
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
var x: float = 0.0
|
|
var y: float = 0.0
|
|
var heading: float = 0.0
|
|
var orbit_angle: float = 0.0
|
|
var hp: int = 20
|
|
var max_hp: int = 20
|
|
var dead: bool = false
|
|
var fire_timer: int = 0
|
|
var is_miniboss: bool = true # false = Boss
|
|
var phase: int = 1 # 1 oder 2 (nur Boss)
|
|
var _phase2_triggered: bool = false
|
|
var _just_entered_phase2: bool = false
|
|
var pixel_size: int = 5 # Miniboss=5, Boss=7
|
|
|
|
const HULL_PIXELS: Array = [
|
|
[6, 0, "nose"],
|
|
[4, -2, "mid"], [4, 2, "mid"],
|
|
[2, 0, "dim"],
|
|
[0, -4, "accent"], [0, 4, "accent"],
|
|
[0, 0, "bright"],
|
|
[-2, -2, "dim"], [-2, 2, "dim"],
|
|
[-4, -4, "shadow"], [-4, 4, "shadow"],
|
|
[-4, 0, "edge"],
|
|
]
|
|
|
|
# Mini-Boss WRAITH: Magenta/Lila
|
|
const PAL_MINI: Dictionary = {
|
|
"nose": Color(1.0, 0.15, 0.9, 1.0),
|
|
"bright": Color(1.0, 0.5, 1.0, 1.0),
|
|
"mid": Color(0.75, 0.05, 0.7, 1.0),
|
|
"dim": Color(0.45, 0.02, 0.38, 1.0),
|
|
"accent": Color(1.0, 0.0, 0.85, 1.0),
|
|
"edge": Color(0.28, 0.0, 0.28, 1.0),
|
|
"shadow": Color(0.12, 0.0, 0.12, 1.0),
|
|
}
|
|
|
|
# Boss LEVIATHAN: Feuer-Orange
|
|
const PAL_BOSS: Dictionary = {
|
|
"nose": Color(1.0, 0.65, 0.0, 1.0),
|
|
"bright": Color(1.0, 0.9, 0.25, 1.0),
|
|
"mid": Color(0.8, 0.38, 0.0, 1.0),
|
|
"dim": Color(0.5, 0.18, 0.0, 1.0),
|
|
"accent": Color(1.0, 0.72, 0.0, 1.0),
|
|
"edge": Color(0.28, 0.08, 0.0, 1.0),
|
|
"shadow": Color(0.12, 0.03, 0.0, 1.0),
|
|
}
|
|
|
|
# ── Initialisierung ────────────────────────────────────────────────────────
|
|
|
|
func init_miniboss(cx: float, cy: float) -> void:
|
|
x = cx
|
|
y = cy - 120.0
|
|
orbit_angle = -PI * 0.5 # startet oben
|
|
hp = 20; max_hp = 20
|
|
is_miniboss = true
|
|
pixel_size = 5
|
|
fire_timer = 25 # kurze Verzögerung vor dem ersten Schuss
|
|
|
|
func init_boss(cx: float, cy: float) -> void:
|
|
x = cx
|
|
y = cy - 140.0
|
|
orbit_angle = -PI * 0.5
|
|
hp = 50; max_hp = 50
|
|
is_miniboss = false
|
|
phase = 1
|
|
pixel_size = 7
|
|
fire_timer = 25
|
|
|
|
# ── Update ─────────────────────────────────────────────────────────────────
|
|
# Gibt Array[Bullet] zurück. Wird von game_world._tick() pro Frame aufgerufen.
|
|
|
|
func update(delta: float, cx: float, cy: float) -> Array:
|
|
if dead:
|
|
return []
|
|
|
|
# Orbitalbewegung (elliptisch, um Spannung zu erzeugen)
|
|
var orbit_speed: float
|
|
if is_miniboss:
|
|
orbit_speed = 0.55
|
|
elif phase == 1:
|
|
orbit_speed = 0.44
|
|
else:
|
|
orbit_speed = 0.72 # Phase 2 schneller → gefährlicher
|
|
|
|
orbit_angle += orbit_speed * delta
|
|
var orbit_r: float = 185.0 if is_miniboss else 215.0
|
|
x = cx + cos(orbit_angle) * orbit_r
|
|
y = cy + sin(orbit_angle) * orbit_r * 0.58 # leicht elliptisch
|
|
|
|
# Heading zeigt immer zur Bildschirmmitte (schießt nach innen)
|
|
heading = atan2(cy - y, cx - x)
|
|
|
|
# Feuer-Intervall
|
|
fire_timer += 1
|
|
var fire_interval: int
|
|
if is_miniboss:
|
|
fire_interval = 72
|
|
elif phase == 1:
|
|
fire_interval = 60
|
|
else:
|
|
fire_interval = 42
|
|
|
|
var result: Array = []
|
|
if fire_timer >= fire_interval:
|
|
fire_timer = 0
|
|
result = _fire()
|
|
|
|
# Phase-2-Übergang (Boss, < 50% HP)
|
|
if not is_miniboss and not _phase2_triggered and hp <= max_hp / 2:
|
|
phase = 2
|
|
_phase2_triggered = true
|
|
_just_entered_phase2 = true
|
|
|
|
return result
|
|
|
|
func _fire() -> Array:
|
|
var ways: int
|
|
if is_miniboss:
|
|
ways = 3
|
|
elif phase == 1:
|
|
ways = 5
|
|
else:
|
|
ways = 8
|
|
|
|
var bullet_speed: float = 4.2 if is_miniboss else 5.0
|
|
var spread: float = PI / float(ways + 2)
|
|
var bullets: Array = []
|
|
|
|
for i in ways:
|
|
var angle: float = heading + (float(i) - float(ways - 1) * 0.5) * spread
|
|
var b := Bullet.new()
|
|
b.init(x, y, angle, "boss")
|
|
# Geschwindigkeit überschreiben (langsamer als Spielerkugeln für Ausweichen)
|
|
b.vx = cos(angle) * bullet_speed
|
|
b.vy = sin(angle) * bullet_speed
|
|
if is_miniboss:
|
|
b.color = Color(1.0, 0.15, 0.9, 1.0) # Magenta
|
|
elif phase == 1:
|
|
b.color = Color(1.0, 0.5, 0.0, 1.0) # Orange
|
|
else:
|
|
b.color = Color(1.0, 0.1, 0.0, 1.0) # Dunkelrot
|
|
bullets.append(b)
|
|
|
|
return bullets
|
|
|
|
func take_hit() -> void:
|
|
hp -= 1
|
|
if hp <= 0:
|
|
hp = 0
|
|
dead = true
|
|
|
|
# ── Zeichnen ───────────────────────────────────────────────────────────────
|
|
|
|
func draw(canvas: CanvasItem) -> void:
|
|
if dead:
|
|
return
|
|
|
|
var pal: Dictionary = PAL_MINI if is_miniboss else PAL_BOSS
|
|
var ps: float = float(pixel_size)
|
|
var hs: float = ps * 0.5
|
|
var ch: float = cos(heading)
|
|
var sh: float = sin(heading)
|
|
|
|
# Rumpf — gleiche Offset-Tabelle wie EnemyShip, skaliert mit pixel_size
|
|
for pix: Array in HULL_PIXELS:
|
|
var lx: float = float(pix[0]) * ps
|
|
var ly: float = float(pix[1]) * ps
|
|
var wx: float = x + lx * ch - ly * sh
|
|
var wy: float = y + lx * sh + ly * ch
|
|
canvas.draw_rect(Rect2(wx - hs, wy - hs, ps, ps), pal[pix[2]])
|
|
|
|
# Thruster-Glühen (hinter dem Schiff)
|
|
var glow_x: float = x + cos(heading + PI) * (ps * 4.2)
|
|
var glow_y: float = y + sin(heading + PI) * (ps * 4.2)
|
|
var glow_r: float = ps + 2.0
|
|
var pulse: float = 0.48 + 0.38 * sin(float(Time.get_ticks_msec()) * 0.012)
|
|
var glow_col: Color
|
|
if is_miniboss:
|
|
glow_col = Color(1.0, 0.0, 0.85, pulse)
|
|
elif phase == 1:
|
|
glow_col = Color(1.0, 0.5, 0.0, pulse)
|
|
else:
|
|
glow_col = Color(1.0, 0.15, 0.0, pulse + 0.15) # Phase 2 heller
|
|
canvas.draw_circle(Vector2(glow_x, glow_y), glow_r, glow_col)
|