Gas bombers consume gas from the fleet hold when firing (#60)
Shots cost GAS_PER_SHOT (5) from GameState.fleet_inventory, not the bank - the fleet has to be carrying its own ammunition. The button's enabled rule now also requires affordability, and a press on a ready- but-unaffordable bomber still consumes the touch to explain itself via a new status label (GasBombStatus, IconButton's own "greyed but still pressable" convention) rather than either silently doing nothing or consuming the bomber's cooldown for a shot that never fires. Also documents a real, reproducible GDScript trap hit while writing tests/test_gas_bomber_ammo.gd: a bare `ClassName.SOME_CONST` reference anywhere in a -s entry test script can corrupt method resolution for the WHOLE script, making an unrelated, earlier `some_node._process()` call fail with "Nonexistent function '_process' in base 'Nil'" despite the node being valid and the method existing (confirmed via has_method()). Root cause unconfirmed; fixed by mirroring the constant 's value into a local const instead of referencing the class directly, in all three gas-bomber tests. tests/test_gas_bomber.gd and test_gas_bomber_cooldowns.gd updated to stock fleet gas first, since shots are no longer free.
This commit is contained in:
parent
116e0f97d3
commit
17d0cf7e66
8 changed files with 230 additions and 9 deletions
|
|
@ -10,11 +10,17 @@ const AIM_DEADZONE := 22.0
|
|||
const PROJECTILE_SPEED := 720.0
|
||||
const DAMAGE := 3
|
||||
const COOLDOWN := 5.0
|
||||
# #60: ammunition from the fleet hold, not the bank - the fleet has to be
|
||||
# carrying its own gas to fire.
|
||||
const GAS_PER_SHOT: int = 5
|
||||
|
||||
@export var minimum_zoom: float = 0.4
|
||||
@onready var fleet: Node2D = get_node("../Fleet")
|
||||
@onready var camera: Camera2D = get_node("../Camera2D")
|
||||
@onready var button: IconButton = get_node("../UI/GasBombButton")
|
||||
@onready var status: Label = get_node("../UI/GasBombStatus")
|
||||
|
||||
var status_message_until: int = 0
|
||||
|
||||
var aiming := false
|
||||
var aim_touch := -1
|
||||
|
|
@ -40,15 +46,27 @@ var explosion_time: float:
|
|||
|
||||
func _ready() -> void:
|
||||
button.visible = false
|
||||
status.text = ""
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _can_afford_shot() -> bool:
|
||||
return GameState.fleet_resource(GameState.RESOURCE_GAS) >= GAS_PER_SHOT
|
||||
|
||||
|
||||
func _say(message: String) -> void:
|
||||
status.text = message
|
||||
status_message_until = Time.get_ticks_msec() + 3000
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var had_visuals := aiming or not projectiles.is_empty() or not explosions.is_empty()
|
||||
button.visible = fleet.has_capability("gas_bomber")
|
||||
button.enabled = _next_ready_bomber() != null
|
||||
button.enabled = _next_ready_bomber() != null and _can_afford_shot()
|
||||
if not button.visible and aiming:
|
||||
_cancel_aim()
|
||||
if status.text != "" and Time.get_ticks_msec() >= status_message_until:
|
||||
status.text = ""
|
||||
|
||||
if restoring_zoom and not aiming:
|
||||
var z := move_toward(camera.zoom.x, saved_zoom, maxf(saved_zoom, 0.1) * delta * 5.0)
|
||||
|
|
@ -81,14 +99,23 @@ func _process(delta: float) -> void:
|
|||
|
||||
|
||||
func can_begin_aim() -> bool:
|
||||
return button.visible and button.enabled and not button._blocked_by_modal() \
|
||||
and not aiming
|
||||
# Deliberately NOT button.enabled: an unaffordable shot still needs the
|
||||
# press to reach here so it can explain itself (IconButton's own "greyed
|
||||
# but still pressable" convention) rather than the touch silently
|
||||
# falling through to whatever's behind it. A wholly unready fleet (no
|
||||
# bomber at all) has nothing more useful to say than "greyed out".
|
||||
return button.visible and not button._blocked_by_modal() \
|
||||
and not aiming and _next_ready_bomber() != null
|
||||
|
||||
|
||||
func handle_touch(event: InputEventScreenTouch) -> bool:
|
||||
if event.pressed:
|
||||
if not can_begin_aim() or not Rect2(button.global_position, button.size).has_point(event.position):
|
||||
return false
|
||||
if not _can_afford_shot():
|
||||
_say("Need %d gas in the fleet hold to fire (have %d)" \
|
||||
% [GAS_PER_SHOT, GameState.fleet_resource(GameState.RESOURCE_GAS)])
|
||||
return true
|
||||
aiming = true
|
||||
aim_touch = event.index
|
||||
aim_start_screen = event.position
|
||||
|
|
@ -156,17 +183,23 @@ func _fire() -> void:
|
|||
if bomber == null:
|
||||
_cancel_aim()
|
||||
return
|
||||
if not _can_afford_shot():
|
||||
_say("Need %d gas in the fleet hold to fire (have %d)" \
|
||||
% [GAS_PER_SHOT, GameState.fleet_resource(GameState.RESOURCE_GAS)])
|
||||
_cancel_aim()
|
||||
return
|
||||
var component: Node = bomber.get_component("gas_bomber")
|
||||
if not component.consume_shot(COOLDOWN):
|
||||
_cancel_aim()
|
||||
return
|
||||
GameState.withdraw_fleet_resource(GameState.RESOURCE_GAS, GAS_PER_SHOT)
|
||||
projectiles.append({
|
||||
"position": bomber.global_position,
|
||||
"target": aim_target,
|
||||
"trail": PackedVector2Array([bomber.global_position]),
|
||||
})
|
||||
_finish_aim()
|
||||
button.enabled = _next_ready_bomber() != null
|
||||
button.enabled = _next_ready_bomber() != null and _can_afford_shot()
|
||||
|
||||
|
||||
func _next_ready_bomber() -> Node2D:
|
||||
|
|
|
|||
Loading…
Reference in a new issue