#211 conquest is the fight now: a planet registers the garrison its setup authors, each defender's own death counts against it, and the last one takes the world. The capture points - stand in a ring, wait out a timer, once per ring - are gone, along with the editor palette that placed them. #212 conquest opens a dialog on the surface naming the research points recovered; the award travels with the signal, after it is banked. #209 the prologue's charted sectors go with the breach, so the map the survivors keep is of where they now are - and "chart 30 sectors" is no longer complete before they have flown anywhere. #218 enemy variety is gated on the FLEET, not on wave strength: a starting fleet meets chasers only at any wave number, and each vessel past three opens one more kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
171 lines
9.2 KiB
GDScript
171 lines
9.2 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless gameplay test: run with
|
|
# godot --headless -s res://tests/test_research_points.gd
|
|
# Forgejo #63: a rare enemy planet type that yields no resources but grants
|
|
# one research point when conquered - a required currency for mid/late
|
|
# skill-tree nodes and vessel/lab research, gated alongside their existing
|
|
# ore/resource costs.
|
|
#
|
|
# Covers:
|
|
# A. research labs generate deterministically and are rare
|
|
# (structural) the definition yields no resources and its surface has no
|
|
# resource provider at all
|
|
# F. a fresh game can still reach a ship crafter and an extractor with
|
|
# zero research points
|
|
# D. a research-point-gated skill node is refused while unaffordable, and
|
|
# explains why
|
|
# E. conquering a research_lab awards what its MAP says it is worth, once.
|
|
# The reward used to be a flat 1 on the planet definition; it now comes
|
|
# entirely from the authored setup, so a hard lab can pay more than an
|
|
# easy one and the value is editable in the map editor. The planet type
|
|
# contributing nothing of its own is part of what is checked (guarded
|
|
# even against a redundant re-mark), emits the signal once, and a
|
|
# non-research_lab planet's conquest never awards one
|
|
# D continued: unlocking spends the points and the balance updates
|
|
# G. the ship-lab research path gates and spends research points the
|
|
# same way, independent of the skill tree
|
|
|
|
var game_state: Node
|
|
|
|
|
|
func _initialize() -> void:
|
|
game_state = root.get_node("GameState")
|
|
|
|
# --- A. deterministic and rare -------------------------------------------
|
|
var lab_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:
|
|
return _fail("planet_data_at_chunk(%s) is not deterministic" % chunk)
|
|
if first.definition_id == "research_lab":
|
|
lab_count += 1
|
|
print("scanned %d planet chunks: %d research labs" % [total_planets, lab_count])
|
|
if lab_count == 0:
|
|
return _fail("no research labs generated across a wide scan - the rare outcome never fires")
|
|
if lab_count * 3 >= total_planets:
|
|
return _fail("research labs (%d of %d) are not rare" % [lab_count, total_planets])
|
|
print("research labs are deterministic and rare (%d of %d planet chunks)" % [lab_count, total_planets])
|
|
|
|
# --- structural: yields nothing at all ------------------------------------
|
|
var lab_def: Resource = load("res://planets/PlanetDefinitions.gd").get_definition("research_lab")
|
|
if String(lab_def.resource_id) != "":
|
|
return _fail("research_lab planet's resource_id is '%s', expected empty" % lab_def.resource_id)
|
|
var profile: Resource = load("res://surface/PlanetSurfaceProfiles.gd").get_profile("research_lab")
|
|
if profile.features_scene != null:
|
|
return _fail("research_lab surface profile has a features_scene - it must yield nothing")
|
|
print("research_lab yields no resource id and no surface feature provider")
|
|
|
|
# --- F. a fresh game still reaches a crafter and an extractor -------------
|
|
game_state.reset_new_game()
|
|
# #108: reset_new_game() arms the prologue, and the prologue opens with a
|
|
# convoy rather than the game's own starting fleet. This test is about
|
|
# what the GAME starts from, which is where the prologue hands over -
|
|
# see test_intro_scene.gd for the prologue itself.
|
|
game_state.intro_active = false
|
|
# can_unlock() also checks the ore price, and a new game now banks
|
|
# nothing - the opening mining run is the point. Stock the ore so this
|
|
# checks what it says it checks: reachability at ZERO RESEARCH POINTS.
|
|
game_state.rocks = 500
|
|
if not game_state.can_unlock("shipyards"):
|
|
return _fail("shipyards is not reachable with 0 research points")
|
|
game_state.unlock("shipyards")
|
|
if not game_state.can_unlock("logistics"):
|
|
return _fail("logistics is not reachable with 0 research points after shipyards")
|
|
game_state.unlock("logistics")
|
|
print("fresh game reaches a ship crafter (shipyards) and an extractor (logistics) at 0 research points")
|
|
|
|
# --- D. a points-gated node is refused and explains why -------------------
|
|
if game_state.can_unlock("coordination"):
|
|
return _fail("coordination (costs research points) was unlockable with 0 research points")
|
|
print("a research-point-gated node (coordination) correctly refused at 0 points")
|
|
|
|
# --- E. earning exactly one point, once, with the signal ------------------
|
|
var totals_seen: Array = []
|
|
game_state.research_points_changed.connect(func(total): totals_seen.append(total))
|
|
game_state.planet_capture.clear()
|
|
# The planet type itself must no longer carry a reward - that is the
|
|
# delegation. If it crept back, a lab would pay its map's value PLUS a
|
|
# hidden one, or pay something even with no map authored.
|
|
var lab_definition: Resource = load("res://planets/PlanetDefinitions.gd").get_definition("research_lab")
|
|
if int(lab_definition.conquest_research_points) != 0:
|
|
return _fail("the research_lab planet type still grants %d point(s) of its own - the reward belongs to the authored map now" \
|
|
% int(lab_definition.conquest_research_points))
|
|
game_state.register_planet_garrison("test_lab_no_reward", 1, "research_lab")
|
|
game_state.mark_garrison_enemy_destroyed("test_lab_no_reward")
|
|
if game_state.research_points != 0:
|
|
return _fail("a lab whose map states no reward still awarded %d" % game_state.research_points)
|
|
print("the planet type grants nothing by itself; an unpriced map awards nothing")
|
|
|
|
# What an authored map is worth is passed through as the override, exactly
|
|
# as PlanetSetupHost does from the setup's research_point_reward.
|
|
game_state.register_planet_garrison("test_lab_planet", 2, "research_lab", 1)
|
|
if game_state.is_planet_conquered("test_lab_planet"):
|
|
return _fail("a lab with 2 real capture points started conquered")
|
|
game_state.mark_garrison_enemy_destroyed("test_lab_planet")
|
|
if game_state.research_points != 0:
|
|
return _fail("a research point was awarded before every point was captured")
|
|
game_state.mark_garrison_enemy_destroyed("test_lab_planet")
|
|
if game_state.research_points != 1:
|
|
return _fail("conquering a research_lab did not award the 1 point its map is worth: %d" % game_state.research_points)
|
|
if totals_seen != [1]:
|
|
return _fail("research_points_changed did not fire exactly once with the new total: %s" % [totals_seen])
|
|
print("conquering a research_lab awarded exactly what its map is worth, once")
|
|
|
|
game_state.mark_garrison_enemy_destroyed("test_lab_planet") # already beaten - redundant kill
|
|
if game_state.research_points != 1:
|
|
return _fail("re-marking an already-captured point re-awarded a research point: %d" % game_state.research_points)
|
|
print("double-award guarded against")
|
|
|
|
game_state.register_planet_garrison("test_rocky_planet", 1, "rocky")
|
|
game_state.mark_garrison_enemy_destroyed("test_rocky_planet")
|
|
if game_state.research_points != 1:
|
|
return _fail("conquering a non-research_lab planet awarded a research point: %d" % game_state.research_points)
|
|
print("a non-research_lab planet's conquest never awards a point")
|
|
|
|
# --- D continued: unlocking spends the points, balance updates ------------
|
|
if not game_state.can_unlock("coordination"):
|
|
return _fail("coordination is still not unlockable with 1 research point and enough ore")
|
|
if not game_state.unlock("coordination"):
|
|
return _fail("unlock(coordination) failed despite can_unlock() being true")
|
|
if game_state.research_points != 0:
|
|
return _fail("unlocking a 1-point node did not spend the point: %d remaining" % game_state.research_points)
|
|
print("unlocking a research-point-gated skill node spent the point, balance now 0")
|
|
|
|
# --- G. the ship-lab research path gates and spends the same currency -----
|
|
var carrier_def: Resource = game_state.vessel_definition(GameState.VESSEL_CARRIER)
|
|
var point_cost: int = int(carrier_def.research_point_cost)
|
|
if point_cost <= 0:
|
|
return _fail("carrier production data has no research-point cost")
|
|
game_state.vessel_levels.erase(carrier_def.unlock_id)
|
|
var lab_id: int = game_state.add_building(GameState.BUILDING_SHIP_LAB, Vector2.ZERO)
|
|
if not game_state.queue_lab_research(lab_id, carrier_def.unlock_id):
|
|
return _fail("could not queue carrier research")
|
|
game_state.deposit_resource_to_building(lab_id, GameState.RESOURCE_ORE, 1000)
|
|
game_state._advance_lab_research(100.0)
|
|
if game_state.vessel_is_unlocked(GameState.VESSEL_CARRIER):
|
|
return _fail("carrier research completed despite 0 research points")
|
|
print("lab research correctly stalled on a research-point shortfall")
|
|
|
|
game_state.research_points = point_cost
|
|
game_state._advance_lab_research(100.0)
|
|
if not game_state.vessel_is_unlocked(GameState.VESSEL_CARRIER):
|
|
return _fail("carrier research did not complete once research points were available")
|
|
if game_state.research_points != 0:
|
|
return _fail("lab research did not spend the research points: %d remaining" % game_state.research_points)
|
|
print("lab research spent the research points once affordable, and completed")
|
|
|
|
print("TEST_RESEARCH_POINTS: PASS")
|
|
quit(0)
|
|
|
|
|
|
func _fail(message: String) -> void:
|
|
print("TEST_RESEARCH_POINTS: FAIL (%s)" % message)
|
|
quit(1)
|