Add standoff sniper enemy (#45)
EnemyStandoffComponent holds a preferred distance from the nearest player ship instead of ramming (advance beyond it, retreat inside retreat_range_factor of it, drift laterally in the comfortable band between) - never paired with a pursuit component. EnemyRangedWeaponComponent mirrors VesselWeaponComponent for an enemy: fires an EnemyProjectile aimed at the target's CURRENT position rather than led, so a moving ship can dodge it (projectile_speed 260 vs the interceptor's 460 max_speed). Registered in EnemyDefinitions so it's placeable from a planet setup by enemy_id. test_sniper_enemy.gd went through a few iterations to get its own timing right: physics ticks can run ahead of a SceneTree test's own _process() clock under this device's load, so the cooldown-violation check reads the component's own fire_timer directly (a rising value means it refired) instead of comparing projectile counts against a wall-clock wait, and manually repositioned ships need their move_target re-pinned or Ship's own steering silently drives them back to the stale target. Part of the #40 conquest-mechanic breakdown.
This commit is contained in:
parent
c4e2ee7178
commit
fc2b5ca74a
13 changed files with 455 additions and 1 deletions
76
tests/_sniper_vs_chaser_preview.gd
Normal file
76
tests/_sniper_vs_chaser_preview.gd
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
extends Node2D
|
||||
|
||||
# Setup is deferred to the first _process() tick rather than done in
|
||||
# _ready(): PlanetAtmosphereController auto-spawns a 5-ship default fleet
|
||||
# in ITS _ready() on any fresh (non-returning) Main.tscn load, and since
|
||||
# this scene is itself added to the tree from inside _screenshot_tool.gd's
|
||||
# own _initialize() (a nested, not-yet-settled context), there's no
|
||||
# guaranteed ordering between the two _ready() calls - clearing the fleet
|
||||
# here could run BEFORE that auto-spawn and then get silently overwritten
|
||||
# by it. Waiting one frame guarantees it has already run first.
|
||||
#
|
||||
# Even after clearing, physics ticks can run several times before this
|
||||
# script's own next _process() tick (softpipe rendering is slow, and
|
||||
# physics is decoupled from it), so an auto-spawned ship can still get one
|
||||
# shot off in flight toward these enemies before the clearing code above
|
||||
# ever runs. Pausing the whole tree once everything is placed (last thing
|
||||
# this script does) freezes any such shot in transit for good, rather than
|
||||
# letting it arrive a few physics ticks later while the screenshot is
|
||||
# still waiting out its frame count.
|
||||
|
||||
var main: Node
|
||||
var setup_done: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.buildings.clear()
|
||||
GameState.extractors.clear()
|
||||
GameState.transporters.clear()
|
||||
main = load("res://Main.tscn").instantiate()
|
||||
add_child(main)
|
||||
main.get_node("BackgroundLayer").visible = false
|
||||
main.get_node("AsteroidField").visible = false
|
||||
main.get_node("PlanetField").visible = false
|
||||
main.get_node("BuildingField").visible = false
|
||||
main.get_node("EnemySpawner").set_process(false)
|
||||
main.get_node("AsteroidField").set_process(false)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if setup_done:
|
||||
return
|
||||
setup_done = true
|
||||
|
||||
var fleet: Node = main.get_node("Fleet")
|
||||
for ship in fleet.ships:
|
||||
if is_instance_valid(ship):
|
||||
ship.set_physics_process(false)
|
||||
fleet.spawn_ships(0, Vector2.ZERO)
|
||||
# spawn_ships(0, ...) still isn't a truly empty fleet: it falls back to
|
||||
# one default interceptor whenever the requested composition would
|
||||
# otherwise be empty (Fleet.gd's own "the fleet is never really empty"
|
||||
# rule). Clear that forced ship too, and halt it the same way.
|
||||
for ship in fleet.ships:
|
||||
if is_instance_valid(ship):
|
||||
ship.set_physics_process(false)
|
||||
ship.queue_free()
|
||||
fleet.ships.clear()
|
||||
for projectile in get_tree().get_nodes_in_group("player_projectiles"):
|
||||
projectile.queue_free()
|
||||
|
||||
var chaser: Node = load("res://Enemy.tscn").instantiate()
|
||||
chaser.enemy_id = "chaser"
|
||||
chaser.position = Vector2(-160, -80)
|
||||
main.add_child(chaser)
|
||||
chaser.speed = 0.0
|
||||
|
||||
var sniper: Node = load("res://Enemy.tscn").instantiate()
|
||||
sniper.enemy_id = "sniper"
|
||||
sniper.position = Vector2(160, -80)
|
||||
main.add_child(sniper)
|
||||
sniper.speed = 0.0
|
||||
|
||||
var camera: Camera2D = main.get_node("Camera2D")
|
||||
camera.zoom = Vector2(1.4, 1.4)
|
||||
|
||||
get_tree().paused = true
|
||||
Loading…
Reference in a new issue