Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2309ea1c2 | ||
|
|
a43fae5e28 | ||
|
|
93bfe62e76 |
15 changed files with 196 additions and 41 deletions
|
|
@ -839,7 +839,7 @@ func _on_hangar_crew_type(selection: Variant) -> void:
|
||||||
if vessel_type == "":
|
if vessel_type == "":
|
||||||
return
|
return
|
||||||
if fleet.ships.size() >= GameState.max_fleet_size():
|
if fleet.ships.size() >= GameState.max_fleet_size():
|
||||||
hangar_menu.set_status("Fleet is full (%d/%d) - research capacity at the mothership" \
|
hangar_menu.set_status("Fleet is full (%d/%d) - each further berth is a supplied Fleet Office" \
|
||||||
% [fleet.ships.size(), GameState.max_fleet_size()])
|
% [fleet.ships.size(), GameState.max_fleet_size()])
|
||||||
return
|
return
|
||||||
var stored_index: int = GameState.stored_vessels.find(vessel_type)
|
var stored_index: int = GameState.stored_vessels.find(vessel_type)
|
||||||
|
|
|
||||||
|
|
@ -383,14 +383,14 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
_handle_key(event)
|
_handle_key(event)
|
||||||
elif event is InputEventMouseMotion:
|
elif event is InputEventMouseMotion:
|
||||||
_note_keyboard()
|
_note_keyboard()
|
||||||
if speed_fader != null:
|
|
||||||
speed_fader.handle_mouse_motion(event)
|
|
||||||
elif event is InputEventMouseButton:
|
elif event is InputEventMouseButton:
|
||||||
_note_keyboard()
|
_note_keyboard()
|
||||||
# #118: the fader gets first refusal, so dragging the boost does not
|
# The speed fader used to be driven from here, and could not work:
|
||||||
# also pan or zoom the world behind it.
|
# MouseCombatController is dispatched before this node (it is a
|
||||||
if speed_fader != null and speed_fader.handle_mouse_button(event):
|
# child, and this is the root) and marks every left press handled to
|
||||||
return
|
# fire the guns, so the press never arrived. It listens for its own
|
||||||
|
# mouse events now - see SpeedFader._unhandled_input - and claims
|
||||||
|
# them from the UI layer, ahead of the combat controllers.
|
||||||
if not event.pressed:
|
if not event.pressed:
|
||||||
return
|
return
|
||||||
# Wheel zoom, the desktop equivalent of the pinch.
|
# Wheel zoom, the desktop equivalent of the pinch.
|
||||||
|
|
|
||||||
|
|
@ -951,11 +951,16 @@ func _advance_fleet_office_upkeep(delta: float) -> void:
|
||||||
fleet_upkeep_failed.emit(i + BASE_FLEET_SIZE, vessel_type)
|
fleet_upkeep_failed.emit(i + BASE_FLEET_SIZE, vessel_type)
|
||||||
|
|
||||||
|
|
||||||
|
# However many berths the fleet offices are keeping supplied, with no
|
||||||
|
# ceiling above them: a player who builds and feeds fifty offices flies
|
||||||
|
# fifty vessels. There used to be one at DEFAULT_FORMATION.size(), which was
|
||||||
|
# a limit of the LAYOUT rather than a rule of the game - the wedge simply ran
|
||||||
|
# out of authored positions at twenty. It now continues by rule instead (see
|
||||||
|
# formation_slot), so there is nothing left for the cap to protect.
|
||||||
func max_fleet_size() -> int:
|
func max_fleet_size() -> int:
|
||||||
if intro_fleet_size > 0:
|
if intro_fleet_size > 0:
|
||||||
return mini(intro_fleet_size, DEFAULT_FORMATION.size())
|
return intro_fleet_size
|
||||||
return mini(BASE_FLEET_SIZE + supplied_fleet_office_count(),
|
return BASE_FLEET_SIZE + supplied_fleet_office_count()
|
||||||
DEFAULT_FORMATION.size())
|
|
||||||
|
|
||||||
|
|
||||||
func reset_progression() -> void:
|
func reset_progression() -> void:
|
||||||
|
|
@ -2798,11 +2803,11 @@ func get_mothership_position() -> Variant:
|
||||||
# capacity upgrade is researched, but they are defined up front so the wedge
|
# capacity upgrade is researched, but they are defined up front so the wedge
|
||||||
# keeps a deliberate shape as it grows rather than having positions invented
|
# keeps a deliberate shape as it grows rather than having positions invented
|
||||||
# at unlock time.
|
# at unlock time.
|
||||||
# Twenty slots, because max_fleet_size() caps itself on this array's length -
|
# Twenty slots are AUTHORED. They are not a cap: the fleet grows as far as
|
||||||
# so the wedge has to be long enough for every doctrine to be worth buying.
|
# the offices supply it, and formation_slot() continues the wedge past the
|
||||||
# Each further pair continues the same V rather than starting a new shape:
|
# end of this array by the same rule the rows here already follow - each
|
||||||
# the rows step 30 out and 40 back, which is the rule the first ten already
|
# further pair steps 30 further out and 40 further back, so the wedge that
|
||||||
# followed, so the wedge that arrives at twenty is the one that left at three.
|
# arrives at fifty is the one that left at three.
|
||||||
const DEFAULT_FORMATION: Array = [
|
const DEFAULT_FORMATION: Array = [
|
||||||
Vector2(0, -48), Vector2(-30, -8), Vector2(30, -8), Vector2(-60, 32), Vector2(60, 32),
|
Vector2(0, -48), Vector2(-30, -8), Vector2(30, -8), Vector2(-60, 32), Vector2(60, 32),
|
||||||
Vector2(-90, 72), Vector2(90, 72), Vector2(0, 56),
|
Vector2(-90, 72), Vector2(90, 72), Vector2(0, 56),
|
||||||
|
|
@ -2814,9 +2819,43 @@ const DEFAULT_FORMATION: Array = [
|
||||||
|
|
||||||
var formation: Array = DEFAULT_FORMATION.duplicate()
|
var formation: Array = DEFAULT_FORMATION.duplicate()
|
||||||
|
|
||||||
|
# Which way the wedge currently faces. Kept because slots are grown on
|
||||||
|
# demand: one that appears after the player has pivoted has to arrive
|
||||||
|
# already turned, or a fleet that outgrows the authored twenty would sprout
|
||||||
|
# hulls pointing back along the formation's original heading.
|
||||||
|
var formation_rotation: float = 0.0
|
||||||
|
|
||||||
|
# The rule the wedge continues by past the authored slots. Pairs start at
|
||||||
|
# index 8 - the point where the hand-placed rows settle into a regular
|
||||||
|
# V - with the even index on the left, and each pair after that steps out
|
||||||
|
# and back by FORMATION_PAIR_STEP.
|
||||||
|
const FORMATION_PAIR_FIRST: int = 8
|
||||||
|
const FORMATION_PAIR_ORIGIN: Vector2 = Vector2(120.0, 112.0)
|
||||||
|
const FORMATION_PAIR_STEP: Vector2 = Vector2(30.0, 40.0)
|
||||||
|
|
||||||
|
|
||||||
func reset_formation() -> void:
|
func reset_formation() -> void:
|
||||||
formation = DEFAULT_FORMATION.duplicate()
|
formation = DEFAULT_FORMATION.duplicate()
|
||||||
|
formation_rotation = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
# Where one slot sits, growing the wedge if the fleet has outgrown the
|
||||||
|
# positions stored so far. Every reader asks through this rather than
|
||||||
|
# indexing `formation` directly, because with no cap on fleet size there is
|
||||||
|
# no index the array is guaranteed to already hold.
|
||||||
|
func formation_slot(index: int) -> Vector2:
|
||||||
|
if index < 0:
|
||||||
|
return Vector2.ZERO
|
||||||
|
while formation.size() <= index:
|
||||||
|
formation.append(_grown_formation_slot(formation.size()).rotated(formation_rotation))
|
||||||
|
return formation[index]
|
||||||
|
|
||||||
|
|
||||||
|
func _grown_formation_slot(index: int) -> Vector2:
|
||||||
|
var pair: int = (index - FORMATION_PAIR_FIRST) / 2
|
||||||
|
var out: float = FORMATION_PAIR_ORIGIN.x + FORMATION_PAIR_STEP.x * float(pair)
|
||||||
|
return Vector2(out if index % 2 == 1 else -out,
|
||||||
|
FORMATION_PAIR_ORIGIN.y + FORMATION_PAIR_STEP.y * float(pair))
|
||||||
|
|
||||||
|
|
||||||
func rotate_formation(radians: float) -> void:
|
func rotate_formation(radians: float) -> void:
|
||||||
|
|
@ -2824,6 +2863,7 @@ func rotate_formation(radians: float) -> void:
|
||||||
# and only changing which way it faces.
|
# and only changing which way it faces.
|
||||||
for i in range(formation.size()):
|
for i in range(formation.size()):
|
||||||
formation[i] = formation[i].rotated(radians)
|
formation[i] = formation[i].rotated(radians)
|
||||||
|
formation_rotation = wrapf(formation_rotation + radians, -PI, PI)
|
||||||
|
|
||||||
|
|
||||||
# --- Extractors ------------------------------------------------------------
|
# --- Extractors ------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const FIELDS: Array = [
|
||||||
"unlocked", "research_points", "vessel_levels",
|
"unlocked", "research_points", "vessel_levels",
|
||||||
# The fleet itself.
|
# The fleet itself.
|
||||||
"fleet_position", "fleet_vessel_types", "surviving_ship_count",
|
"fleet_position", "fleet_vessel_types", "surviving_ship_count",
|
||||||
"surviving_vessel_types", "formation", "stored_vessels",
|
"surviving_vessel_types", "formation", "formation_rotation", "stored_vessels",
|
||||||
"vessel_targets", "vessel_deliveries", "repair_drones",
|
"vessel_targets", "vessel_deliveries", "repair_drones",
|
||||||
"max_fleet_speed_multiplier", "fleet_speed_multiplier", "build_time_scale",
|
"max_fleet_speed_multiplier", "fleet_speed_multiplier", "build_time_scale",
|
||||||
# The world as the player has seen it.
|
# The world as the player has seen it.
|
||||||
|
|
|
||||||
1
tests/_lab_planet_visual_preview.gd.uid
Normal file
1
tests/_lab_planet_visual_preview.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bk3grd8nn8pj1
|
||||||
1
tests/test_building_separation.gd.uid
Normal file
1
tests/test_building_separation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ckdulc4563wt5
|
||||||
|
|
@ -10,16 +10,19 @@ extends SceneTree
|
||||||
# The boost was unreachable on a desktop and the formation could not be
|
# The boost was unreachable on a desktop and the formation could not be
|
||||||
# rearranged at all.
|
# rearranged at all.
|
||||||
#
|
#
|
||||||
# Driven through the REAL routing in each case (GameController for the
|
# Driven through the REAL routing in each case - the viewport itself for the
|
||||||
# fader, BuildController for the editor) rather than by calling the controls
|
# fader, BuildController for the editor - rather than by calling the
|
||||||
# directly, because the routing is what was missing - a test that called
|
# controls directly, because the routing is what was missing: a test that
|
||||||
# handle_mouse_button() itself would have passed against both reports.
|
# called handle_mouse_button() itself would have passed against both
|
||||||
|
# reports. It did, in fact, and hid a second one: the fader was dispatched
|
||||||
|
# from Main, which is the LAST node to see an unhandled press, so
|
||||||
|
# MouseCombatController fired the guns with it and the knob never moved.
|
||||||
#
|
#
|
||||||
# Covers:
|
# Covers:
|
||||||
# A. #118 - a click on the fader takes hold of it, dragging moves the
|
# A. #118 - a click on the fader takes hold of it, dragging moves the
|
||||||
# boost, and releasing lets go
|
# boost, and releasing lets go
|
||||||
# B. the fader claims the press, so dragging the boost does not also zoom
|
# B. the press stops at the fader: not the camera behind it, and not the
|
||||||
# the world behind it
|
# guns, which claim every other left press on a desktop
|
||||||
# C. #121 - a click grabs a formation slot, moving drags it, and the
|
# C. #121 - a click grabs a formation slot, moving drags it, and the
|
||||||
# formation really changes
|
# formation really changes
|
||||||
# D. releasing drops the slot, and moving afterwards does not keep dragging
|
# D. releasing drops the slot, and moving afterwards does not keep dragging
|
||||||
|
|
@ -67,6 +70,31 @@ func _fail(message: String) -> bool:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# Pushed through the viewport, not handed to a node's own _unhandled_input:
|
||||||
|
# WHICH node sees a press first is the thing under test. #118 shipped a fader
|
||||||
|
# that only worked when a test called it directly - MouseCombatController
|
||||||
|
# claims every left press to fire the guns, and it is dispatched well before
|
||||||
|
# Main's own handler, so on a real desktop the click never arrived.
|
||||||
|
# `true` keeps the coordinates in viewport space, which is the space the
|
||||||
|
# controls are laid out in.
|
||||||
|
func _push(event: InputEvent) -> void:
|
||||||
|
root.push_input(event, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _push_click(at: Vector2, pressed: bool) -> void:
|
||||||
|
var event: InputEventMouseButton = InputEventMouseButton.new()
|
||||||
|
event.button_index = MOUSE_BUTTON_LEFT
|
||||||
|
event.pressed = pressed
|
||||||
|
event.position = at
|
||||||
|
_push(event)
|
||||||
|
|
||||||
|
|
||||||
|
func _push_move(to: Vector2) -> void:
|
||||||
|
var event: InputEventMouseMotion = InputEventMouseMotion.new()
|
||||||
|
event.position = to
|
||||||
|
_push(event)
|
||||||
|
|
||||||
|
|
||||||
func _click(at: Vector2, pressed: bool, target: Node) -> void:
|
func _click(at: Vector2, pressed: bool, target: Node) -> void:
|
||||||
var event: InputEventMouseButton = InputEventMouseButton.new()
|
var event: InputEventMouseButton = InputEventMouseButton.new()
|
||||||
event.button_index = MOUSE_BUTTON_LEFT
|
event.button_index = MOUSE_BUTTON_LEFT
|
||||||
|
|
@ -112,22 +140,29 @@ func _process(delta: float) -> bool:
|
||||||
return _fail("the boost has no range to move through")
|
return _fail("the boost has no range to move through")
|
||||||
|
|
||||||
# --- A. click, drag, release -----------------------------
|
# --- A. click, drag, release -----------------------------
|
||||||
|
# With mouse aiming live, which is what a desktop always has:
|
||||||
|
# the guns are the other thing a left press means, and they used
|
||||||
|
# to take every one of them.
|
||||||
|
var mouse_combat: Node = scene.get_node("MouseCombatController")
|
||||||
|
mouse_combat.pointer_mode = mouse_combat.PointerMode.ON
|
||||||
var top: Vector2 = fader.global_position + Vector2(fader.size.x * 0.5,
|
var top: Vector2 = fader.global_position + Vector2(fader.size.x * 0.5,
|
||||||
fader.track_margin + 2.0)
|
fader.track_margin + 2.0)
|
||||||
var bottom: Vector2 = fader.global_position + Vector2(fader.size.x * 0.5,
|
var bottom: Vector2 = fader.global_position + Vector2(fader.size.x * 0.5,
|
||||||
fader.size.y - fader.track_margin - 2.0)
|
fader.size.y - fader.track_margin - 2.0)
|
||||||
_click(bottom, true, scene)
|
_push_click(bottom, true)
|
||||||
if not fader.is_active():
|
if not fader.is_active():
|
||||||
return _fail("a click on the fader did not take hold of it")
|
return _fail("a click on the fader did not take hold of it")
|
||||||
|
if mouse_combat.firing:
|
||||||
|
return _fail("the click that grabbed the fader also fired the fleet's guns")
|
||||||
var at_bottom: float = game_state.fleet_speed_multiplier
|
var at_bottom: float = game_state.fleet_speed_multiplier
|
||||||
_move(top, scene)
|
_push_move(top)
|
||||||
var at_top: float = game_state.fleet_speed_multiplier
|
var at_top: float = game_state.fleet_speed_multiplier
|
||||||
if at_top <= at_bottom:
|
if at_top <= at_bottom:
|
||||||
return _fail("dragging the fader from bottom to top left the boost at %.2f" % at_top)
|
return _fail("dragging the fader from bottom to top left the boost at %.2f" % at_top)
|
||||||
_click(top, false, scene)
|
_push_click(top, false)
|
||||||
if fader.is_active():
|
if fader.is_active():
|
||||||
return _fail("the fader is still held after the button came up")
|
return _fail("the fader is still held after the button came up")
|
||||||
_move(bottom, scene)
|
_push_move(bottom)
|
||||||
if not is_equal_approx(game_state.fleet_speed_multiplier, at_top):
|
if not is_equal_approx(game_state.fleet_speed_multiplier, at_top):
|
||||||
return _fail("the fader kept following the pointer after release")
|
return _fail("the fader kept following the pointer after release")
|
||||||
print("fader: %.2fx at the bottom, %.2fx at the top, released cleanly" \
|
print("fader: %.2fx at the bottom, %.2fx at the top, released cleanly" \
|
||||||
|
|
@ -135,10 +170,13 @@ func _process(delta: float) -> bool:
|
||||||
|
|
||||||
# --- B. it claims the press ------------------------------
|
# --- B. it claims the press ------------------------------
|
||||||
var zoom_before: float = scene.get_node("Camera2D").zoom.x
|
var zoom_before: float = scene.get_node("Camera2D").zoom.x
|
||||||
_click(bottom, true, scene)
|
_push_click(bottom, true)
|
||||||
_click(bottom, false, scene)
|
_push_click(bottom, false)
|
||||||
if not is_equal_approx(scene.get_node("Camera2D").zoom.x, zoom_before):
|
if not is_equal_approx(scene.get_node("Camera2D").zoom.x, zoom_before):
|
||||||
return _fail("pressing the fader also moved the camera behind it")
|
return _fail("pressing the fader also moved the camera behind it")
|
||||||
|
if mouse_combat.firing:
|
||||||
|
return _fail("the fader's press reached the guns behind it")
|
||||||
|
mouse_combat.pointer_mode = mouse_combat.PointerMode.AUTO
|
||||||
print("the press stops at the fader; the world behind it is untouched")
|
print("the press stops at the fader; the world behind it is untouched")
|
||||||
|
|
||||||
# --- C/D/E. the formation editor -------------------------
|
# --- C/D/E. the formation editor -------------------------
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ extends SceneTree
|
||||||
# B. each supplied office adds exactly one berth, and the cap bites
|
# B. each supplied office adds exactly one berth, and the cap bites
|
||||||
# C. an office that cannot pay loses its vessel, and says so
|
# C. an office that cannot pay loses its vessel, and says so
|
||||||
# D. a restocked office goes back to work without being rebuilt
|
# D. a restocked office goes back to work without being rebuilt
|
||||||
# E. capacity can never outrun the formation's defined positions
|
# E. the wedge has a place for every slot, however far out it is
|
||||||
|
# F. and nothing caps the fleet at the twenty positions the wedge authors
|
||||||
|
|
||||||
var game_state: Node
|
var game_state: Node
|
||||||
var scene: Node
|
var scene: Node
|
||||||
|
|
@ -107,10 +108,35 @@ func _process(delta: float) -> bool:
|
||||||
GameState.BASE_FLEET_SIZE + i + 1])
|
GameState.BASE_FLEET_SIZE + i + 1])
|
||||||
print("3 supplied offices -> %d berths" % game_state.max_fleet_size())
|
print("3 supplied offices -> %d berths" % game_state.max_fleet_size())
|
||||||
|
|
||||||
# --- E. and never more than the formation can seat ------------
|
# --- E. the wedge has a place for every slot ------------------
|
||||||
if game_state.max_fleet_size() > GameState.DEFAULT_FORMATION.size():
|
# The formation used to be the fleet's real ceiling: it defined
|
||||||
return _fail("capacity %d exceeds the %d positions the formation defines" \
|
# twenty positions and capacity clamped itself to them. It now
|
||||||
% [game_state.max_fleet_size(), GameState.DEFAULT_FORMATION.size()])
|
# continues by rule instead, so what is pinned here is that the
|
||||||
|
# rule keeps the authored shape - the far slots are distinct,
|
||||||
|
# and each pair still steps 30 out and 40 back from the last.
|
||||||
|
var authored: int = GameState.DEFAULT_FORMATION.size()
|
||||||
|
var probe: int = authored * 3
|
||||||
|
var seen: Dictionary = {}
|
||||||
|
for i in range(probe):
|
||||||
|
var slot: Vector2 = game_state.formation_slot(i)
|
||||||
|
var key: String = "%.1f,%.1f" % [slot.x, slot.y]
|
||||||
|
if seen.has(key):
|
||||||
|
return _fail("slot %d sits on top of slot %d at %s" % [i, int(seen[key]), key])
|
||||||
|
seen[key] = i
|
||||||
|
for i in range(authored):
|
||||||
|
if not game_state.formation_slot(i).is_equal_approx(GameState.DEFAULT_FORMATION[i]):
|
||||||
|
return _fail("growing the wedge moved authored slot %d to %s" \
|
||||||
|
% [i, game_state.formation_slot(i)])
|
||||||
|
for i in range(authored, probe - 2):
|
||||||
|
var here: Vector2 = game_state.formation_slot(i)
|
||||||
|
var behind: Vector2 = game_state.formation_slot(i + 2)
|
||||||
|
var step: Vector2 = Vector2(GameState.FORMATION_PAIR_STEP.x * signf(here.x),
|
||||||
|
GameState.FORMATION_PAIR_STEP.y)
|
||||||
|
if not (behind - here).is_equal_approx(step):
|
||||||
|
return _fail("slot %d steps %s from slot %d, expected %s" \
|
||||||
|
% [i + 2, behind - here, i, step])
|
||||||
|
print("the wedge continues past its %d authored slots, %d checked and all distinct" \
|
||||||
|
% [authored, probe])
|
||||||
|
|
||||||
scene = load("res://world/Main.tscn").instantiate()
|
scene = load("res://world/Main.tscn").instantiate()
|
||||||
root.add_child(scene)
|
root.add_child(scene)
|
||||||
|
|
@ -187,6 +213,32 @@ func _process(delta: float) -> bool:
|
||||||
return _fail("a supplied office lost its vessel anyway")
|
return _fail("a supplied office lost its vessel anyway")
|
||||||
print("restocked and re-crewed: the office pays and the vessel stays")
|
print("restocked and re-crewed: the office pays and the vessel stays")
|
||||||
|
|
||||||
|
# --- F. and nothing caps the fleet at twenty ------------------
|
||||||
|
# Offices past the wedge's authored positions buy real berths,
|
||||||
|
# and the fleet really flies them: every hull gets a place of
|
||||||
|
# its own rather than piling onto the last defined slot.
|
||||||
|
var target: int = GameState.DEFAULT_FORMATION.size() + 4
|
||||||
|
while game_state.max_fleet_size() < target:
|
||||||
|
var extra: int = game_state.add_building(GameState.BUILDING_FLEET_OFFICE,
|
||||||
|
Vector2(600.0 * float(game_state.max_fleet_size()), 900.0))
|
||||||
|
_stock(extra, 500)
|
||||||
|
if game_state.max_fleet_size() != target:
|
||||||
|
return _fail("%d supplied offices gave %d berths, expected %d" \
|
||||||
|
% [game_state.fleet_offices().size(), game_state.max_fleet_size(), target])
|
||||||
|
while fleet.ships.size() < target:
|
||||||
|
if not fleet.add_ship(GameState.VESSEL_INTERCEPTOR):
|
||||||
|
return _fail("berth %d could not be crewed" % (fleet.ships.size() + 1))
|
||||||
|
var places: Dictionary = {}
|
||||||
|
for i in range(fleet.ships.size()):
|
||||||
|
var offset: Vector2 = fleet.formation_offsets[i]
|
||||||
|
var place: String = "%.1f,%.1f" % [offset.x, offset.y]
|
||||||
|
if places.has(place):
|
||||||
|
return _fail("vessel %d flies the same place as vessel %d" \
|
||||||
|
% [i, int(places[place])])
|
||||||
|
places[place] = i
|
||||||
|
print("%d berths, %d vessels, %d distinct places - no ceiling at %d" \
|
||||||
|
% [target, fleet.ships.size(), places.size(), GameState.DEFAULT_FORMATION.size()])
|
||||||
|
|
||||||
print("TEST_FLEET_GROWTH: PASS")
|
print("TEST_FLEET_GROWTH: PASS")
|
||||||
quit(0)
|
quit(0)
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
1
tests/test_guidance_marker_redraw.gd.uid
Normal file
1
tests/test_guidance_marker_redraw.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bvb4aa4k1702
|
||||||
1
tests/test_main_menu_continue.gd.uid
Normal file
1
tests/test_main_menu_continue.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ctxpryt5rqddw
|
||||||
1
tests/test_save_game.gd.uid
Normal file
1
tests/test_save_game.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ntytohsnghw3
|
||||||
|
|
@ -37,8 +37,8 @@ func _on_fleet_add_requested(vessel_type: String) -> void:
|
||||||
if fleet.add_ship(vessel_type):
|
if fleet.add_ship(vessel_type):
|
||||||
menu.set_status("Added %s" % GameState.vessel_name(vessel_type))
|
menu.set_status("Added %s" % GameState.vessel_name(vessel_type))
|
||||||
else:
|
else:
|
||||||
menu.set_status("Fleet is full — unlock all skills for up to %d slots" \
|
menu.set_status("Fleet is full at %d — every berth past the first is a supplied Fleet Office" \
|
||||||
% GameState.DEFAULT_FORMATION.size())
|
% GameState.max_fleet_size())
|
||||||
|
|
||||||
|
|
||||||
func _on_fleet_remove_requested(vessel_type: String) -> void:
|
func _on_fleet_remove_requested(vessel_type: String) -> void:
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ func _centre() -> Vector2:
|
||||||
|
|
||||||
|
|
||||||
func _slot_screen_pos(i: int) -> Vector2:
|
func _slot_screen_pos(i: int) -> Vector2:
|
||||||
return _centre() + GameState.formation[i] * scale_factor
|
return _centre() + GameState.formation_slot(i) * scale_factor
|
||||||
|
|
||||||
|
|
||||||
func _screen_to_formation(p: Vector2) -> Vector2:
|
func _screen_to_formation(p: Vector2) -> Vector2:
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,28 @@ func handle_mouse_motion(event: InputEventMouseMotion) -> bool:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# ...and listening for those events HERE rather than being handed them by
|
||||||
|
# GameController, which is what #118 did.
|
||||||
|
#
|
||||||
|
# Unhandled input reaches the last nodes in the tree first, and Main - where
|
||||||
|
# that dispatch lives - is the root, so it is served last of all.
|
||||||
|
# MouseCombatController is a child of it and claims every left press to fire
|
||||||
|
# the fleet's guns, which meant a click on the fader shot at the fader
|
||||||
|
# instead of taking hold of it: the knob never moved on a desktop. The UI
|
||||||
|
# layer sits after the combat controllers, so asking for the event here puts
|
||||||
|
# the control the player is actually pointing at ahead of the guns behind it.
|
||||||
|
#
|
||||||
|
# Touch still comes through GameController: nothing competes for a finger,
|
||||||
|
# and the joystick/fire stick ordering it arbitrates is real.
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if handle_mouse_button(event):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event is InputEventMouseMotion:
|
||||||
|
if handle_mouse_motion(event):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
func handle_touch(event: InputEventScreenTouch) -> bool:
|
func handle_touch(event: InputEventScreenTouch) -> bool:
|
||||||
return _press(event.pressed, event.position, event.index)
|
return _press(event.pressed, event.position, event.index)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func spawn_ships(count: int, center: Vector2 = Vector2.ZERO,
|
||||||
spawn_types.append(GameState.VESSEL_INTERCEPTOR)
|
spawn_types.append(GameState.VESSEL_INTERCEPTOR)
|
||||||
var n: int = clamp(spawn_types.size(), 1, GameState.max_fleet_size())
|
var n: int = clamp(spawn_types.size(), 1, GameState.max_fleet_size())
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
var offset: Vector2 = GameState.formation[i]
|
var offset: Vector2 = GameState.formation_slot(i)
|
||||||
var ship: Area2D = ship_scene.instantiate()
|
var ship: Area2D = ship_scene.instantiate()
|
||||||
ship.vessel_type = spawn_types[i]
|
ship.vessel_type = spawn_types[i]
|
||||||
ship.position = center + offset
|
ship.position = center + offset
|
||||||
|
|
@ -178,7 +178,7 @@ func add_ship(vessel_type: String = GameState.VESSEL_INTERCEPTOR) -> bool:
|
||||||
if not GameState.vessel_is_fleet_eligible(vessel_type) \
|
if not GameState.vessel_is_fleet_eligible(vessel_type) \
|
||||||
or ships.size() >= GameState.max_fleet_size():
|
or ships.size() >= GameState.max_fleet_size():
|
||||||
return false
|
return false
|
||||||
var offset: Vector2 = GameState.formation[ships.size()]
|
var offset: Vector2 = GameState.formation_slot(ships.size())
|
||||||
var ship: Area2D = ship_scene.instantiate()
|
var ship: Area2D = ship_scene.instantiate()
|
||||||
# Before add_child, so Ship._ready() builds the right vessel - the shield
|
# Before add_child, so Ship._ready() builds the right vessel - the shield
|
||||||
# ring is created there and only for a shield vessel.
|
# ring is created there and only for a shield vessel.
|
||||||
|
|
@ -222,9 +222,7 @@ func apply_formation() -> void:
|
||||||
# Without this, editing the layout at a command centre (or pivoting it)
|
# Without this, editing the layout at a command centre (or pivoting it)
|
||||||
# only affected ships built later, which read as the edit doing nothing.
|
# only affected ships built later, which read as the edit doing nothing.
|
||||||
for i in range(ships.size()):
|
for i in range(ships.size()):
|
||||||
if i >= GameState.max_fleet_size():
|
formation_offsets[i] = GameState.formation_slot(i)
|
||||||
break
|
|
||||||
formation_offsets[i] = GameState.formation[i]
|
|
||||||
_assign_slot_targets()
|
_assign_slot_targets()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue