spaceship/buildings/BuildingHitbox.gd

54 lines
1.9 KiB
GDScript3
Raw Normal View History

#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>
2026-08-23 13:53:17 +02:00
extends Area2D
# #101 (#97c): the part of a structure a wave can actually hit.
#
# A Building is a plain Node2D, so nothing could collide with it - enemies
# flew through the mothership without noticing. This gives it a body in the
# same shape the rest of the game already understands: an Area2D in a group,
# exactly like a Ship or an asteroid, so EnemyProjectile needed one extra
# branch rather than a new concept.
#
# Damage is applied on a TICK rather than on area_entered. Entry fires once,
# which is right for a rammer - it 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, which is what #97 asks
# for.
const STRIKE_INTERVAL: float = 0.7
var _strike_timer: float = 0.0
func _ready() -> void:
add_to_group("player_buildings")
func building_id() -> int:
var building: Node = get_parent()
return 0 if building == null else int(building.building_id)
# Called by EnemyProjectile, which knows nothing about buildings beyond this
# name - the same call it makes on a Ship.
func take_damage(amount: int) -> void:
GameState.damage_building(building_id(), amount)
func _physics_process(delta: float) -> void:
_strike_timer -= delta
if _strike_timer > 0.0:
return
_strike_timer = STRIKE_INTERVAL
var id: int = building_id()
if id == 0:
return
for area in get_overlapping_areas():
if not is_instance_valid(area) or not area.is_in_group("enemies"):
continue
var damage: int = area.contact_damage() if area.has_method("contact_damage") else 6
# The structure is told first, for the same reason Ship tells the
# enemy first: resolving the enemy's own death can free it, and a
# rammer must still land its hit on the way out.
GameState.damage_building(id, damage)
if area.has_method("collide_with_player_building"):
area.collide_with_player_building()