#101 (#97c): waves attack buildings
Every enemy component used to ask the same question - which player SHIP is nearest - so a structure was scenery they flew straight through. A Building is also a plain Node2D, so there was nothing to collide with even if one had tried. EnemyTargets answers the question once, for pursuit, standoff, the lancer's lunge and the ranged weapon together, so they cannot drift into disagreeing about it. It keeps one thumb on the scale: a vessel inside 520px always wins, even against a nearer building, or the fleet could not defend anything by interposing itself and an enemy would fly past the ships shooting it. Buildings get a hitbox - an Area2D in a group, the same shape as a Ship or an asteroid, so EnemyProjectile needed one extra branch rather than a new concept. It applies damage on a tick rather than on area_entered: entry fires once, which is right for a rammer that dies on the first strike, but would let a sniper that drifted inside sit there scratch-free for ever. On a tick, an enemy parked on a structure is besieging it. What one strike costs is data on the enemy definition, for the same reason dies_on_contact is - a lancer arriving at 950px/s is not a sniper drifting into a wall. Also clears the asteroids in test_enemy_projectile_damage. set_process(false) only stops NEW asteroids; the ones already placed keep drifting, and one wandering into the shield vessel parked at (2000, 0) spent its single charge so the projectile fired next killed it. That failed about one parallel suite run in thirty and never in isolation. Clearing them is what the other combat tests already do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
26f3728546
commit
3debcd7edd
18 changed files with 403 additions and 54 deletions
65
enemies/EnemyTargets.gd
Normal file
65
enemies/EnemyTargets.gd
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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
|
||||
|
||||
|
||||
static func nearest_ship(node: Node, from: Vector2) -> Node2D:
|
||||
var fleet: Node2D = node.get_tree().get_first_node_in_group("player_fleet")
|
||||
if fleet == null:
|
||||
return null
|
||||
var nearest: Node2D = null
|
||||
var best: float = INF
|
||||
for ship in fleet.ships:
|
||||
if not is_instance_valid(ship):
|
||||
continue
|
||||
var d: float = from.distance_to(ship.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")
|
||||
Loading…
Reference in a new issue