Add planet scanner and octopus biome
This commit is contained in:
parent
2ecd61de98
commit
8fbcc340ce
21 changed files with 784 additions and 30 deletions
22
tests/_scanner_menu_preview.gd
Normal file
22
tests/_scanner_menu_preview.gd
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
extends Node2D
|
||||
|
||||
# Phone-sized visual fixture for the scene-owned scanner panel.
|
||||
# bash tests/screenshot.sh res://tests/_scanner_menu_preview.tscn /tmp/scanner.png 20 720x1280
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.reset_new_game()
|
||||
GameState.intro_active = false
|
||||
GameState.reset_quests(false)
|
||||
GameState.unlocked.append("deep_space_scanning")
|
||||
GameState.add_building(GameState.BUILDING_PLANET_SCANNER, Vector2.ZERO)
|
||||
for definition_id in GameState.SCANNER_SURVEY_TYPES:
|
||||
GameState.note_planet_visited("preview:%s" % definition_id, definition_id)
|
||||
# The panel owns a full-screen backdrop, so loading the entire live world
|
||||
# only adds software-renderer work and no pixels that can be seen.
|
||||
var menu: Control = load("res://ui/menus/ScannerMenu.tscn").instantiate()
|
||||
add_child(menu)
|
||||
menu.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
menu.position = Vector2.ZERO
|
||||
menu.size = get_viewport_rect().size
|
||||
menu.open_menu()
|
||||
6
tests/_scanner_menu_preview.tscn
Normal file
6
tests/_scanner_menu_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/_scanner_menu_preview.gd" id="1"]
|
||||
|
||||
[node name="ScannerMenuPreview" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
|
|
@ -4,8 +4,9 @@ extends SceneTree
|
|||
# point loop. This test drives the real landed surface and encounter nodes.
|
||||
#
|
||||
# Covers:
|
||||
# A. the planet/profile are registered, dev mode plants it nearby, and its
|
||||
# scripted profile is excluded from the map editor
|
||||
# A. the planet/profile are registered, dev mode plants it nearby, normal
|
||||
# generation owns exactly one inside its octopus biome, and its scripted
|
||||
# profile is excluded from the map editor
|
||||
# B. landing composes BossEncounter instead of a PlanetSetup and does not
|
||||
# mark the world conquered for free
|
||||
# C. the off-screen approach remains controllable and reveals with its own cue
|
||||
|
|
@ -86,20 +87,20 @@ func _process(delta: float) -> bool:
|
|||
!= PlanetDefinitions.BOSS_ID:
|
||||
return _fail("dev mode does not plant the boss planet")
|
||||
state.dev_mode = false
|
||||
var normal_boss_found: bool = false
|
||||
var normal_boss_count: int = 0
|
||||
for x in range(-24, 25):
|
||||
for y in range(-24, 25):
|
||||
var chunk := Vector2i(x, y)
|
||||
if WorldGen.has_planet_at_chunk(chunk) \
|
||||
and String(WorldGen.planet_data_at_chunk(chunk).get(
|
||||
"definition_id", "")) == PlanetDefinitions.BOSS_ID:
|
||||
normal_boss_found = true
|
||||
break
|
||||
if normal_boss_found:
|
||||
break
|
||||
if not normal_boss_found:
|
||||
return _fail("normal world generation never produces the boss planet")
|
||||
print("boss planet registered, editor-hidden, and generated normally plus in dev mode")
|
||||
normal_boss_count += 1
|
||||
if normal_boss_count != 1 \
|
||||
or WorldGen.biome_at_chunk(WorldGen.OCTOPUS_BIOME_CENTER) \
|
||||
!= WorldGen.Biome.OCTOPUS:
|
||||
return _fail("normal world generation has %d bosses, or its only boss is outside the octopus biome" \
|
||||
% normal_boss_count)
|
||||
print("boss planet registered, editor-hidden, unique in its normal octopus biome, plus nearby in dev mode")
|
||||
|
||||
# --- B. generic surface composition ---------------------------
|
||||
encounter = surface.get_node_or_null("SurfaceFeatures/BossEncounter")
|
||||
|
|
|
|||
|
|
@ -21,10 +21,16 @@ func _initialize() -> void:
|
|||
])
|
||||
return
|
||||
var provider: Node = building.visual.get_node_or_null("ActionProvider")
|
||||
if definition.definition_id == GameState.BUILDING_TRANSPORT_HUB:
|
||||
if "transport_hub" in definition.capabilities:
|
||||
if provider != null or not building.available_actions().is_empty():
|
||||
_fail("transport hub composed a manual action")
|
||||
return
|
||||
elif "planet_scanner" in definition.capabilities:
|
||||
if provider == null or not provider.actions.has("scanner") \
|
||||
or provider.actions.has("transfer"):
|
||||
_fail("%s did not compose its scanner-only action" \
|
||||
% definition.definition_id)
|
||||
return
|
||||
elif provider == null or not provider.actions.has("transfer"):
|
||||
_fail("%s has no composed transfer action" % definition.definition_id)
|
||||
return
|
||||
|
|
|
|||
132
tests/test_planet_scanner.gd
Normal file
132
tests/test_planet_scanner.gd
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
extends SceneTree
|
||||
|
||||
# Focused coverage for #192-#196: the scanner progression, its scene-owned
|
||||
# menu, the one hostile region and the ambient enemies that inhabit it.
|
||||
|
||||
const QUESTS = preload("res://core/Quests.gd")
|
||||
|
||||
class FakeFleet:
|
||||
extends Node2D
|
||||
var ships: Array = [Node2D.new()]
|
||||
|
||||
func get_centroid() -> Vector2:
|
||||
return position
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var state: Node = root.get_node("GameState")
|
||||
state.reset_new_game()
|
||||
state.intro_active = false
|
||||
state.reset_quests(false)
|
||||
|
||||
# #194: exact, finite hostile geography instead of an endless rare roll.
|
||||
WorldGen.dev_planets_enabled = false
|
||||
var boss_count: int = 0
|
||||
for x in range(-40, 41):
|
||||
for y in range(-40, 41):
|
||||
var chunk := Vector2i(x, y)
|
||||
if WorldGen.has_planet_at_chunk(chunk) \
|
||||
and String(WorldGen.planet_data_at_chunk(chunk).definition_id) \
|
||||
== PlanetDefinitions.BOSS_ID:
|
||||
boss_count += 1
|
||||
if boss_count != 1:
|
||||
return _fail("normal generation contains %d boss planets, expected exactly one" % boss_count)
|
||||
if WorldGen.biome_at_chunk(WorldGen.OCTOPUS_BIOME_CENTER) \
|
||||
!= WorldGen.Biome.OCTOPUS:
|
||||
return _fail("the unique boss is not inside the octopus biome")
|
||||
if WorldGen.octopus_biome_chunks().size() < 5:
|
||||
return _fail("the octopus biome is not a multi-chunk region")
|
||||
for chunk in WorldGen.octopus_biome_chunks():
|
||||
if WorldGen.biome_at_chunk(chunk) != WorldGen.Biome.OCTOPUS:
|
||||
return _fail("declared octopus chunk %s has another biome" % chunk)
|
||||
if chunk != WorldGen.OCTOPUS_BIOME_CENTER \
|
||||
and WorldGen.has_planet_at_chunk(chunk):
|
||||
return _fail("the hostile region contains an extra planet at %s" % chunk)
|
||||
print("one finite nine-chunk octopus biome contains the one normal boss planet")
|
||||
|
||||
# #193/#195: research gate, real generated scan results and boss gating.
|
||||
if state.is_building_unlocked(GameState.BUILDING_PLANET_SCANNER):
|
||||
return _fail("planet scanner is available without research")
|
||||
state.unlocked.append("coordination")
|
||||
state.bank_inventory.deposit(GameState.RESOURCE_ORE, 50)
|
||||
state.bank_inventory.deposit(GameState.RESOURCE_GAS, 15)
|
||||
state.research_points = 1
|
||||
if not state.unlock("deep_space_scanning") \
|
||||
or not state.is_building_unlocked(GameState.BUILDING_PLANET_SCANNER):
|
||||
return _fail("Deep-Space Scanning did not unlock its building")
|
||||
state.add_building(GameState.BUILDING_PLANET_SCANNER, Vector2.ZERO)
|
||||
var rocky: Dictionary = state.scan_for_planet(PlanetDefinitions.ROCKY_ID,
|
||||
Vector2.ZERO)
|
||||
if rocky.is_empty() or String(rocky.definition_id) != PlanetDefinitions.ROCKY_ID:
|
||||
return _fail("the scanner did not find a real rocky planet")
|
||||
if not state.explored_chunks.has(Vector2i(rocky.chunk)):
|
||||
return _fail("a scan result was not revealed on the persistent map")
|
||||
if PlanetDefinitions.BOSS_ID in state.scanner_available_planet_types():
|
||||
return _fail("boss signature is available before the four-world survey")
|
||||
|
||||
var menu: Control = load("res://ui/menus/ScannerMenu.tscn").instantiate()
|
||||
root.add_child(menu)
|
||||
menu.open_menu()
|
||||
if menu.get_node("BossButton").visible:
|
||||
return _fail("scanner menu shows the boss before the survey")
|
||||
for definition_id in GameState.SCANNER_SURVEY_TYPES:
|
||||
state.note_planet_visited("scanner:test:%s" % definition_id, definition_id)
|
||||
menu.refresh()
|
||||
if not menu.get_node("BossButton").visible \
|
||||
or not menu.get_node("BossLabel").visible:
|
||||
return _fail("the fifth scanner option did not appear after four planet types")
|
||||
var boss: Dictionary = state.scan_for_planet(PlanetDefinitions.BOSS_ID,
|
||||
Vector2.ZERO)
|
||||
if boss.is_empty() or Vector2i(boss.chunk) != WorldGen.OCTOPUS_BIOME_CENTER:
|
||||
return _fail("boss scan did not resolve the unique hostile region")
|
||||
print("researched scanner reveals generated destinations; the fifth option waits for 4/4")
|
||||
|
||||
# #192: the scanner is taught immediately before the widened survey.
|
||||
var scanner_quest_at: int = -1
|
||||
var worlds_at: int = -1
|
||||
for i in range(QUESTS.count()):
|
||||
if String(QUESTS.at(i).id) == "planet_scanner":
|
||||
scanner_quest_at = i
|
||||
if String(QUESTS.at(i).id) == "worlds":
|
||||
worlds_at = i
|
||||
if scanner_quest_at < 0 or worlds_at != scanner_quest_at + 1:
|
||||
return _fail("scanner quest is not directly before the world survey")
|
||||
if int(QUESTS.at(worlds_at).goal.count) != 4:
|
||||
return _fail("world survey does not require four planet types")
|
||||
print("the quest chain teaches the scanner immediately before the four-world survey")
|
||||
|
||||
# #196: entering/leaving the biome owns a bounded roaming population.
|
||||
var host := Node2D.new()
|
||||
root.add_child(host)
|
||||
var fake_fleet := FakeFleet.new()
|
||||
fake_fleet.name = "Fleet"
|
||||
fake_fleet.position = WorldGen.chunk_origin(WorldGen.OCTOPUS_BIOME_CENTER) \
|
||||
+ Vector2(WorldGen.CHUNK_SIZE * 0.5, WorldGen.CHUNK_SIZE * 0.5)
|
||||
fake_fleet.add_to_group("player_fleet")
|
||||
host.add_child(fake_fleet)
|
||||
var field: Node2D = load("res://world/OctopusField.gd").new()
|
||||
field.name = "OctopusField"
|
||||
host.add_child(field)
|
||||
field._process(0.1)
|
||||
if field.active.size() != 1:
|
||||
return _fail("entering the octopus biome spawned %d roamers" % field.active.size())
|
||||
var roamer: Node = field.active[0]
|
||||
if roamer.max_hp < 7 or not roamer.is_in_group("roaming_octopuses"):
|
||||
return _fail("ambient octopus lacks its durable roaming composition")
|
||||
fake_fleet.position = Vector2.ZERO
|
||||
field._process(0.1)
|
||||
if not field.active.is_empty():
|
||||
return _fail("roaming octopuses followed the fleet outside their biome")
|
||||
print("octopus biome spawns bounded durable roamers and retires them at its edge")
|
||||
|
||||
print("TEST_PLANET_SCANNER: PASS")
|
||||
quit(0)
|
||||
|
||||
|
||||
func _fail(message: String) -> void:
|
||||
print("FAIL: %s" % message)
|
||||
quit(1)
|
||||
Loading…
Reference in a new issue