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 # #165: how fast a friendly vessel has to be going before touching a structure # counts as hitting it. Comfortably above the speed a fleet drifts onto its own # mothership to unload at, and below cruise - so ordinary docking is free and # flying INTO a building is not. const RAM_SPEED: float = 420.0 const RAM_DAMAGE: int = 4 const RAM_DAMAGE_MAX: int = 12 var _strike_timer: float = 0.0 func _ready() -> void: add_to_group("player_buildings") area_entered.connect(_on_area_entered) # #165: a vessel of the player's own, arriving fast enough, damages what it # hits. # # On ENTRY, not on the strike tick above. The tick is right for a siege - an # enemy parked on a structure is besieging it - but wrong for a collision: a # vessel is only moving fast for the instant it arrives, and by the time a # 0.7s tick came round it had already braked to nothing. Measured: a ship sent # in at 900 did no damage at all until this moved to entry. # # Below the threshold it is docking, not a collision. The fleet sits on its own # mothership constantly to bank ore, and if resting there hurt, the economy # loop would grind the player's own buildings down. func _on_area_entered(area: Area2D) -> void: if not is_instance_valid(area) or not area.is_in_group("player_ships"): return # A vessel stationed at an outpost is not ramming it. It launches from # there, patrols, and comes back - repeatedly, and at speed. Only the # fleet can collide with a structure. if area.is_in_group("garrison_ships"): return var speed: float = float(area.velocity.length()) if "velocity" in area else 0.0 if speed < RAM_SPEED: return var id: int = building_id() if id == 0: return # Scaled by how much faster than the threshold it was going, so a glancing # touch above the line is not a full-speed charge, and capped so no engine # upgrade turns a vessel into a wrecking ball. var impact: int = mini(int(round(RAM_DAMAGE * speed / RAM_SPEED)), RAM_DAMAGE_MAX) GameState.damage_building(id, impact) area.take_damage(1) 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()