A wave is an attack on what the player has built, not a hazard sitting in space waiting to be flown near. Wave enemies now ignore the detection radius their components otherwise respect and close on the nearest building from wherever they arrived. The flag is per INSTANCE rather than per definition: the same chaser is a wave when the director launches it and a garrison when a map author places it, and a planet garrison that chased the player across the system would be a very different thing from what the editor is used to place. The test pins both halves - a wave chaser 6000px out closed on a crafter, and an identically built one nobody sent held its station. What it fights on the way is unchanged: EnemyTargets already prefers a vessel inside 520px over a building further off, and already searches the player_ships group, so the fleet and an outpost's garrison both count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
2.7 KiB
GDScript
73 lines
2.7 KiB
GDScript
class_name EnemyTargets
|
|
extends RefCounted
|
|
|
|
# #101 (#97c): who a wave enemy is coming for.
|
|
#
|
|
# Before this, every enemy component asked the same question - "which player
|
|
# SHIP is nearest?" - and a structure was scenery they flew straight past.
|
|
# #97 makes waves go after what the player has built, so the question
|
|
# becomes "which player thing is nearest", asked in one place so pursuit,
|
|
# standoff, the lancer's lunge and the ranged weapon cannot drift into
|
|
# disagreeing about it.
|
|
#
|
|
# There is one thumb on the scale. A ship inside SHIP_PRIORITY_RANGE always
|
|
# wins, even if a building is nearer: an enemy that ignored the vessels
|
|
# shooting it in order to reach a depot two hundred pixels closer would read
|
|
# as broken, and would make the fleet unable to defend anything by
|
|
# interposing itself.
|
|
|
|
const SHIP_PRIORITY_RANGE: float = 520.0
|
|
|
|
|
|
# The GROUP, not Fleet.ships. #105 puts vessels at outposts that are not in
|
|
# the fleet at all, and an enemy that ignored the guns actually shooting at
|
|
# it - because they were not in the player's formation - would let a
|
|
# garrison hold a position without ever being fought back.
|
|
static func nearest_ship(node: Node, from: Vector2) -> Node2D:
|
|
var nearest: Node2D = null
|
|
var best: float = INF
|
|
for ship in node.get_tree().get_nodes_in_group("player_ships"):
|
|
if not is_instance_valid(ship) or ship.is_queued_for_deletion():
|
|
continue
|
|
var d: float = from.distance_to((ship as Node2D).global_position)
|
|
if d < best:
|
|
best = d
|
|
nearest = ship
|
|
return nearest
|
|
|
|
|
|
static func nearest_building(node: Node, from: Vector2) -> Node2D:
|
|
var nearest: Node2D = null
|
|
var best: float = INF
|
|
for building in node.get_tree().get_nodes_in_group("buildings"):
|
|
if not is_instance_valid(building) or building.is_queued_for_deletion():
|
|
continue
|
|
var d: float = from.distance_to(building.global_position)
|
|
if d < best:
|
|
best = d
|
|
nearest = building
|
|
return nearest
|
|
|
|
|
|
static func nearest_target(node: Node, from: Vector2) -> Node2D:
|
|
var ship: Node2D = nearest_ship(node, from)
|
|
if ship != null and from.distance_to(ship.global_position) <= SHIP_PRIORITY_RANGE:
|
|
return ship
|
|
var building: Node2D = nearest_building(node, from)
|
|
if building == null:
|
|
return ship
|
|
if ship == null:
|
|
return building
|
|
return ship if from.distance_to(ship.global_position) \
|
|
<= from.distance_to(building.global_position) else building
|
|
|
|
|
|
static func is_building(target: Node) -> bool:
|
|
return target != null and target.is_in_group("buildings")
|
|
|
|
|
|
# #113: whether this enemy may act on a target however far away it is.
|
|
# Asked here rather than read directly, so the three movement components
|
|
# cannot drift into disagreeing about what "relentless" means.
|
|
static func ignores_range(enemy: Node) -> bool:
|
|
return enemy != null and bool(enemy.get("relentless"))
|