Resolve pending gameplay tickets

This commit is contained in:
Loïc Wisniewski 2026-09-02 13:43:56 +02:00
commit c2ebbfaa2d
28 changed files with 618 additions and 188 deletions

View file

@ -383,7 +383,7 @@ func _show_building_info(type: String) -> void:
GameState.resource_name(planet.resource_type), planet.planet_name])
elif can_build and definition.unique and GameState.has_building_of_type(type):
lines.append("WARNING: only one may exist - this destroys your current one.")
if can_build and not GameState.can_afford_resources(costs):
if can_build and not GameState.can_afford_resources(costs, fleet.get_centroid()):
can_build = false
pending_build_type = type if can_build else ""
build_menu.show_info(definition.display_name, "\n\n".join(lines), costs, can_build)
@ -416,7 +416,7 @@ func _confirm_build() -> void:
if String(planet.resource_type) == "":
build_menu.set_status("%s has nothing to extract" % planet.planet_name)
return
if not GameState.spend_resources(GameState.building_costs(type)):
if not GameState.spend_resources(GameState.building_costs(type), fleet.get_centroid()):
build_menu.set_status(_cannot_afford(type))
return
GameState.add_extractor(planet.planet_name, planet.position,
@ -424,7 +424,7 @@ func _confirm_build() -> void:
build_menu.close_menu()
return
if definition.unique and GameState.has_building_of_type(type):
if not GameState.can_afford_resources(GameState.building_costs(type)):
if not GameState.can_afford_resources(GameState.building_costs(type), fleet.get_centroid()):
build_menu.set_status(_cannot_afford(type))
return
GameState.remove_buildings_of_type(type)
@ -437,14 +437,14 @@ func _cannot_afford(type: String) -> String:
for resource_type in GameState.building_costs(type):
var id: String = String(resource_type)
var want: int = int(GameState.building_costs(type)[resource_type])
var have: int = GameState.available_resource(id)
var have: int = GameState.available_resource_at(id, fleet.get_centroid())
if have < want:
missing.append("%s (need %d, have %d)" % [GameState.resource_name(id), want, have])
return "" if missing.is_empty() else "Not enough %s" % " and ".join(missing)
func _try_place(type: String) -> void:
if not GameState.spend_resources(GameState.building_costs(type)):
if not GameState.spend_resources(GameState.building_costs(type), fleet.get_centroid()):
build_menu.set_status(_cannot_afford(type))
return
building_field.place(type, fleet.get_centroid())
@ -452,7 +452,12 @@ func _try_place(type: String) -> void:
func _on_unlock_requested(id: String) -> void:
if not GameState.unlock(id):
var research_building: Dictionary = GameState.find_building(skill_menu.building_id)
if research_building.is_empty():
skill_menu.set_status("The research building is no longer available")
return
var payment_position: Variant = research_building.get("position", null)
if not GameState.unlock(id, payment_position):
skill_menu.refresh(id)
return
if int(GameState.skill_node(id).get("fleet_bonus", 0)) > 0:

View file

@ -0,0 +1,66 @@
class_name DashController
extends "res://controllers/AimedFleetAbilityController.gd"
# Energy Propulser active ability (#179): hold, aim a direction, release, and
# the complete fleet surges along that heading. Cooldown belongs to the
# particular Propulser selected by the shared ability controller, so adding a
# second hull adds a second independently recharging dash.
const DASH_COLOR := Color(0.85, 0.95, 0.15)
const DASH_DISTANCE: float = 520.0
const ENERGY_PER_DASH: int = 15
const TRAIL_SECONDS: float = 0.45
var trail_from: Vector2 = Vector2.ZERO
var trail_time: float = 0.0
func _button_path() -> NodePath: return "../UI/DashButton"
func _status_path() -> NodePath: return "../UI/DashStatus"
func _component_id() -> String: return "distorder"
func _ammo_resource() -> String: return GameState.RESOURCE_ENERGY
func _ammo_cost() -> int: return ENERGY_PER_DASH
func _aim_padding() -> float: return 36.0
func _aim_max_range() -> float: return DASH_DISTANCE
func _has_effect_visuals() -> bool: return trail_time > 0.0 or fleet.is_dashing()
func _calculate_aim_target(drag: Vector2, origin: Vector2) -> Vector2:
var direction: Vector2 = drag.normalized() if drag.length() > 0.001 else Vector2.UP
return origin + direction * DASH_DISTANCE
func _consume_shot(component: Node) -> bool:
return component.consume_dash()
func _perform_shot(_ship: Node2D, _component: Node) -> void:
var direction: Vector2 = aim_target - _aim_origin()
if direction.length() < 0.001:
return
trail_from = fleet.get_centroid()
trail_time = TRAIL_SECONDS
fleet.dash(direction, DASH_DISTANCE)
func _advance_effects(delta: float) -> void:
trail_time = maxf(trail_time - delta, 0.0)
func _draw() -> void:
if aiming:
var origin: Vector2 = _aim_origin()
NeonDraw.draw_glowing_line(self, origin, aim_target, DASH_COLOR, 0.65, 0.65)
var direction: Vector2 = (aim_target - origin).normalized()
var side: Vector2 = direction.orthogonal() * 18.0
NeonDraw.draw_glowing_path(self, PackedVector2Array([
aim_target - direction * 38.0 + side, aim_target,
aim_target - direction * 38.0 - side,
]), DASH_COLOR, 0.9, 0.55)
if trail_time > 0.0:
var alpha: float = trail_time / TRAIL_SECONDS
var now: Vector2 = fleet.get_centroid()
for offset in [-20.0, 0.0, 20.0]:
var across: Vector2 = (now - trail_from).normalized().orthogonal() * offset
NeonDraw.draw_glowing_line(self, trail_from + across, now + across,
DASH_COLOR, alpha * 0.6, 0.35)

View file

@ -0,0 +1 @@
uid://bk38ws0glrkyk

View file

@ -31,6 +31,7 @@ extends Node2D
# indirectly - e.g. Main.tscn loaded from inside a _screenshot_tool.gd
# preview - for a class_name this new. Node + duck-typed calls sidesteps it.
@onready var laser_controller: Node = get_node_or_null("LaserController")
@onready var dash_controller: Node = get_node_or_null("DashController")
@onready var speed_fader: Node = get_node_or_null("UI/SpeedFader")
var touches: Dictionary = {}
@ -367,6 +368,8 @@ func _handle_touch(event: InputEventScreenTouch) -> void:
return
if laser_controller != null and laser_controller.handle_touch(event):
return
if dash_controller != null and dash_controller.handle_touch(event):
return
if speed_fader != null and speed_fader.handle_touch(event):
return
if _handle_pivot_touch(event):
@ -399,6 +402,8 @@ func _handle_drag(event: InputEventScreenDrag) -> void:
return
if laser_controller != null and laser_controller.handle_drag(event):
return
if dash_controller != null and dash_controller.handle_drag(event):
return
if speed_fader != null and speed_fader.handle_drag(event):
return
if joystick != null and joystick.visible and joystick.handle_drag(event):

View file

@ -23,7 +23,7 @@ extends Node2D
# A literal, not PackedStringArray(...): a constructor call is not a constant
# expression, and the script fails to PARSE rather than to run - which shows up
# as "nonexistent function" on a node that looks fine in the scene.
const ABILITY_ORDER: PackedStringArray = ["GasBombController", "LaserController"]
const ABILITY_ORDER: PackedStringArray = ["GasBombController", "LaserController", "DashController"]
@onready var fleet: Node2D = get_node("../Fleet")
@onready var camera: Camera2D = get_node("../Camera2D")