Files
spacel/scripts/planet.gd
T
alpacaman edc40f9008 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>
2026-04-21 14:38:09 +02:00

417 lines
14 KiB
GDScript

extends RefCounted
# Accessed as CosmicObjects.Planet via preload in cosmic_objects.gd.
# No class_name to avoid outer-scope shadowing inside CosmicObjects' inner classes.
enum PType { TERRESTRIAL, DESERT, GAS_GIANT, ICE, LAVA, TOXIC }
# Orbit / position
var orbit_cx: float; var orbit_cy: float
var orbit_radius: float; var orbit_angle: float; var orbit_speed: float
var x: float; var y: float; var radius: float
# Legacy public fields (kept for compatibility with game_world.gd / debris)
var color: Color
var ring: bool = false
var moons: Array = []
var alpha: float = 1.0
var dead: bool = false
# Capture / tidal state (unchanged)
var captured: bool = false
var capture_bh_x: float = 0.0; var capture_bh_y: float = 0.0
var capture_angle: float = 0.0; var capture_dist: float = 0.0
var capture_initial_dist: float = 0.0; var capture_timer: float = 0.0
var initial_radius: float = 0.0
var debris_trail: Array = []
const CAPTURE_DURATION := 1.2
const DEBRIS_MAX := 20
# New: type & palette
var ptype: int = PType.TERRESTRIAL
var palette: Array = [] # [base, accent, highlight]
# New: surface noise & animation
var surface_noise: FastNoiseLite
var cloud_noise: FastNoiseLite
var has_clouds: bool = false
var spin_angle: float = 0.0
var spin_speed: float = 0.25
var cloud_drift: float = 0.0
var cloud_speed: float = 0.5
# Gas giant spot
var has_spot: bool = false
var spot_longitude: float = 0.0 # 0..TAU
var spot_latitude: float = 0.0 # in -radius..+radius
var spot_radius: float = 2.5
var spot_band_offset: float = 0.0 # for band index match
# Rings (extended)
var ring_count: int = 0 # 0 = none
var ring_inner: float = 0.0
var ring_outer: float = 0.0
var ring_tilt: float = 0.25
var ring_colors: Array = []
var ring_gaps: Array = [] # indices of skipped "bands"
func init(cx: float, cy: float, _world_w: float, _world_h: float) -> void:
orbit_cx = cx; orbit_cy = cy
orbit_radius = randf_range(19.0, 72.0)
orbit_angle = randf() * TAU
orbit_speed = randf_range(0.001, 0.004) * (1.0 if randf() > 0.5 else -1.0)
var is_gas_giant := randf() < 0.35
radius = randf_range(12.0, 19.0) if is_gas_giant else randf_range(5.0, 10.0)
initial_radius = radius
# Type selection
if is_gas_giant:
ptype = PType.GAS_GIANT
else:
var r := randf()
if r < 0.35: ptype = PType.TERRESTRIAL
elif r < 0.65: ptype = PType.DESERT
elif r < 0.80: ptype = PType.ICE
elif r < 0.90: ptype = PType.LAVA
else: ptype = PType.TOXIC
_setup_palette()
_setup_noise()
_setup_animation()
_setup_rings(is_gas_giant)
_setup_moons(is_gas_giant)
color = palette[0] # debris / legacy compatibility
func _setup_palette() -> void:
match ptype:
PType.TERRESTRIAL:
palette = [Color("#2a5aa0"), Color("#3a8a4a"), Color("#e8f0ff")]
PType.DESERT:
palette = [Color("#c87848"), Color("#7a3e25"), Color("#f0d8b0")]
PType.GAS_GIANT:
# Two palette variants — warm (Jupiter-like) or cool (Neptune-like)
if randf() < 0.6:
palette = [Color("#d8b078"), Color("#a06040"), Color("#f0d8a0")]
else:
palette = [Color("#88a8d8"), Color("#506090"), Color("#c8d8f0")]
PType.ICE:
palette = [Color("#b8d8f0"), Color("#5080a0"), Color("#ffffff")]
PType.LAVA:
palette = [Color("#2a0a00"), Color("#ff4408"), Color("#ffc040")]
PType.TOXIC:
palette = [Color("#4a8030"), Color("#d0d040"), Color("#204018")]
func _setup_noise() -> void:
surface_noise = FastNoiseLite.new()
surface_noise.seed = randi()
surface_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
match ptype:
PType.TERRESTRIAL: surface_noise.frequency = 0.25
PType.DESERT: surface_noise.frequency = 0.22
PType.GAS_GIANT: surface_noise.frequency = 0.15
PType.ICE: surface_noise.frequency = 0.50
PType.LAVA: surface_noise.frequency = 0.35
PType.TOXIC: surface_noise.frequency = 0.18
func _setup_animation() -> void:
spin_angle = randf() * TAU
spin_speed = randf_range(0.15, 0.55) * (1.0 if randf() > 0.5 else -1.0)
if ptype == PType.TERRESTRIAL and randf() < 0.7:
has_clouds = true
cloud_noise = FastNoiseLite.new()
cloud_noise.seed = randi()
cloud_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
cloud_noise.frequency = 0.30
cloud_drift = randf() * 100.0
cloud_speed = randf_range(0.4, 0.9)
if ptype == PType.GAS_GIANT:
has_spot = randf() < 0.55
if has_spot:
spot_longitude = randf() * TAU
spot_latitude = randf_range(-radius * 0.4, radius * 0.4)
spot_radius = randf_range(2.0, 3.0)
func _setup_rings(is_gas_giant: bool) -> void:
var has_ring := (is_gas_giant and randf() < 0.7) or (not is_gas_giant and randf() < 0.15)
if not has_ring:
ring = false
return
ring = true
ring_count = (randi() % 2) + 1 # 1..2 visible bands
ring_inner = radius + randf_range(3.0, 5.0)
ring_outer = ring_inner + randf_range(4.0, 8.0)
ring_tilt = randf_range(0.15, 0.35)
# Slight variation in ring color per band
ring_colors.clear()
for i in ring_count:
var base: Color = palette[0] if randf() < 0.5 else palette[2]
var shade: float = randf_range(0.65, 1.0)
ring_colors.append(Color(base.r * shade, base.g * shade, base.b * shade))
# Random gap bands (visual holes); we use radii, so gaps are "dark" arcs
ring_gaps.clear()
if randf() < 0.4:
ring_gaps.append(randf_range(ring_inner + 1.0, ring_outer - 1.0))
func _setup_moons(is_gas_giant: bool) -> void:
var max_moons := 4 if is_gas_giant else 3
var moon_count := randi() % (max_moons + 1)
for _i in moon_count:
var mtype := randi() % 3
var mcolor: Color
match mtype:
0: mcolor = Color(0.75, 0.75, 0.78) # rocky grey
1: mcolor = Color(0.85, 0.92, 1.0) # icy
2: mcolor = Color(0.85, 0.35, 0.25) # volcanic
_: mcolor = Color(0.8, 0.8, 0.8)
var msize: float = randf_range(2.0, 6.0) if is_gas_giant else randf_range(2.0, 4.0)
moons.append({
"angle": randf() * TAU,
"dist": radius + randf_range(8.0, 22.0),
"speed": randf_range(0.03, 0.08),
"size": msize,
"color": mcolor,
"mtype": mtype,
})
func start_capture(bh_x: float, bh_y: float) -> void:
captured = true
capture_bh_x = bh_x; capture_bh_y = bh_y
var dx := x - bh_x; var dy := y - bh_y
capture_dist = sqrt(dx * dx + dy * dy)
capture_initial_dist = maxf(capture_dist, 1.0)
capture_angle = atan2(dy, dx)
capture_timer = 0.0
func update(delta: float) -> void:
# Animation
spin_angle += spin_speed * delta
cloud_drift += cloud_speed * delta
if captured:
capture_timer += delta
var t: float = clampf(capture_timer / CAPTURE_DURATION, 0.0, 1.0)
capture_dist = capture_initial_dist * (1.0 - t * t)
var angular_speed: float = 4.0 + 10.0 * t * t
capture_angle += angular_speed * delta
x = capture_bh_x + cos(capture_angle) * capture_dist
y = capture_bh_y + sin(capture_angle) * capture_dist
radius = maxf(1.0, initial_radius * (1.0 - t * 0.8))
if int(capture_timer * 12.5) > int((capture_timer - delta) * 12.5):
var da: float = randf() * TAU
debris_trail.append({"x": x + cos(da) * radius,
"y": y + sin(da) * radius, "life": 0.5})
if debris_trail.size() > DEBRIS_MAX: debris_trail.pop_front()
for d: Dictionary in debris_trail:
d["life"] = float(d["life"]) - delta
debris_trail = debris_trail.filter(func(d): return float(d["life"]) > 0.0)
if t > 0.6:
alpha = maxf(0.0, 1.0 - (t - 0.6) / 0.4)
if capture_timer >= CAPTURE_DURATION:
dead = true
return
orbit_angle += orbit_speed
x = orbit_cx + cos(orbit_angle) * orbit_radius
y = orbit_cy + sin(orbit_angle) * orbit_radius * 0.4
for m: Dictionary in moons:
m["angle"] = float(m["angle"]) + float(m["speed"])
# ─── Drawing ──────────────────────────────────────────────────────────────────
func draw(canvas: CanvasItem) -> void:
# Back half of rings (behind planet)
if ring:
_draw_ring_half(canvas, true)
# Lava outer halo (drawn before body, under-glow)
if ptype == PType.LAVA:
var halo_r := int(ceil(radius + 3.0))
var pulse: float = 0.55 + sin(spin_angle * 3.0) * 0.15
var hc := Color(palette[1].r, palette[1].g, palette[1].b, alpha * 0.18 * pulse)
canvas.draw_rect(Rect2(x - halo_r, y - halo_r, halo_r * 2 + 1, halo_r * 2 + 1), hc)
# Planet body
_draw_body(canvas)
# Front half of rings (in front of planet)
if ring:
_draw_ring_half(canvas, false)
# Moons
for m: Dictionary in moons:
var mx: float = x + cos(float(m["angle"])) * float(m["dist"])
var my: float = y + sin(float(m["angle"])) * float(m["dist"]) * 0.5
_draw_moon(canvas, mx, my, m)
# Debris trail (tidal disruption)
for d: Dictionary in debris_trail:
var da: float = float(d["life"]) / 0.5
canvas.draw_rect(Rect2(float(d["x"]) - 1, float(d["y"]) - 1, 2, 2),
Color(palette[0].r, palette[0].g, palette[0].b, alpha * da * 0.7))
func _draw_body(canvas: CanvasItem) -> void:
var ir := int(ceil(radius))
var r2 := radius * radius
for py in range(-ir, ir + 1):
for px in range(-ir, ir + 1):
if px * px + py * py > r2:
continue
var idx := _sample_pattern(px, py)
var c: Color = palette[idx]
# Spherical shading — light from upper-left
var nx := float(px) / radius
var ny := float(py) / radius
var light: float = clampf(0.65 - nx * 0.35 + ny * 0.35, 0.35, 1.0)
var out := Color(c.r * light, c.g * light, c.b * light, alpha)
canvas.draw_rect(Rect2(x + px, y + py, 1, 1), out)
# Cloud layer (terrestrial only)
if has_clouds and ptype == PType.TERRESTRIAL:
var cv: float = cloud_noise.get_noise_2d(
float(px) + cloud_drift + spin_angle * radius * 0.6,
float(py) * 1.2)
if cv > 0.35:
var cloud_a: float = alpha * 0.55 * clampf((cv - 0.35) / 0.25, 0.0, 1.0) * light
canvas.draw_rect(Rect2(x + px, y + py, 1, 1),
Color(1.0, 1.0, 1.0, cloud_a))
# Lava glow — small emissive bloom near glow pixels
if ptype == PType.LAVA and idx == 2:
var gc := Color(palette[2].r, palette[2].g, palette[2].b, alpha * 0.35)
canvas.draw_rect(Rect2(x + px - 1, y + py, 1, 1), gc)
canvas.draw_rect(Rect2(x + px + 1, y + py, 1, 1), gc)
canvas.draw_rect(Rect2(x + px, y + py - 1, 1, 1), gc)
canvas.draw_rect(Rect2(x + px, y + py + 1, 1, 1), gc)
func _sample_pattern(px: int, py: int) -> int:
var fpx: float = float(px)
var fpy: float = float(py)
var shifted_x: float = fpx + spin_angle * radius * 0.6
match ptype:
PType.TERRESTRIAL:
# Polar caps
if absf(fpy) / radius > 0.78:
return 2
var v: float = surface_noise.get_noise_2d(shifted_x, fpy * 1.1)
return 1 if v > 0.05 else 0
PType.DESERT:
if fpy / radius < -0.82:
return 2
var v2: float = surface_noise.get_noise_2d(shifted_x, fpy * 1.1)
return 1 if v2 > 0.15 else 0
PType.GAS_GIANT:
# Distorted banding; spot overrides band
if has_spot:
# Spot position rotates with spin
var spot_x: float = cos(spot_longitude + spin_angle) * radius * 0.6
# Only visible on front half (cos > 0)
var front: float = cos(spot_longitude + spin_angle)
if front > 0.0:
var dx: float = fpx - spot_x
var dy: float = fpy - spot_latitude
if dx * dx + dy * dy < spot_radius * spot_radius:
return 2
var warp: float = surface_noise.get_noise_2d(shifted_x * 0.5, fpy * 4.0) * 2.0
var band: int = int(floor((fpy + warp + radius) / 2.5))
var m: int = band % 3
if m < 0: m += 3
return m
PType.ICE:
var v3: float = surface_noise.get_noise_2d(shifted_x, fpy * 1.1)
if v3 > 0.55:
return 2
if absf(v3) < 0.08:
return 1
return 0
PType.LAVA:
var shift: float = sin(spin_angle * 3.0) * 0.03
var v4: float = surface_noise.get_noise_2d(shifted_x, fpy * 1.1)
if v4 > 0.45 + shift: return 2
if v4 > 0.15 + shift: return 1
return 0
PType.TOXIC:
var v5: float = surface_noise.get_noise_2d(shifted_x, fpy * 1.1)
var v6: float = surface_noise.get_noise_2d(shifted_x + v5 * 3.0, fpy * 1.1 + v5 * 3.0)
if v6 > 0.3: return 2
if v6 > 0.0: return 1
return 0
return 0
func _draw_ring_half(canvas: CanvasItem, back: bool) -> void:
# Build band radii (each integer radius = one pixel band). Split by ring_count groups.
var band_width: float = (ring_outer - ring_inner) / float(maxi(ring_count, 1) * 2 + 1)
var a_start: float = PI
var a_end: float = TAU
if not back:
a_start = 0.0
a_end = PI
# For each band
for i in ring_count:
var inner: float = ring_inner + float(i * 2) * band_width
var outer: float = inner + band_width
var col: Color = ring_colors[i]
var rc := Color(col.r, col.g, col.b, alpha * 0.55)
# Sample arc points
var steps: int = int(clampf(outer * 1.6, 20.0, 64.0))
var prev: Vector2 = Vector2.ZERO
var have_prev: bool = false
for s in range(steps + 1):
var a: float = a_start + (a_end - a_start) * float(s) / float(steps)
# For each radius in [inner, outer]
var rad: float = (inner + outer) * 0.5
if _ring_gap_hit(rad):
have_prev = false
continue
var px: float = x + cos(a) * rad
var py: float = y + sin(a) * rad * ring_tilt
if have_prev:
canvas.draw_line(prev, Vector2(px, py), rc, maxf(1.0, band_width))
prev = Vector2(px, py)
have_prev = true
func _ring_gap_hit(rad: float) -> bool:
for g in ring_gaps:
if absf(rad - float(g)) < 0.6:
return true
return false
func _draw_moon(canvas: CanvasItem, mx: float, my: float, m: Dictionary) -> void:
var mc: Color = m["color"]
var ms: float = float(m["size"])
var a_out := Color(mc.r, mc.g, mc.b, alpha)
# Solid body
canvas.draw_rect(Rect2(mx - ms * 0.5, my - ms * 0.5, ms, ms), a_out)
# Type-specific detail
var mtype: int = int(m["mtype"])
match mtype:
0:
# Rocky — darker crater pixel
var dark := Color(mc.r * 0.55, mc.g * 0.55, mc.b * 0.55, alpha)
canvas.draw_rect(Rect2(mx - ms * 0.5 + 1, my - ms * 0.5, 1, 1), dark)
if ms >= 4.0:
canvas.draw_rect(Rect2(mx + ms * 0.5 - 2, my + ms * 0.5 - 2, 1, 1), dark)
1:
# Icy — bright highlight
var hi := Color(1.0, 1.0, 1.0, alpha)
canvas.draw_rect(Rect2(mx - ms * 0.5, my - ms * 0.5, 1, 1), hi)
2:
# Volcanic — red/black speckle
var hot := Color(1.0, 0.6, 0.1, alpha)
var black := Color(0.1, 0.05, 0.05, alpha)
canvas.draw_rect(Rect2(mx - ms * 0.5 + 1, my - ms * 0.5 + 1, 1, 1), hot)
canvas.draw_rect(Rect2(mx + ms * 0.5 - 1, my - ms * 0.5, 1, 1), black)