extends SceneTree const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd") # Headless gameplay test: run with # godot --headless -s res://tests/test_transport_hub_building.gd # Forgejo #18 (part 5 of #12): the Transport Hub is a placeable building of # its own. Only the building here - the vessels that do the hauling are #19. # # Verifies: # 1. it is gated behind its research node, like every other structure # 2. researching it makes it buildable, and placing it charges its cost # 3. it is drawn as ITSELF, not as the crafter - Building._draw()'s match # has a catch-all that renders anything unrecognised as a ship crafter, # so a missing case is silent and looks almost right # 4. its geometry is rasterised ONCE however long it animates (#10), and # its stationed transports really do move # 5. it has its own build-menu icon and its own map marker rather than # borrowing another building's - the menu was once unreadable because # two buildings shared a glyph var game_state: Node var scene: Node var field: Node var controller: Node var map_content: Node2D var hub_building: Node2D var hub: Node2D var stage: int = 0 var elapsed: float = 0.0 var spin_at_start: float = 0.0 const HUB_AT: Vector2 = Vector2(700.0, 0.0) func _initialize() -> void: game_state = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() game_state.reset_progression() game_state.rocks = 500 game_state.fleet_cargo = 0 scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) field = scene.get_node("BuildingField") controller = scene.get_node("BuildController") map_content = scene.get_node("UI/WorldMap/MapContent") func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) # --- 1. locked until researched ------------------------------ if game_state.is_building_unlocked(GameState.BUILDING_TRANSPORT_HUB): print("TEST_TRANSPORT_HUB_BUILDING: FAIL (buildable with no research at all)") quit(1) return true var gate: String = game_state.research_for_building(GameState.BUILDING_TRANSPORT_HUB) if gate == "": print("TEST_TRANSPORT_HUB_BUILDING: FAIL (no research node gates it)") quit(1) return true print("locked behind research: '%s'" % gate) # --- 5. its own icon, distinct from every other building ----- var icons: Dictionary = {} for type in [GameState.BUILDING_MOTHERSHIP, GameState.BUILDING_SHIP_CRAFTER, GameState.BUILDING_COMMAND_CENTER, GameState.BUILDING_TRANSPORT_HUB]: var icon: int = BUILDING_DEFINITIONS.get_definition(type).action_icon if icons.has(icon): print("TEST_TRANSPORT_HUB_BUILDING: FAIL (%s and %s share an icon)" % [type, icons[icon]]) quit(1) return true icons[icon] = type print("action icons distinct across %d buildings" % icons.size()) # --- 2. research it, then build it --------------------------- game_state.unlocked = ["shipyards", "logistics", "freight"] if not game_state.is_building_unlocked(GameState.BUILDING_TRANSPORT_HUB): print("TEST_TRANSPORT_HUB_BUILDING: FAIL (still locked after researching '%s')" % gate) quit(1) return true var hub_cost: int = int(game_state.building_costs(GameState.BUILDING_TRANSPORT_HUB)[GameState.RESOURCE_ORE]) var before: int = game_state.rocks field.place(GameState.BUILDING_TRANSPORT_HUB, HUB_AT) game_state.spend(controller.cost_of(GameState.BUILDING_TRANSPORT_HUB)) if game_state.rocks != before - hub_cost: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (cost %d charged, expected %d)" % [ before - game_state.rocks, hub_cost ]) quit(1) return true print("researched and placed for %d ore" % hub_cost) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.3: return false for b in field.spawned: if is_instance_valid(b) and b.building_type == GameState.BUILDING_TRANSPORT_HUB: hub_building = b hub = b.visual if hub == null: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (never spawned)") quit(1) return true # #216 gave the hub one action, and only one: saying how many # transporters to keep here. Its ROUTES are still automatic - # there is nothing to hand-carry - so a transfer action would put # back exactly what this check was written to keep out. if hub_building.available_actions() != PackedStringArray(["staff"]): print("TEST_TRANSPORT_HUB_BUILDING: FAIL (offers %s, expected only the crew menu)" \ % [hub_building.available_actions()]) quit(1) return true for ship in scene.get_node("Fleet").ships: ship.position = HUB_AT scene.get_node("Fleet").move_to(HUB_AT) # --- 3. drawn as itself, not via the crafter catch-all ------- # _hub_traffic only exists if _ready() took the transport-hub # branch; a missing case falls through to the crafter and would # leave it null while still rendering something plausible. if hub._hub_traffic == null: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (rendered through the crafter catch-all, not as a hub)") quit(1) return true # --- 5. its own map marker ---------------------------------- var markers: Array = map_content.building_markers() var found: bool = false for m in markers: if m.type == GameState.BUILDING_TRANSPORT_HUB: found = true if m.color == BUILDING_DEFINITIONS.get_definition(GameState.BUILDING_SHIP_CRAFTER).map_color \ or m.color == BUILDING_DEFINITIONS.get_definition(GameState.BUILDING_MOTHERSHIP).map_color \ or m.color == BUILDING_DEFINITIONS.get_definition(GameState.BUILDING_COMMAND_CENTER).map_color: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (map marker reuses another building's colour)") quit(1) return true if not found: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (no map marker - see #11)") quit(1) return true print("has its own map marker") spin_at_start = hub._hub_traffic.rotation elapsed = 0.0 stage = 2 return false 2: # --- 4. animates without ever redrawing (#10) ---------------- if elapsed < 2.0: return false # #216 gave the hub one button - how many transporters to keep # here. The TRANSFER button stays absent: its routes are still # automatic, and hand-carrying to a hub is what this check was # written to keep out. if controller.current_building != hub_building \ or not controller.action_button.visible \ or controller.transfer_button.visible: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (hub context current=%s expected=%s action=%s transfer=%s)" % [ controller.current_building, hub_building, controller.action_button.visible, controller.transfer_button.visible, ]) quit(1) return true print("rasterisations after %.1fs: %d" % [elapsed, hub.draw_count]) if hub.draw_count != 1: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (redrew its geometry %d times to animate)" % hub.draw_count) quit(1) return true var spun: float = absf(hub._hub_traffic.rotation - spin_at_start) print("stationed transports turned %.3f rad" % spun) if spun < 0.05: print("TEST_TRANSPORT_HUB_BUILDING: FAIL (nothing moved - a frozen hub trivially passes the redraw check)") quit(1) return true print("TEST_TRANSPORT_HUB_BUILDING: PASS") quit(0) return true return false