146 lines
5.9 KiB
GDScript3
146 lines
5.9 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless test: run with
|
||
|
|
# godot --headless -s res://tests/test_outpost_building.gd
|
||
|
|
# Forgejo #102 (#97d): "A new building should be available, pretty early in
|
||
|
|
# the tech tree, to create outposts."
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. the outpost is in the building catalog, with its own visual, its own
|
||
|
|
# icon and its own map colour - not a reused one
|
||
|
|
# B. a skill node unlocks it, and it is EARLY: shallower than the deep
|
||
|
|
# branches, and reachable for less than the mid-game structures cost
|
||
|
|
# C. it is hidden from the build menu until that node is researched, and
|
||
|
|
# appears afterwards, like every other gated structure
|
||
|
|
# D. it can be placed, it stands up in the world, and it has enough health
|
||
|
|
# to be worth attacking - it exists to be shot at
|
||
|
|
|
||
|
|
const MAX_EARLY_DEPTH: int = 2
|
||
|
|
|
||
|
|
var game_state: Node
|
||
|
|
var scene: Node
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
# GameState is an autoload: a bare GameState.<instance member> does not
|
||
|
|
# compile in a SceneTree script, so the node is fetched by name instead.
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.buildings.clear()
|
||
|
|
game_state.extractors.clear()
|
||
|
|
game_state.transporters.clear()
|
||
|
|
game_state.reset_progression()
|
||
|
|
game_state.reset_waves()
|
||
|
|
game_state.rocks = 5000
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
print("TEST_OUTPOST_BUILDING: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
|
||
|
|
func _catalog_cases() -> bool:
|
||
|
|
var definitions: Script = load("res://buildings/BuildingDefinitions.gd")
|
||
|
|
var outpost: Resource = definitions.get_definition(GameState.BUILDING_OUTPOST)
|
||
|
|
if outpost == null:
|
||
|
|
return _fail("no \"outpost\" definition in the catalog")
|
||
|
|
if outpost.visual_scene == null:
|
||
|
|
return _fail("the outpost has no visual - it would be an invisible structure")
|
||
|
|
if outpost.menu_button_name == "":
|
||
|
|
return _fail("the outpost declares no build-menu entry, so it could never be built")
|
||
|
|
# --- A. its own look -------------------------------------------------
|
||
|
|
for other in definitions.all():
|
||
|
|
if other.definition_id == GameState.BUILDING_OUTPOST:
|
||
|
|
continue
|
||
|
|
if other.menu_icon == outpost.menu_icon:
|
||
|
|
return _fail("the outpost shares its icon with the %s" % other.definition_id)
|
||
|
|
if other.visual_scene == outpost.visual_scene:
|
||
|
|
return _fail("the outpost shares its visual with the %s" % other.definition_id)
|
||
|
|
if other.map_color.is_equal_approx(outpost.map_color):
|
||
|
|
return _fail("the outpost shares its map colour with the %s" % other.definition_id)
|
||
|
|
print("the outpost has its own icon, visual and map colour")
|
||
|
|
|
||
|
|
# --- B. researched, and early ----------------------------------------
|
||
|
|
var node_name: String = game_state.research_for_building(GameState.BUILDING_OUTPOST)
|
||
|
|
if node_name == "":
|
||
|
|
return _fail("nothing in the skill tree unlocks the outpost")
|
||
|
|
var node_id: String = ""
|
||
|
|
for node in GameState.SKILL_TREE:
|
||
|
|
if node.get("unlocks", "") == GameState.BUILDING_OUTPOST:
|
||
|
|
node_id = String(node.id)
|
||
|
|
var depth: int = game_state.skill_depth(node_id)
|
||
|
|
if depth > MAX_EARLY_DEPTH:
|
||
|
|
return _fail("\"%s\" sits %d prerequisites deep - that is not early" % [node_name, depth])
|
||
|
|
var cost: int = int(game_state.skill_node(node_id).cost)
|
||
|
|
for node in GameState.SKILL_TREE:
|
||
|
|
if String(node.get("unlocks", "")) in [GameState.BUILDING_TRANSPORT_HUB,
|
||
|
|
GameState.BUILDING_SHIP_LAB, GameState.BUILDING_GAS_BURNER]:
|
||
|
|
if int(node.cost) <= cost:
|
||
|
|
return _fail("\"%s\" costs %d, no less than the mid-game %s at %d" \
|
||
|
|
% [node_name, cost, node.name, node.cost])
|
||
|
|
print("\"%s\" is %d deep and costs %d ore - earlier and cheaper than the mid-game structures" \
|
||
|
|
% [node_name, depth, cost])
|
||
|
|
|
||
|
|
# --- C. gated until researched ---------------------------------------
|
||
|
|
if game_state.is_building_unlocked(GameState.BUILDING_OUTPOST):
|
||
|
|
return _fail("the outpost is buildable before its node is researched")
|
||
|
|
game_state.unlocked.append(node_id)
|
||
|
|
if not game_state.is_building_unlocked(GameState.BUILDING_OUTPOST):
|
||
|
|
return _fail("researching \"%s\" did not unlock the outpost" % node_name)
|
||
|
|
print("hidden until \"%s\" is researched, then offered" % node_name)
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> bool:
|
||
|
|
elapsed += delta
|
||
|
|
match stage:
|
||
|
|
0:
|
||
|
|
if _catalog_cases():
|
||
|
|
return true
|
||
|
|
scene = load("res://world/Main.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 1
|
||
|
|
return false
|
||
|
|
1:
|
||
|
|
if elapsed < 0.3:
|
||
|
|
return false
|
||
|
|
scene.get_node("AsteroidField").set_process(false)
|
||
|
|
scene.get_node("EnemySpawner").set_process(false)
|
||
|
|
# --- C (continued). the menu shows it ------------------------
|
||
|
|
var controller: Node = scene.get_node("BuildController")
|
||
|
|
var button: Control = scene.get_node("UI/BuildMenu/OutpostButton")
|
||
|
|
controller._layout_build_menu()
|
||
|
|
if not button.visible:
|
||
|
|
return _fail("the outpost is researched but its build-menu button is hidden")
|
||
|
|
scene.get_node("BuildingField").place(GameState.BUILDING_OUTPOST, Vector2(500.0, 0.0))
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 2
|
||
|
|
return false
|
||
|
|
2:
|
||
|
|
if elapsed < 0.2:
|
||
|
|
return false
|
||
|
|
# --- D. it stands up, and can be shot at ---------------------
|
||
|
|
var node: Node2D = null
|
||
|
|
for b in scene.get_node("BuildingField").spawned:
|
||
|
|
if is_instance_valid(b) and b.building_type == GameState.BUILDING_OUTPOST:
|
||
|
|
node = b
|
||
|
|
if node == null:
|
||
|
|
return _fail("placing an outpost produced no node")
|
||
|
|
if node.get_node_or_null("VisualHost").get_child_count() == 0:
|
||
|
|
return _fail("the outpost node has no visual under it")
|
||
|
|
if node.get_node_or_null("Hitbox") == null:
|
||
|
|
return _fail("the outpost has no hitbox - a wave could not touch it")
|
||
|
|
var hp: int = game_state.building_hp(node.building_id)
|
||
|
|
var crafter_hp: int = game_state.building_max_hp(GameState.BUILDING_SHIP_CRAFTER)
|
||
|
|
if hp < crafter_hp:
|
||
|
|
return _fail("the outpost has %d hp, less than a crafter's %d - it is meant to take hits" \
|
||
|
|
% [hp, crafter_hp])
|
||
|
|
print("an outpost stands at %d hp, against a crafter's %d" % [hp, crafter_hp])
|
||
|
|
|
||
|
|
print("TEST_OUTPOST_BUILDING: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|