Add black hole planet and dark matter resource (#55)
A rare, deterministically-seeded landable body yielding a fourth resource, dark matter, obtainable only through an extractor - never a surface pickup. Reuses every existing generic system (resource/planet definitions, surface profiles, extractor/transporter pipeline) with no type-switch branching. Also fixes a latent conquest-gating gap: a surface profile with no authored PlanetSetup (like the black hole's "void") never registered capture points at all, permanently blocking extractor placement. register_capture_points() now treats zero required points as vacuously conquered, and PlanetSurfaceController registers that zero-point state explicitly when no setup exists.
This commit is contained in:
parent
097e17f7aa
commit
cbe07bbb55
20 changed files with 421 additions and 2 deletions
14
BlackHolePlanetDefinition.tres
Normal file
14
BlackHolePlanetDefinition.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="PlanetDefinition" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://PlanetDefinition.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://BlackHoleVisual.tscn" id="2"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
definition_id = "black_hole"
|
||||
display_name = "Black hole"
|
||||
names = PackedStringArray("Event Zero", "Umbral Well", "The Sinkhole", "Lightless Reach", "Null Point")
|
||||
visual_scene = ExtResource("2")
|
||||
surface_profile_id = "void"
|
||||
resource_id = "dark_matter"
|
||||
map_color = Color(0.55, 0.15, 0.85, 1)
|
||||
49
BlackHoleVisual.gd
Normal file
49
BlackHoleVisual.gd
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
extends Node2D
|
||||
|
||||
# Same informal interface as GasGiantVisual/RockyPlanetVisual - configure(),
|
||||
# extractor_anchor(), animates via its own _process(). No solid body: the
|
||||
# core stays genuinely empty (nothing drawn inside CORE_RADIUS), and an
|
||||
# accretion disc of rotating arc segments - not closed rings - stands in
|
||||
# for a surface, brighter and thicker toward the centre.
|
||||
|
||||
var disc_phase: float = 0.0
|
||||
|
||||
const CORE_RADIUS: float = 46.0
|
||||
const RING_COUNT: int = 5
|
||||
const RING_STEP: float = 16.0
|
||||
const DISC_COLOR: Color = Color(0.55, 0.15, 0.85)
|
||||
const DISC_HOT_COLOR: Color = Color(0.9, 0.6, 1.0)
|
||||
|
||||
|
||||
func configure(_shape_seed: int) -> void:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func extractor_anchor() -> Vector2:
|
||||
return Vector2(0.0, -(CORE_RADIUS + float(RING_COUNT - 1) * RING_STEP + 12.0))
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
disc_phase += delta
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
for i in range(RING_COUNT):
|
||||
var radius: float = CORE_RADIUS + float(i) * RING_STEP
|
||||
# 1.0 at the innermost ring, fading to 0.0 at the outermost - drives
|
||||
# both colour (hotter inward) and stroke weight/brightness.
|
||||
var t: float = 1.0 - float(i) / float(maxi(RING_COUNT - 1, 1))
|
||||
var ring_color: Color = DISC_COLOR.lerp(DISC_HOT_COLOR, t * 0.65)
|
||||
# Alternating spin direction per ring and a speed tied to t reads as
|
||||
# genuine turbulence rather than one disc rotating as a rigid whole.
|
||||
var spin: float = disc_phase * (0.12 + t * 0.22) * (1.0 if i % 2 == 0 else -1.0)
|
||||
for arc_i in range(2):
|
||||
var start: float = spin + float(arc_i) * PI + 0.15
|
||||
var end: float = start + PI - 0.3
|
||||
var points := PackedVector2Array()
|
||||
var steps: int = 24
|
||||
for j in range(steps + 1):
|
||||
var angle: float = lerpf(start, end, float(j) / float(steps))
|
||||
points.append(Vector2(cos(angle), sin(angle)) * radius)
|
||||
NeonDraw.draw_glowing_path(self, points, ring_color, 0.5 + t * 0.5, 0.45 + t * 0.35)
|
||||
1
BlackHoleVisual.gd.uid
Normal file
1
BlackHoleVisual.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c6448m0c5bses
|
||||
6
BlackHoleVisual.tscn
Normal file
6
BlackHoleVisual.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://BlackHoleVisual.gd" id="1"]
|
||||
|
||||
[node name="Visual" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
11
DarkMatterResourceDefinition.tres
Normal file
11
DarkMatterResourceDefinition.tres
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[gd_resource type="Resource" script_class="ResourceDefinition" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ResourceDefinition.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://DarkMatterResourceGlyph.tscn" id="2"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
resource_id = "dark_matter"
|
||||
display_name = "Dark Matter"
|
||||
color = Color(0.55, 0.15, 0.85, 1)
|
||||
glyph_scene = ExtResource("2")
|
||||
26
DarkMatterResourceGlyph.gd
Normal file
26
DarkMatterResourceGlyph.gd
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
extends Control
|
||||
|
||||
@export var colour: Color = Color(0.55, 0.15, 0.85)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var centre: Vector2 = size * 0.5
|
||||
var radius: float = minf(size.x, size.y) * 0.34
|
||||
var points := PackedVector2Array()
|
||||
for i in range(8):
|
||||
var angle: float = TAU * float(i) / 8.0 - PI * 0.5
|
||||
points.append(centre + Vector2(cos(angle), sin(angle)) * radius)
|
||||
# Deep violet glow around a near-black core - not colour.darkened() like
|
||||
# every other glyph's core, deliberately: dark matter has to read as an
|
||||
# absence even against deep space, where a merely-darker version of the
|
||||
# glow colour would still show up as a faint fill instead of a void
|
||||
# with a rim.
|
||||
NeonDraw.draw_glowing_polygon(self, points, colour, 1.0, Color(0.04, 0.01, 0.06), maxf(0.5, radius * 0.035))
|
||||
# A tighter inner ring hints at an event horizon rather than a solid
|
||||
# body - the same outline-only idiom every glyph already uses, just
|
||||
# nested once more.
|
||||
var inner := PackedVector2Array()
|
||||
for i in range(8):
|
||||
var angle: float = TAU * float(i) / 8.0 - PI * 0.5
|
||||
inner.append(centre + Vector2(cos(angle), sin(angle)) * radius * 0.5)
|
||||
NeonDraw.draw_glowing_polygon(self, inner, colour.lightened(0.25), 0.75, Color(0.04, 0.01, 0.06), maxf(0.35, radius * 0.02))
|
||||
1
DarkMatterResourceGlyph.gd.uid
Normal file
1
DarkMatterResourceGlyph.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bldtveflnyu2x
|
||||
13
DarkMatterResourceGlyph.tscn
Normal file
13
DarkMatterResourceGlyph.tscn
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://DarkMatterResourceGlyph.gd" id="1"]
|
||||
|
||||
[node name="GlyphVisual" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1")
|
||||
|
|
@ -33,6 +33,7 @@ var explored_chunks: Dictionary = {}
|
|||
const RESOURCE_ORE: String = RESOURCE_DEFINITIONS.ORE_ID
|
||||
const RESOURCE_GAS: String = RESOURCE_DEFINITIONS.GAS_ID
|
||||
const RESOURCE_ENERGY: String = RESOURCE_DEFINITIONS.ENERGY_ID
|
||||
const RESOURCE_DARK_MATTER: String = RESOURCE_DEFINITIONS.DARK_MATTER_ID
|
||||
const RESOURCE_TYPES: PackedStringArray = RESOURCE_DEFINITIONS.ORDER
|
||||
|
||||
# All resource-bearing locations use the same container. The old public
|
||||
|
|
@ -1115,7 +1116,11 @@ func register_capture_points(planet_key: String, total: int) -> void:
|
|||
var captured: Array = []
|
||||
captured.resize(total)
|
||||
captured.fill(false)
|
||||
planet_capture[planet_key] = {"captured": captured, "conquered": false}
|
||||
# A planet demanding zero points is vacuously already held (#55: a
|
||||
# black hole's "void" profile has no authored PlanetSetup at all, so
|
||||
# this is the only registration it ever gets) - every planet WITH real
|
||||
# points still starts unconquered, exactly as before.
|
||||
planet_capture[planet_key] = {"captured": captured, "conquered": total <= 0}
|
||||
|
||||
|
||||
func capture_state(planet_key: String) -> Dictionary:
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ extends RefCounted
|
|||
|
||||
const ROCKY_ID: String = "rocky"
|
||||
const GAS_GIANT_ID: String = "gas_giant"
|
||||
const BLACK_HOLE_ID: String = "black_hole"
|
||||
const DEFAULT_DEFINITION_ID: String = ROCKY_ID
|
||||
const DEFINITIONS: Dictionary = {
|
||||
ROCKY_ID: preload("res://RockyPlanetDefinition.tres"),
|
||||
GAS_GIANT_ID: preload("res://GasGiantDefinition.tres"),
|
||||
BLACK_HOLE_ID: preload("res://BlackHolePlanetDefinition.tres"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ func _compose_surface(profile: Resource) -> void:
|
|||
var setup_host: Node = SETUP_HOST_SCENE.instantiate()
|
||||
feature_host.add_child(setup_host)
|
||||
setup_host.apply_setup(setup)
|
||||
else:
|
||||
# No PlanetSetup exists for this profile at all (#55: "void" has
|
||||
# none authored) - register zero required points directly, rather
|
||||
# than leaving this planet's conquest state unregistered forever.
|
||||
# capture_state()'s fallback for an unregistered planet_id defaults
|
||||
# to conquered=false (see test_planet_conquest_gate.gd's own "a
|
||||
# fresh planet reported conquered" check), which would otherwise
|
||||
# permanently block extractor placement here - register_capture_
|
||||
# points() treats a total of 0 as vacuously already conquered.
|
||||
GameState.register_capture_points(GameState.planet_id, 0)
|
||||
|
||||
|
||||
func _on_exit_pressed() -> void:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const DEFAULT_PROFILE_ID: String = "rocky"
|
|||
const PROFILES: Dictionary = {
|
||||
"rocky": preload("res://RockySurfaceProfile.tres"),
|
||||
"gas": preload("res://GasSurfaceProfile.tres"),
|
||||
"void": preload("res://VoidSurfaceProfile.tres"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ extends RefCounted
|
|||
const ORE_ID: String = "ore"
|
||||
const GAS_ID: String = "gas"
|
||||
const ENERGY_ID: String = "energy"
|
||||
const ORDER: PackedStringArray = [ORE_ID, GAS_ID, ENERGY_ID]
|
||||
const DARK_MATTER_ID: String = "dark_matter"
|
||||
const ORDER: PackedStringArray = [ORE_ID, GAS_ID, ENERGY_ID, DARK_MATTER_ID]
|
||||
const DEFINITIONS: Dictionary = {
|
||||
ORE_ID: preload("res://OreResourceDefinition.tres"),
|
||||
GAS_ID: preload("res://GasResourceDefinition.tres"),
|
||||
ENERGY_ID: preload("res://EnergyResourceDefinition.tres"),
|
||||
DARK_MATTER_ID: preload("res://DarkMatterResourceDefinition.tres"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
12
VoidSurfaceProfile.tres
Normal file
12
VoidSurfaceProfile.tres
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[gd_resource type="Resource" script_class="PlanetSurfaceProfile" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://PlanetSurfaceProfile.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
profile_id = "void"
|
||||
base_color = Color(0.04, 0.02, 0.07, 1)
|
||||
crater_color = Color(0.01, 0.005, 0.02, 1)
|
||||
highlight_color = Color(0.45, 0.15, 0.65, 1)
|
||||
caption_color = Color(0.85, 0.7, 0.95, 1)
|
||||
caption_outline_color = Color(0.03, 0.01, 0.05, 0.9)
|
||||
16
WorldGen.gd
16
WorldGen.gd
|
|
@ -16,6 +16,12 @@ const NOISE_SEED: int = 1337
|
|||
const ASTEROID_FIELD_THRESHOLD: float = 0.15
|
||||
const PLANET_THRESHOLD: float = 0.33
|
||||
const GAS_GIANT_THRESHOLD: float = 0.55
|
||||
# A flat per-planet-chunk probability rather than another noise field: gas
|
||||
# giants already cover every SPACE-biome planet chunk (see GAS_GIANT_THRESHOLD
|
||||
# above), so making black holes noticeably rarer just needs a small, uniform
|
||||
# chance independent of biome - not a second smooth region to tune against
|
||||
# the first.
|
||||
const BLACK_HOLE_CHANCE: float = 0.05
|
||||
|
||||
# A predictable early gas source for play-testing. The origin chunk is
|
||||
# deterministically normal space. Its explicit in-chunk position puts it
|
||||
|
|
@ -84,10 +90,20 @@ static func planet_data_at_chunk(chunk: Vector2i) -> Dictionary:
|
|||
var local_pos: Vector2 = NEARBY_GAS_GIANT_LOCAL_POS if guaranteed_nearby_giant \
|
||||
else Vector2(rng.randf() * CHUNK_SIZE, rng.randf() * CHUNK_SIZE)
|
||||
var shape_seed: int = rng.randi()
|
||||
# Drawn unconditionally, in the same fixed position every call,
|
||||
# regardless of biome or the gas-giant roll below - so adding this
|
||||
# roll doesn't shift which random values any LATER draw (planet_name,
|
||||
# below) consumes for chunks that would have existed before it.
|
||||
var black_hole_roll: float = rng.randf()
|
||||
# A stable per-chunk roll makes gas giants persistent landmarks rather
|
||||
# than a different resource whenever the scene reloads.
|
||||
var gas_giant: bool = guaranteed_nearby_giant or biome_at_chunk(chunk) == Biome.SPACE
|
||||
var definition_id: String = PLANET_DEFINITIONS.GAS_GIANT_ID if gas_giant else PLANET_DEFINITIONS.ROCKY_ID
|
||||
# A rare third outcome, seeded the same deterministic way as everything
|
||||
# else here. Never overrides the guaranteed early gas giant landmark -
|
||||
# that chunk's whole purpose is to always be the same reachable source.
|
||||
if not guaranteed_nearby_giant and black_hole_roll < BLACK_HOLE_CHANCE:
|
||||
definition_id = PLANET_DEFINITIONS.BLACK_HOLE_ID
|
||||
var definition: Resource = PLANET_DEFINITIONS.get_definition(definition_id)
|
||||
var names: PackedStringArray = definition.names
|
||||
var planet_name: String = names[rng.randi() % names.size()]
|
||||
|
|
|
|||
23
tests/_black_hole_space_preview.gd
Normal file
23
tests/_black_hole_space_preview.gd
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
extends Node2D
|
||||
|
||||
# Preview, not a test - a black hole planet as seen in space, next to a
|
||||
# rocky planet for scale/contrast.
|
||||
#
|
||||
# bash tests/screenshot.sh res://tests/_black_hole_space_preview.tscn /tmp/black_hole.png 15 720x1280
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.extractors.clear()
|
||||
GameState.buildings.clear()
|
||||
|
||||
_spawn("Event Zero", "black_hole", Vector2(360.0, 420.0), 12345)
|
||||
_spawn("Dustworn", "rocky", Vector2(360.0, 950.0), 4242)
|
||||
|
||||
|
||||
func _spawn(planet_name: String, definition_id: String, at: Vector2, shape_seed: int) -> void:
|
||||
var planet: Node2D = load("res://Planet.tscn").instantiate()
|
||||
planet.planet_name = planet_name
|
||||
planet.definition_id = definition_id
|
||||
planet.shape_seed = shape_seed
|
||||
planet.position = at
|
||||
add_child(planet)
|
||||
6
tests/_black_hole_space_preview.tscn
Normal file
6
tests/_black_hole_space_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/_black_hole_space_preview.gd" id="1"]
|
||||
|
||||
[node name="BlackHoleSpacePreview" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
15
tests/_void_surface_preview.gd
Normal file
15
tests/_void_surface_preview.gd
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
extends Node2D
|
||||
|
||||
# Preview, not a test - a black hole's landed "void" surface: no craters, no
|
||||
# resource pickups, nothing but the empty field itself.
|
||||
#
|
||||
# bash tests/screenshot.sh res://tests/_void_surface_preview.tscn /tmp/void.png 15 720x1280
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.planet_name = "Event Zero"
|
||||
GameState.planet_definition_id = "black_hole"
|
||||
GameState.surviving_ship_count = 3
|
||||
var surface: Node = load("res://PlanetSurface.tscn").instantiate()
|
||||
surface.get_node("SurfaceController").fade_duration = 0.0
|
||||
add_child(surface)
|
||||
6
tests/_void_surface_preview.tscn
Normal file
6
tests/_void_surface_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/_void_surface_preview.gd" id="1"]
|
||||
|
||||
[node name="VoidSurfacePreview" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
200
tests/test_black_hole.gd
Normal file
200
tests/test_black_hole.gd
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
extends SceneTree
|
||||
|
||||
# Headless gameplay test: run with
|
||||
# godot --headless -s res://tests/test_black_hole.gd
|
||||
# Forgejo #55: a rare landable body yielding dark matter, obtainable only
|
||||
# through an extractor - never a surface pickup.
|
||||
#
|
||||
# Covers:
|
||||
# A. WorldGen.planet_data_at_chunk() is a pure, deterministic function of
|
||||
# chunk coordinates, and black holes are noticeably rarer than gas
|
||||
# giants across a wide scan
|
||||
# B. dark matter is fully generic - in resource_types(), its own
|
||||
# ResourceDefinition/glyph/colour, distinct from every other resource
|
||||
# C. landing on a black hole spawns no resource provider and no capture
|
||||
# points at all (void's features_scene is null, and it has no
|
||||
# authored PlanetSetup) - yet the planet is immediately conquered
|
||||
# (register_capture_points treats zero required points as vacuous),
|
||||
# so an extractor is buildable there right away
|
||||
# D. an extractor on a black hole mines dark matter locally exactly like
|
||||
# any other extractor, and a hub transporter banks it through the
|
||||
# normal collection path with no black-hole-specific code
|
||||
|
||||
var game_state: Node
|
||||
var stage: int = 0
|
||||
var elapsed: float = 0.0
|
||||
var scene: Node
|
||||
var extractor_index: int = 0
|
||||
var load_limit: int = 0
|
||||
var baseline: Dictionary = {}
|
||||
|
||||
const BLACK_HOLE_AT := Vector2(500.0, 0.0)
|
||||
const MOTHERSHIP_AT := Vector2.ZERO
|
||||
const HUB_AT := Vector2(150.0, 0.0)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
game_state = root.get_node("GameState")
|
||||
|
||||
# --- A. deterministic and rare -----------------------------------------
|
||||
var black_hole_count: int = 0
|
||||
var gas_giant_count: int = 0
|
||||
var total_planets: int = 0
|
||||
for x in range(-40, 40):
|
||||
for y in range(-40, 40):
|
||||
var chunk: Vector2i = Vector2i(x, y)
|
||||
if not WorldGen.has_planet_at_chunk(chunk):
|
||||
continue
|
||||
total_planets += 1
|
||||
var first: Dictionary = WorldGen.planet_data_at_chunk(chunk)
|
||||
var second: Dictionary = WorldGen.planet_data_at_chunk(chunk)
|
||||
if first.definition_id != second.definition_id or first.local_pos != second.local_pos \
|
||||
or first.shape_seed != second.shape_seed or first.name != second.name:
|
||||
_fail("planet_data_at_chunk(%s) is not deterministic" % chunk)
|
||||
return
|
||||
if first.definition_id == "black_hole":
|
||||
black_hole_count += 1
|
||||
elif first.definition_id == "gas_giant":
|
||||
gas_giant_count += 1
|
||||
print("scanned %d planet chunks: %d black holes, %d gas giants" % [
|
||||
total_planets, black_hole_count, gas_giant_count
|
||||
])
|
||||
if black_hole_count == 0:
|
||||
_fail("no black holes generated across a wide scan - the rare outcome never fires")
|
||||
return
|
||||
if black_hole_count >= gas_giant_count:
|
||||
_fail("black holes (%d) are not rarer than gas giants (%d)" % [black_hole_count, gas_giant_count])
|
||||
return
|
||||
|
||||
# --- B. dark matter is fully generic -----------------------------------
|
||||
if not game_state.resource_types().has(GameState.RESOURCE_DARK_MATTER):
|
||||
_fail("dark_matter is not in resource_types() - every resource menu would miss it")
|
||||
return
|
||||
var definition: Resource = game_state.resource_definition(GameState.RESOURCE_DARK_MATTER)
|
||||
if definition == null or definition.glyph_scene == null:
|
||||
_fail("dark_matter has no ResourceDefinition or glyph_scene")
|
||||
return
|
||||
var dm_color: Color = game_state.resource_color(GameState.RESOURCE_DARK_MATTER)
|
||||
for other in [GameState.RESOURCE_ORE, GameState.RESOURCE_GAS, GameState.RESOURCE_ENERGY]:
|
||||
if dm_color == game_state.resource_color(other):
|
||||
_fail("dark_matter shares a colour with %s" % other)
|
||||
return
|
||||
print("dark matter is generic: in resource_types(), own definition, own colour %s" % dm_color)
|
||||
|
||||
# --- structural: the black hole definition itself -----------------------
|
||||
var black_hole_def: Resource = load("res://PlanetDefinitions.gd").get_definition("black_hole")
|
||||
if black_hole_def.resource_id != "dark_matter":
|
||||
_fail("black_hole planet's resource_id is '%s', expected 'dark_matter'" % black_hole_def.resource_id)
|
||||
return
|
||||
if black_hole_def.surface_profile_id != "void":
|
||||
_fail("black_hole planet's surface_profile_id is '%s', expected 'void'" % black_hole_def.surface_profile_id)
|
||||
return
|
||||
var void_profile: Resource = load("res://PlanetSurfaceProfiles.gd").get_profile("void")
|
||||
if void_profile.features_scene != null:
|
||||
_fail("void surface profile has a features_scene - dark matter must never be a surface pickup")
|
||||
return
|
||||
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.transporters.clear()
|
||||
game_state.planet_capture.clear()
|
||||
game_state.planet_id = "test_black_hole"
|
||||
game_state.planet_name = "Event Zero"
|
||||
game_state.planet_definition_id = "black_hole"
|
||||
scene = load("res://PlanetSurface.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
current_scene = scene
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
elapsed += delta
|
||||
match stage:
|
||||
0:
|
||||
# --- C. no resource provider, no capture points, yet conquered ---
|
||||
if elapsed < 0.3:
|
||||
return false
|
||||
var feature_host: Node = scene.get_node("SurfaceFeatures")
|
||||
if feature_host.get_child_count() != 0:
|
||||
return _fail("black hole surface spawned %d children under SurfaceFeatures - expected none" % feature_host.get_child_count())
|
||||
if not game_state.is_planet_conquered(game_state.planet_id):
|
||||
return _fail("a black hole with no capture points required is not marked conquered")
|
||||
print("no resource provider, no capture points, already conquered")
|
||||
|
||||
# Snapshot rather than assert > 0: bank_inventory starts with 500
|
||||
# ore by default (a fresh game's starting stock), unrelated to
|
||||
# this planet - "nothing was picked up" has to mean "unchanged",
|
||||
# not "still zero".
|
||||
for resource_type in game_state.resource_types():
|
||||
baseline[resource_type] = game_state.banked_resource(resource_type) + game_state.fleet_resource(resource_type)
|
||||
|
||||
var fleet: Node = scene.get_node("Fleet")
|
||||
for ship in fleet.ships:
|
||||
ship.position += Vector2(300.0, 200.0)
|
||||
fleet.move_to(fleet.get_centroid())
|
||||
elapsed = 0.0
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
# --- C continued: flying around picks up nothing, ever -----------
|
||||
if elapsed < 2.0:
|
||||
return false
|
||||
for resource_type in game_state.resource_types():
|
||||
var total: int = game_state.banked_resource(resource_type) + game_state.fleet_resource(resource_type)
|
||||
if total != baseline.get(resource_type, 0):
|
||||
return _fail("something (%s) was picked up over a black hole surface" % resource_type)
|
||||
print("flew around for %.1fs, nothing picked up" % elapsed)
|
||||
|
||||
# --- D: extractor mining + transporter banking --------------------
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.transporters.clear()
|
||||
game_state.reset_progression()
|
||||
for node in GameState.SKILL_TREE:
|
||||
game_state.unlocked.append(node.id)
|
||||
game_state.add_building(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
||||
game_state.add_building(GameState.BUILDING_TRANSPORT_HUB, HUB_AT)
|
||||
game_state.add_extractor("Event Zero", BLACK_HOLE_AT, GameState.RESOURCE_DARK_MATTER, "test_black_hole_extractor")
|
||||
extractor_index = game_state.extractor_index_at("test_black_hole_extractor")
|
||||
if extractor_index < 0:
|
||||
return _fail("add_extractor did not register the black hole extractor")
|
||||
load_limit = game_state.transporter_load_limit()
|
||||
elapsed = 0.0
|
||||
stage = 2
|
||||
return false
|
||||
2:
|
||||
# Mining is local, exactly like ore - no black-hole-specific code.
|
||||
if elapsed < 1.2:
|
||||
return false
|
||||
var mined: int = game_state.extractor_resource(extractor_index, GameState.RESOURCE_DARK_MATTER)
|
||||
if mined <= 0:
|
||||
return _fail("extractor mined no dark matter locally after %.1fs" % elapsed)
|
||||
print("extractor mined %d dark matter locally, bank still %d" % [
|
||||
mined, game_state.banked_resource(GameState.RESOURCE_DARK_MATTER)
|
||||
])
|
||||
# Force a full load directly (bypasses waiting out the real
|
||||
# mining rate for the rest) so the transporter pickup dispatch
|
||||
# fires without a long extra wait.
|
||||
game_state.extractors[extractor_index].inventory.set_amount(GameState.RESOURCE_DARK_MATTER, load_limit)
|
||||
elapsed = 0.0
|
||||
stage = 3
|
||||
return false
|
||||
3:
|
||||
# --- a transporter collects the full load and banks it -----------
|
||||
if game_state.banked_resource(GameState.RESOURCE_DARK_MATTER) >= load_limit:
|
||||
print("transporter banked %d dark matter" % game_state.banked_resource(GameState.RESOURCE_DARK_MATTER))
|
||||
print("TEST_BLACK_HOLE: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
if elapsed > 10.0:
|
||||
return _fail("dark matter was never collected to the bank (extractor=%d bank=%d)" % [
|
||||
game_state.extractor_resource(extractor_index, GameState.RESOURCE_DARK_MATTER),
|
||||
game_state.banked_resource(GameState.RESOURCE_DARK_MATTER),
|
||||
])
|
||||
return false
|
||||
return false
|
||||
|
||||
|
||||
func _fail(message: String) -> bool:
|
||||
print("TEST_BLACK_HOLE: FAIL (%s)" % message)
|
||||
quit(1)
|
||||
return true
|
||||
Loading…
Reference in a new issue