extends SceneTree # Forgejo #188: the authored, non-editor boss planet and its complete weak- # 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 # 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 # D. waves are larger, expelled outward, varied, and golden only 1/3 times # E. killing the marked hatchling sends a visible return to the boss and # opens an attackable tentacle eye # F. three destroyed eyes start the three-lane laser phase # G. eyes expose a separate core; destroying it persists conquest, and a # second landing does not resurrect the boss const COMBAT_TARGET = preload("res://core/CombatTarget.gd") const BOSS_SCRIPT = preload("res://boss/OctopusBoss.gd") var state: Node var surface: Node var encounter: Node var boss: Node2D var stage: int = 0 var elapsed: float = 0.0 func _initialize() -> void: state = root.get_node("GameState") state.reset_new_game() state.intro_active = false state.reset_quests(false) state.planet_name = "Many-Eyed Deep" state.planet_id = "test:boss:planet" state.planet_definition_id = PlanetDefinitions.BOSS_ID state.surviving_ship_count = 1 state.surviving_vessel_types = PackedStringArray([GameState.VESSEL_INTERCEPTOR]) surface = load("res://surface/PlanetSurface.tscn").instantiate() root.add_child(surface) current_scene = surface func _process(delta: float) -> bool: elapsed += delta match stage: 0: if elapsed < 0.2: return false # --- A. definition, dev placement, editor exclusion ---------- var definition: Resource = PlanetDefinitions.get_definition( PlanetDefinitions.BOSS_ID) if definition == null or definition.definition_id != PlanetDefinitions.BOSS_ID \ or definition.surface_profile_id != "boss" \ or definition.visual_scene == null: return _fail("boss planet definition is not registered") # Load at runtime, after the GameState autoload has compiled. Resolving # the whole surface registry while that autoload is still compiling # also pulls in pickup scripts which legitimately refer back to it. var surface_profiles: Script = load("res://surface/PlanetSurfaceProfiles.gd") var profile: Resource = surface_profiles.get_profile("boss") if profile == null or profile.encounter_scene == null: return _fail("boss surface has no scripted encounter") if profile.background_field_texture == null \ or not String(profile.background_shader.resource_path).ends_with( "lunar_tiled.gdshader"): return _fail("boss backdrop still performs procedural rendering during combat") var composed_background: ShaderMaterial = surface.get_node( "BackgroundLayer/Background").material if composed_background.get_shader_parameter("field_texture") \ != profile.background_field_texture: return _fail("boss backdrop did not bind its ready-made field texture") if profile.editor_visible or "boss" in surface_profiles.editor_profile_ids(): return _fail("boss surface is configurable in the map editor") state.dev_mode = true var boss_chunk: Variant = null for chunk in WorldGen.DEV_PLANET_CHUNKS: if String(WorldGen.DEV_PLANET_CHUNKS[chunk]) == PlanetDefinitions.BOSS_ID: boss_chunk = chunk break if boss_chunk == null or not WorldGen.has_planet_at_chunk(boss_chunk) \ or String(WorldGen.planet_data_at_chunk(boss_chunk).definition_id) \ != PlanetDefinitions.BOSS_ID: return _fail("dev mode does not plant the boss planet") state.dev_mode = false var normal_boss_found: bool = false 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") # --- B. generic surface composition --------------------------- encounter = surface.get_node_or_null("SurfaceFeatures/BossEncounter") if encounter == null: return _fail("landing did not compose BossEncounter") if surface.get_node_or_null("SurfaceFeatures/PlanetSetupHost") != null: return _fail("boss planet was routed through configurable PlanetSetup") if state.total_capture_points(state.planet_id) != 1 \ or state.is_planet_conquered(state.planet_id): return _fail("boss planet did not start as one unfinished persistent objective") if surface.get_node("UI/CaptureLabel").visible: return _fail("ordinary capture-point status is visible during the boss encounter") boss = encounter.get_node("Boss") if not (boss is Area2D) or boss.get_node_or_null("CoreCollision") == null: return _fail("the boss core has no projectile collision target") if boss.position.y > -2400.0 or boss.scale.x < 2.7: return _fail("the boss is not far above the landing point and at least twice its old size") if encounter.health_bar.visible or encounter.status_label.visible: return _fail("boss combat UI is visible before the boss enters view") if not encounter.indicator.visible or not encounter.indicator_label.visible: return _fail("the off-screen boss has no directional border indicator") if state.encounter_locks_movement or boss.combat_active: return _fail("the off-screen approach locked player movement or already started combat") # Put the camera where the player can carry the fleet; the encounter # itself must detect the boss crossing into view. surface.get_node("Camera2D").global_position = boss.global_position + Vector2(0.0, 1000.0) encounter._process(0.0) if not encounter.reveal_started: return _fail("bringing the boss into view did not trigger its reveal") if not state.encounter_locks_movement: return _fail("the reveal did not briefly lock movement for the cinematic") if not encounter.health_bar.visible or encounter.indicator.visible: return _fail("the reveal did not exchange the indicator for the health UI") var scream_heard: bool = false var sfx_pool: Node = root.get_node_or_null(Sfx.NODE_NAME) if sfx_pool != null: for voice in sfx_pool.get_children(): if voice.stream != null and voice.stream.resource_path.ends_with("sfx_boss_scream.wav"): scream_heard = true if not scream_heard: return _fail("the on-screen reveal played no boss scream") encounter._process(encounter.REVEAL_SECONDS + 0.1) if not encounter.encounter_active or not boss.combat_active \ or state.encounter_locks_movement: return _fail("the reveal did not start combat and return fleet control") print("off-screen signal led into a roar, shake, health reveal and player control") boss.wave_timer = 999.0 boss.laser_timer = 999.0 var health_bar: ProgressBar = encounter.get_node("EncounterUI/HealthBar") if health_bar.max_value != boss.max_health() \ or health_bar.value != boss.current_health(): return _fail("aggregate boss health bar did not start full") if boss.eyes.size() != boss.EYE_COUNT: return _fail("boss has %d tentacle eyes, expected %d" \ % [boss.eyes.size(), boss.EYE_COUNT]) for eye in boss.eyes: if eye.opened or eye.is_in_group(COMBAT_TARGET.GROUP): return _fail("a tentacle eye starts open and attackable") # --- D. larger, varied, one-in-three waves -------------------- boss.spawn_wave() var minions: Array = get_nodes_in_group("boss_minions") if minions.size() != boss.minions_per_wave: return _fail("phase one spawned %d hatchlings, expected %d" \ % [minions.size(), boss.minions_per_wave]) var marked: Node = null var dashers: int = 0 for minion in minions: if minion.uses_dash_pattern: dashers += 1 if minion.marked: if marked != null: return _fail("one wave contains more than one marked hatchling") marked = minion else: minion.take_damage(999) if marked == null: return _fail("phase-one wave has no marked hatchling") if minions.size() < 7 or dashers < 2: return _fail("the boss wave is not larger and behaviorally mixed") var launch_distance: float = marked.global_position.distance_to(boss.global_position) var launch_velocity: Vector2 = marked.velocity if launch_distance <= boss.BODY_RADIUS * boss.scale.x \ or launch_velocity.dot(marked.global_position - boss.global_position) <= 0.0: return _fail("a hatchling was not expelled outward from the boss") marked._physics_process(0.4) if marked.global_position.distance_to(boss.global_position) <= launch_distance + 100.0: return _fail("the outward launch did not spread a hatchling before it targeted the fleet") marked._physics_process(1.0) marked.dash_cooldown = 0.0 marked._physics_process(0.01) if marked.motion != marked.Motion.BRAKING: return _fail("a dash-pattern hatchling never stops before lunging") marked._physics_process(marked.BRAKE_SECONDS + 0.01) marked._physics_process(0.2) if marked.motion != marked.Motion.DASHING or marked.velocity.length() <= marked.speed: return _fail("a stopped hatchling did not accelerate into its random dash") if marked.max_hp < 5: return _fail("boss hatchlings still have only %d health" % marked.max_hp) marked.take_damage(1) if not is_instance_valid(marked) or marked.hp != marked.max_hp - 1: return _fail("a hatchling did not survive a normal weapon hit") if not get_nodes_in_group("boss_returns").is_empty(): return _fail("an ordinary hatchling returned energy to the boss") marked.take_damage(999) var returns: Array = get_nodes_in_group("boss_returns") if returns.size() != 1: return _fail("marked death launched %d returns, expected one" % returns.size()) returns[0].speed = 4000.0 for expected_marked in [0, 0, 1]: boss.spawn_wave() var spawned: Array = get_nodes_in_group("boss_minions") var marked_count: int = 0 for minion in spawned: if minion.is_queued_for_deletion(): continue if minion.marked: marked_count += 1 minion.retire() if marked_count != expected_marked: return _fail("golden cadence produced %d marked hatchlings, expected %d" \ % [marked_count, expected_marked]) print("larger waves launch outward with dashers; only one of three is golden") stage = 1 elapsed = 0.0 return false 1: # --- D. return opens an attackable eye ------------------------ if boss.eyes_opened < 1: if elapsed > 1.0: var pending: Array = get_nodes_in_group("boss_returns") var detail: String = "no return remains" if not pending.is_empty(): detail = "return at %s, boss at %s, speed %.1f, processing %s" % [ pending[0].global_position, boss.global_position, pending[0].speed, pending[0].is_processing()] return _fail("marked energy reached no tentacle eye (%s)" % detail) return false var eye: Node = boss.eyes[0] if not eye.opened or not eye.is_in_group(COMBAT_TARGET.GROUP): return _fail("returned energy did not make the eye attackable") var hp_before: int = eye.hp eye.take_damage(1) if eye.hp != hp_before - 1: return _fail("an open eye ignored weapon damage") if encounter.health_bar.value != boss.current_health() \ or encounter.health_bar.value >= encounter.health_bar.max_value: return _fail("total boss health bar ignored eye damage") eye.take_damage(999) if boss.eyes_destroyed != 1: return _fail("destroying the first eye did not damage the boss") if boss.max_health() - boss.current_health() >= boss.max_health() / boss.EYE_COUNT: return _fail("one eye still removes a full sixth of the total encounter health") print("return opened a tentacle eye; the eye accepted damage and broke") _open_return() stage = 2 elapsed = 0.0 return false 2: if boss.eyes_opened < 2: if elapsed > 1.0: return _fail("second marked return opened no eye") return false boss.eyes[1].take_damage(999) _open_return() stage = 3 elapsed = 0.0 return false 3: # --- E. phase two and laser pattern --------------------------- if boss.eyes_opened < 3: if elapsed > 1.0: return _fail("third marked return opened no eye") return false boss.eyes[2].take_damage(999) if boss.phase != BOSS_SCRIPT.Phase.LASER_STORM: return _fail("three broken eyes did not start the laser phase") boss.laser_timer = 999.0 boss.fire_laser_volley() var beams: Array = get_nodes_in_group("boss_lasers") if beams.size() != 3: return _fail("laser phase launched %d beams, expected three lanes" % beams.size()) if boss.laser_interval >= 5.5 or boss.laser_charge_time >= 1.6: return _fail("boss lasers retained their old slow cadence") if beams[0].aim_direction.is_equal_approx(beams[1].aim_direction) \ or beams[1].aim_direction.is_equal_approx(beams[2].aim_direction): return _fail("laser volley lanes all aim in the same direction") for beam in beams: if beam.phase != 0 or beam.charge_left <= 0.0: return _fail("boss laser was not telegraphed before firing") if not beam.infinite_range or not beam.reaches_point( beam.origin + beam.aim_direction * 1000000.0): return _fail("boss laser ray stops at a finite range") if beam.reaches_point(beam.origin - beam.aim_direction * 10.0): return _fail("boss laser extends backwards through its source") beam.queue_free() boss.spawn_wave() boss.spawn_wave() boss.spawn_wave() if boss.spawned_minions.size() > boss.MAX_ACTIVE_MINIONS: return _fail("boss waves can accumulate without a rendering/gameplay cap") for minion in boss.spawned_minions.duplicate(): if is_instance_valid(minion): minion.retire() print("three broken eyes started a telegraphed three-lane laser phase") # --- G. all eyes -> exposed core -> persistent victory -------- for i in range(3, boss.EYE_COUNT): boss.eyes[i].open_eye() boss.eyes[i].take_damage(999) if boss.phase != BOSS_SCRIPT.Phase.CORE_EXPOSED or not boss.visible: return _fail("breaking every eye did not expose a still-living core") if state.is_planet_conquered(state.planet_id): return _fail("breaking the eyes conquered the planet before the core died") if not boss.is_in_group(COMBAT_TARGET.GROUP): return _fail("the exposed core is not an attackable combat target") var core_before: int = boss.core_health boss.take_damage(1) if boss.core_health != core_before - 1 or boss.phase == BOSS_SCRIPT.Phase.DEFEATED: return _fail("the exposed core did not provide a separate final health pool") boss.take_damage(999) if boss.phase != BOSS_SCRIPT.Phase.DEFEATED or boss.visible: return _fail("destroying the core did not defeat and hide the boss") if not state.is_planet_conquered(state.planet_id): return _fail("boss victory did not persist through planet conquest state") if encounter.health_bar.value != 0 \ or encounter.health_bar.max_value != boss.max_health(): return _fail("boss health bar did not end empty with a stable total") print("all eyes exposed a durable core; destroying it persisted planet conquest") surface.free() surface = load("res://surface/PlanetSurface.tscn").instantiate() root.add_child(surface) current_scene = surface stage = 4 elapsed = 0.0 return false 4: if elapsed < 0.2: return false var second_encounter: Node = surface.get_node_or_null( "SurfaceFeatures/BossEncounter") if second_encounter == null: return _fail("second landing lost the boss encounter host") var second_boss: Node = second_encounter.get_node("Boss") if second_boss.phase != BOSS_SCRIPT.Phase.DEFEATED or second_boss.visible: return _fail("defeated boss resurrected on the second landing") if not "DEAD" in second_encounter.status_label.text: return _fail("second landing does not report the defeated boss") print("second landing keeps the boss dead") print("TEST_BOSS_PLANET: PASS") quit(0) return true return false func _open_return() -> void: boss.accept_marked_kill(boss.global_position + Vector2(12, 0)) var returns: Array = get_nodes_in_group("boss_returns") for returning in returns: returning.speed = 4000.0 func _fail(message: String) -> bool: print("TEST_BOSS_PLANET: FAIL (%s)" % message) quit(1) return true