Turn dev mode into sandbox menu

This commit is contained in:
Loïc Wisniewski 2026-09-03 20:05:33 +02:00
commit c325501999
19 changed files with 651 additions and 54 deletions

View file

@ -185,9 +185,12 @@ var research_points: int = 0
# generation is static and dependency-free by design. This stays the single
# place to set it - assigning dev_mode anywhere, tests included, reaches
# world generation.
var dev_unlimited_resources: bool = false
var dev_mode: bool = false:
set(value):
dev_mode = value
if not value:
dev_unlimited_resources = false
WORLD_GEN.dev_planets_enabled = value
# Explored-chunk history for the world map's fog-of-war reveal. Lives
@ -763,6 +766,7 @@ func reset_new_game() -> void:
_distorder_drain_progress = 0.0
boost_depleted_notice = false
research_points = 0
dev_unlimited_resources = false
dev_mode = false
explored_chunks.clear()
bank_inventory = INVENTORY.new()
@ -786,12 +790,15 @@ func reset_new_game() -> void:
# the game immediately, without being so large that costs stop registering.
const DEV_RESOURCE_AMOUNT: int = 2000
const DEV_RESEARCH_POINTS: int = 500
const DEV_UNLIMITED_AMOUNT: int = 999999
# A new game stocked for play-testing: every resource banked, research points
# to spend, and one of each planet type next to the spawn (WorldGen reads
# dev_mode for that). Offered by the main menu only when running from Godot -
# see MainMenu.gd - so it cannot be reached in a shipped build.
# A post-prologue game stocked for play-testing: every resource banked,
# research points to spend, and one of each planet type next to the spawn
# (WorldGen reads dev_mode for that). Offered by the main menu only when
# running from Godot - see MainMenu.gd - so it cannot be reached in a shipped
# build. Dev mode is for iterating on the game itself, not replaying the long
# scripted introduction before every check.
#
# Deliberately built ON TOP of reset_new_game() rather than beside it, so a
# dev game is a normal game plus a stock rather than a second, separately
@ -799,12 +806,51 @@ const DEV_RESEARCH_POINTS: int = 500
func start_dev_game() -> void:
reset_new_game()
dev_mode = true
intro_active = false
for resource_type in resource_types():
bank_inventory.set_amount(resource_type, DEV_RESOURCE_AMOUNT)
research_points = DEV_RESEARCH_POINTS
research_points_changed.emit(research_points)
# Development toggles deliberately live beside the state they affect. The
# menu is only a front-end; scene swaps cannot turn an infinite bank off or
# forget which doctrines were exposed.
func set_dev_unlimited_resources(enabled: bool) -> bool:
if enabled and not dev_mode:
return false
dev_unlimited_resources = enabled
if enabled:
_refill_dev_resources()
return true
func _refill_dev_resources() -> void:
for resource_type in resource_types():
if bank_inventory.amount(resource_type) < DEV_UNLIMITED_AMOUNT:
bank_inventory.set_amount(resource_type, DEV_UNLIMITED_AMOUNT)
if research_points < DEV_UNLIMITED_AMOUNT:
research_points = DEV_UNLIMITED_AMOUNT
research_points_changed.emit(research_points)
func dev_unlock_all_skills() -> int:
if not dev_mode:
return 0
var added: int = 0
for node in SKILL_TREE:
var id: String = String(node.id)
if id in unlocked:
continue
unlocked.append(id)
added += 1
# These permanent doctrines affect structures which may already exist.
# Sync once after the batch instead of once per node.
_sync_hub_transporters()
_sync_workshop_drones()
return added
# --- building stock and production queue (#16) -----------------------------
# Since #12, a building cannot just deduct from the bank at the moment of
@ -2755,6 +2801,8 @@ func wave_cleared() -> void:
func _process(delta: float) -> void:
# Runs in the autoload, so mining and transport continue while the player
# is on a planet's surface or nowhere near either.
if dev_mode and dev_unlimited_resources:
_refill_dev_resources()
advance_waves(delta)
_advance_production(delta)
_advance_lab_research(delta)

View file

@ -0,0 +1,28 @@
extends Node2D
# Visual-only phone preview for the editor sandbox panel. It builds only the
# fleet and the menu; loading Main also schedules infinite-world population,
# which is wasted behind this opaque panel and painfully slow in softpipe.
#
# bash tests/screenshot.sh res://tests/_dev_menu_preview.tscn /tmp/dev_menu.png 4 720x1280
const FLEET = preload("res://vessels/Fleet.gd")
const DEV_MENU = preload("res://ui/menus/DevMenu.tscn")
func _ready() -> void:
GameState.start_dev_game()
GameState.dev_unlock_all_skills()
var fleet: Node = FLEET.new()
add_child(fleet)
fleet.spawn_ships(4, Vector2.ZERO, PackedStringArray([
GameState.VESSEL_INTERCEPTOR,
GameState.VESSEL_SHIELD,
GameState.VESSEL_GAS_BOMBER,
GameState.VESSEL_LASER,
]))
var layer := CanvasLayer.new()
add_child(layer)
var menu: Control = DEV_MENU.instantiate()
layer.add_child(menu)
menu.open_for(fleet)

View file

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

View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://tests/_dev_menu_preview.gd" id="1"]
[node name="DevMenuPreview" type="Node2D"]
script = ExtResource("1")

95
tests/test_dev_menu.gd Normal file
View file

@ -0,0 +1,95 @@
extends SceneTree
# The dev button is now a doorway rather than a one-off wave shortcut. This
# exercises the real Main scene so additions and removals prove they affect
# the live fleet, not only GameState bookkeeping.
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
var state: Node
var scene: Node
var fleet: Node
var dev_button: Control
var menu: Control
var stage: int = 0
var elapsed: float = 0.0
func _initialize() -> void:
state = root.get_node("GameState")
state.start_dev_game()
scene = load("res://world/Main.tscn").instantiate()
root.add_child(scene)
current_scene = scene
fleet = scene.get_node("Fleet")
dev_button = scene.get_node("UI/DevMenuButton")
menu = scene.get_node("UI/DevMenu")
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
if elapsed < 0.2:
return false
if state.intro_active:
return _fail("the dev world opened inside the intro")
if fleet.ships.size() != GameState.STARTING_SHIP_COUNT:
return _fail("post-intro dev mode did not start with the normal fleet")
dev_button.button.pressed.emit()
if not menu.visible:
return _fail("the HUD dev button opened no menu")
menu.unlock_button.pressed.emit()
for skill in GameState.SKILL_TREE:
if not state.is_unlocked(String(skill.id)):
return _fail("unlock-all missed %s" % skill.id)
if state.max_fleet_size() != GameState.DEFAULT_FORMATION.size():
return _fail("unlock-all did not expose the complete fleet capacity")
for definition in VESSEL_DEFINITIONS.fleet():
menu.fleet_add_requested.emit(String(definition.definition_id))
for definition in VESSEL_DEFINITIONS.fleet():
if not fleet.has_vessel_type(String(definition.definition_id)):
return _fail("the menu could not add %s" % definition.display_name)
print("dev menu added every fleet-eligible vessel to the live fleet")
var before: int = fleet.ships.size()
menu.fleet_remove_requested.emit(GameState.VESSEL_GAS_BOMBER)
if fleet.ships.size() != before - 1:
return _fail("the dev menu did not remove a vessel")
menu.unlimited_button.pressed.emit()
if not state.dev_unlimited_resources:
return _fail("the unlimited-resources toggle did not turn on")
for resource_type in state.resource_types():
state.bank_inventory.withdraw(resource_type, GameState.DEV_UNLIMITED_AMOUNT)
state.research_points = 0
state._process(0.0)
for resource_type in state.resource_types():
if state.banked_resource(resource_type) != GameState.DEV_UNLIMITED_AMOUNT:
return _fail("unlimited mode did not replenish %s" % resource_type)
if state.research_points != GameState.DEV_UNLIMITED_AMOUNT:
return _fail("unlimited mode did not replenish research points")
print("skill unlock and unlimited resources operate on persistent GameState")
var wave_before: int = state.wave_number
menu.wave_button.pressed.emit()
if menu.visible:
return _fail("starting a wave left the dev menu over the action")
if state.wave_number != wave_before + 1 or not state.wave_pending:
return _fail("the dev menu did not retain the immediate wave tool")
state.reset_new_game()
if state.dev_mode or state.dev_unlimited_resources:
return _fail("dev cheats leaked into a normal new game")
print("TEST_DEV_MENU: PASS")
quit(0)
return true
return false
func _fail(message: String) -> bool:
print("TEST_DEV_MENU: FAIL (%s)" % message)
quit(1)
return true

View file

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

View file

@ -73,6 +73,8 @@ func _process(delta: float) -> bool:
game_state.start_dev_game()
if not game_state.dev_mode:
return _fail("start_dev_game() did not set dev_mode")
if game_state.intro_active:
return _fail("dev mode still starts at the prologue instead of after it")
for resource_type in game_state.resource_types():
var banked: int = game_state.banked_resource(resource_type)
if banked != GameState.DEV_RESOURCE_AMOUNT:
@ -116,6 +118,8 @@ func _process(delta: float) -> bool:
game_state.reset_new_game()
if game_state.dev_mode:
return _fail("starting a normal game after a dev one left dev_mode set")
if game_state.dev_unlimited_resources:
return _fail("starting a normal game kept unlimited resources enabled")
if game_state.rocks != 0 or game_state.research_points != 0:
return _fail("starting a normal game after a dev one kept the dev stock")
print("starting a normal game afterwards clears dev mode entirely")

View file

@ -2,7 +2,7 @@ extends SceneTree
const MENUS: Array[String] = [
"BuildMenu", "CraftMenu", "LabMenu", "TransferMenu", "HangarMenu",
"FormationMenu", "SkillMenu", "PlanningMenu", "GarrisonMenu",
"FormationMenu", "SkillMenu", "PlanningMenu", "GarrisonMenu", "DevMenu",
]

View file

@ -434,7 +434,7 @@ func _process(delta: float) -> bool:
if elapsed < 0.3:
return false
# #140: the objective lives behind a button next to the map's,
# not on the HUD - it used to sit on top of the wave button.
# not on the HUD - it used to sit on top of the dev button.
if panel.visible:
return _fail("the objective panel is open before anyone pressed anything")
if not quest_button.visible:
@ -442,9 +442,9 @@ func _process(delta: float) -> bool:
var map_button: Control = scene.get_node("UI/MapButton")
if quest_button.get_rect().intersects(map_button.get_rect()):
return _fail("the quest button is drawn on top of the map button")
var wave_button: Control = scene.get_node("UI/DevWaveButton")
if quest_button.get_rect().intersects(wave_button.get_rect()):
return _fail("the quest button is drawn on top of the wave button")
var dev_button: Control = scene.get_node("UI/DevMenuButton")
if quest_button.get_rect().intersects(dev_button.get_rect()):
return _fail("the quest button is drawn on top of the dev button")
if absf(quest_button.get_rect().get_center().y - map_button.get_rect().get_center().y) > 1.0:
return _fail("the quest button does not sit beside the map button")
panel.open()
@ -458,7 +458,7 @@ func _process(delta: float) -> bool:
return _fail("the panel does not show the objective")
if panel.progress != Vector2i(0, 5):
return _fail("the panel reads %s, expected 0 of 5" % panel.progress)
print("quest button beside the map, clear of the wave button; panel: \"%s\" - %s (%d/%d)" \
print("quest button beside the map, clear of the dev button; panel: \"%s\" - %s (%d/%d)" \
% [panel.quest_label.text, panel.objective_label.text,
panel.progress.x, panel.progress.y])
panel.close()

View file

@ -224,20 +224,24 @@ func _process(delta: float) -> bool:
return _fail("killing the whole wave did not bring the quiet back")
print("the wave died and the quiet came back")
# --- G. the dev button ---------------------------------------
var dev_button: Control = scene.get_node("UI/DevWaveButton")
# --- G. the dev menu keeps the immediate wave tool -----------
var dev_button: Control = scene.get_node("UI/DevMenuButton")
var dev_menu: Control = scene.get_node("UI/DevMenu")
game_state.dev_mode = false
dev_button._process(0.0)
if dev_button.visible:
return _fail("the dev wave button is offered in a normal game")
return _fail("the dev menu button is offered in a normal game")
game_state.dev_mode = true
dev_button._process(0.0)
if not dev_button.visible:
return _fail("the dev wave button is hidden in dev mode")
var number_before: int = game_state.wave_number
return _fail("the dev menu button is hidden in dev mode")
dev_button.button.pressed.emit()
if not dev_menu.visible:
return _fail("the dev button did not open the dev menu")
var number_before: int = game_state.wave_number
dev_menu.wave_button.pressed.emit()
if game_state.wave_phase != GameState.WAVE_PHASE_ACTIVE:
return _fail("the button left the wave in \"%s\"" % game_state.wave_phase)
return _fail("the menu's wave tool left the wave in \"%s\"" % game_state.wave_phase)
if not game_state.wave_pending:
return _fail("the button turned the wave active without owing it to anyone")
if game_state.wave_number != number_before + 1:
@ -249,7 +253,7 @@ func _process(delta: float) -> bool:
spawner._process(0.0)
if _enemy_count() <= 0:
return _fail("the forced wave produced no enemies")
print("dev button: wave %d on the board at once, %d enemies" \
print("dev menu: wave %d on the board at once, %d enemies" \
% [game_state.wave_number, _enemy_count()])
game_state.dev_mode = false

75
ui/DevMenuButton.gd Normal file
View file

@ -0,0 +1,75 @@
extends Control
# Editor-only entry into the live sandbox. The modal scene owns presentation;
# this small controller owns the boundary to the world nodes it manipulates.
@onready var button: IconButton = $Button
@onready var menu: Control = get_node("../DevMenu")
@onready var fleet: Node = get_node("../../Fleet")
func _ready() -> void:
button.pressed.connect(_on_pressed)
menu.fleet_add_requested.connect(_on_fleet_add_requested)
menu.fleet_remove_requested.connect(_on_fleet_remove_requested)
menu.unlock_skills_requested.connect(_on_unlock_skills_requested)
menu.unlimited_resources_requested.connect(_on_unlimited_resources_requested)
menu.wave_requested.connect(_on_wave_requested)
visible = false
func _process(_delta: float) -> void:
var show_it: bool = GameState.dev_mode and not GameState.intro_active
if show_it != visible:
visible = show_it
if not show_it and menu.visible:
menu.close_menu()
func _on_pressed() -> void:
if not GameState.dev_mode:
return
menu.open_for(fleet)
func _on_fleet_add_requested(vessel_type: String) -> void:
if fleet.add_ship(vessel_type):
menu.set_status("Added %s" % GameState.vessel_name(vessel_type))
else:
menu.set_status("Fleet is full — unlock all skills for up to %d slots" \
% GameState.DEFAULT_FORMATION.size())
func _on_fleet_remove_requested(vessel_type: String) -> void:
var index: int = -1
for i in range(fleet.ships.size()):
if is_instance_valid(fleet.ships[i]) \
and String(fleet.ships[i].vessel_type) == vessel_type:
index = i
break
if index < 0:
menu.set_status("No %s is currently deployed" % GameState.vessel_name(vessel_type))
return
if fleet.store_ship(index) == "":
menu.set_status("Keep at least one vessel in the fleet")
return
menu.set_status("Removed %s" % GameState.vessel_name(vessel_type))
func _on_unlock_skills_requested() -> void:
var unlocked_count: int = GameState.dev_unlock_all_skills()
if unlocked_count > 0:
menu.set_status("Unlocked all skills (%d new)" % unlocked_count)
else:
menu.set_status("All skills are already unlocked")
func _on_unlimited_resources_requested(enabled: bool) -> void:
if not GameState.set_dev_unlimited_resources(enabled):
return
menu.set_status("Unlimited resources %s" % ("enabled" if enabled else "disabled"))
func _on_wave_requested() -> void:
GameState.force_wave_now()
menu.close_menu()

1
ui/DevMenuButton.gd.uid Normal file
View file

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

View file

@ -1,28 +0,0 @@
extends Control
# #110: a dev-only button that puts a wave on the board now.
#
# Waves are otherwise on a fifty-second clock, which makes anything that
# happens during one - the alert, the pathing, buildings taking damage, an
# outpost garrison actually earning its keep - slow to look at even once.
#
# Dev mode is the same gate the main menu already uses for the map editor:
# these are tools for building the game, and a player who installed a build
# has no use for them. The button does not exist outside it.
@onready var button: IconButton = get_node("Button")
func _ready() -> void:
button.pressed.connect(_on_pressed)
visible = false
func _process(_delta: float) -> void:
var show_it: bool = GameState.dev_mode and not GameState.intro_active
if show_it != visible:
visible = show_it
func _on_pressed() -> void:
GameState.force_wave_now()

View file

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

View file

@ -63,6 +63,9 @@ enum Icon {
QUEST,
# #179: a fleet-wide thrust burst, distinct from the Propulser vessel icon.
DASH,
# Development tools: three sliders, used only by the editor-only sandbox
# button. Appended to preserve every serialized icon number above.
DEV_TOOLS,
}
# Size the glyphs were originally drawn against. Every glyph dimension is
@ -323,6 +326,23 @@ func _draw() -> void:
_draw_quest_icon(center, pulse, s)
Icon.DASH:
_draw_dash_icon(center, pulse, s)
Icon.DEV_TOOLS:
_draw_dev_tools_icon(center, pulse, s)
func _draw_dev_tools_icon(center: Vector2, pulse: float, s: float) -> void:
# A compact bank of adjustment sliders. It reads as configuration rather
# than as the old BUILD glyph, which made the wave shortcut look like a
# second construction button.
for row in range(3):
var y: float = (-9.0 + float(row) * 9.0) * s
var left: Vector2 = center + Vector2(-13.0 * s, y)
var right: Vector2 = center + Vector2(13.0 * s, y)
NeonDraw.draw_glowing_line(self, left, right, _active_color(), pulse,
0.45 * s, core_color)
var knob_x: float = [-5.0, 7.0, -1.0][row] * s
NeonDraw.draw_glowing_circle(self, center + Vector2(knob_x, y),
2.7 * s, _active_color(), pulse, core_color)
func _draw_dash_icon(center: Vector2, pulse: float, s: float) -> void:

149
ui/menus/DevMenu.gd Normal file
View file

@ -0,0 +1,149 @@
extends "res://ui/menus/ModalMenu.gd"
signal fleet_add_requested(vessel_type: String)
signal fleet_remove_requested(vessel_type: String)
signal unlock_skills_requested
signal unlimited_resources_requested(enabled: bool)
signal wave_requested
const ICON_BUTTON = preload("res://ui/IconButton.gd")
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
const ROW_HEIGHT: float = 86.0
const DESIGN_SIZE := Vector2(720.0, 1280.0)
@onready var content: Control = $Content
@onready var fleet_summary: Label = $Content/FleetSummary
@onready var fleet_rows: VBoxContainer = $Content/FleetRows
@onready var unlock_button: IconButton = $Content/ToolButtons/UnlockSkills
@onready var unlimited_button: IconButton = $Content/ToolButtons/UnlimitedResources
@onready var wave_button: IconButton = $Content/ToolButtons/StartWave
@onready var unlimited_label: Label = $Content/ToolLabels/UnlimitedLabel
@onready var status: Label = $Content/StatusLabel
var fleet: Node = null
var row_controls: Dictionary = {}
func _ready() -> void:
super._ready()
$Content/CloseButton.pressed.connect(close_menu)
_build_fleet_rows()
unlock_button.pressed.connect(func(): unlock_skills_requested.emit())
unlimited_button.pressed.connect(func():
unlimited_resources_requested.emit(not GameState.dev_unlimited_resources))
wave_button.pressed.connect(func(): wave_requested.emit())
_layout_content()
func _notification(what: int) -> void:
if what == NOTIFICATION_RESIZED and is_node_ready():
_layout_content()
func _layout_content() -> void:
# The project has a landscape reference viewport but is played in portrait
# too. A fixed design canvas keeps touch targets at their authored size and
# scales the complete composition uniformly into either shape.
var factor: float = minf(size.x / DESIGN_SIZE.x, size.y / DESIGN_SIZE.y)
content.scale = Vector2(factor, factor)
content.position = (size - DESIGN_SIZE * factor) * 0.5
func open_for(fleet_node: Node) -> void:
fleet = fleet_node
status.text = ""
open_menu()
refresh()
func set_status(message: String) -> void:
status.text = message
refresh()
func refresh() -> void:
if fleet == null or not is_instance_valid(fleet):
return
var total: int = fleet.ships.size()
fleet_summary.text = "CURRENT FLEET %d / %d" % [total, GameState.max_fleet_size()]
for definition in VESSEL_DEFINITIONS.fleet():
var vessel_type: String = String(definition.definition_id)
var count: int = 0
for ship in fleet.ships:
if is_instance_valid(ship) and String(ship.vessel_type) == vessel_type:
count += 1
var controls: Dictionary = row_controls.get(vessel_type, {})
if controls.is_empty():
continue
(controls.count as Label).text = str(count)
(controls.add as IconButton).enabled = total < GameState.max_fleet_size()
(controls.remove as IconButton).enabled = count > 0 and total > 1
unlock_button.highlighted = _all_skills_unlocked()
unlimited_button.highlighted = GameState.dev_unlimited_resources
unlimited_label.text = "UNLIMITED RESOURCES\n%s" \
% ("ON" if GameState.dev_unlimited_resources else "OFF")
func _all_skills_unlocked() -> bool:
for node in GameState.SKILL_TREE:
if not GameState.is_unlocked(String(node.id)):
return false
return true
func _build_fleet_rows() -> void:
for definition in VESSEL_DEFINITIONS.fleet():
var vessel_type: String = String(definition.definition_id)
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(0.0, ROW_HEIGHT)
row.add_theme_constant_override("separation", 12)
fleet_rows.add_child(row)
var vessel_icon := ICON_BUTTON.new()
vessel_icon.custom_minimum_size = Vector2(76.0, 76.0)
vessel_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
vessel_icon.icon = int(definition.icon)
vessel_icon.glow_color = definition.glow_color
row.add_child(vessel_icon)
var name_label := Label.new()
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
name_label.text = String(definition.display_name)
name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
name_label.add_theme_font_size_override("font_size", 25)
row.add_child(name_label)
var remove_button := ICON_BUTTON.new()
remove_button.custom_minimum_size = Vector2(70.0, 70.0)
remove_button.icon = IconButton.Icon.MINUS
remove_button.glow_color = definition.glow_color
remove_button.pressed.connect(_request_remove.bind(vessel_type))
row.add_child(remove_button)
var count_label := Label.new()
count_label.custom_minimum_size = Vector2(48.0, 0.0)
count_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
count_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
count_label.add_theme_font_size_override("font_size", 31)
row.add_child(count_label)
var add_button := ICON_BUTTON.new()
add_button.custom_minimum_size = Vector2(70.0, 70.0)
add_button.icon = IconButton.Icon.PLUS
add_button.glow_color = definition.glow_color
add_button.pressed.connect(_request_add.bind(vessel_type))
row.add_child(add_button)
row_controls[vessel_type] = {
"count": count_label,
"add": add_button,
"remove": remove_button,
}
func _request_add(vessel_type: String) -> void:
fleet_add_requested.emit(vessel_type)
func _request_remove(vessel_type: String) -> void:
fleet_remove_requested.emit(vessel_type)

1
ui/menus/DevMenu.gd.uid Normal file
View file

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

182
ui/menus/DevMenu.tscn Normal file
View file

@ -0,0 +1,182 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://ui/IconButton.gd" id="icon_button"]
[ext_resource type="Script" path="res://ui/menus/DevMenu.gd" id="menu_script"]
[node name="DevMenu" type="Control" groups=["modal_menus"]]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("menu_script")
[node name="Backdrop" type="ColorRect" parent="."]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.96)
[node name="Content" type="Control" parent="."]
layout_mode = 0
offset_right = 720.0
offset_bottom = 1280.0
[node name="TitleLabel" type="Label" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 34.0
offset_right = -160.0
offset_bottom = 92.0
theme_override_font_sizes/font_size = 38
text = "DEV MENU"
horizontal_alignment = 1
vertical_alignment = 1
[node name="SubtitleLabel" type="Label" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 96.0
offset_right = -24.0
offset_bottom = 138.0
theme_override_colors/font_color = Color(0.55, 0.78, 0.86, 1)
theme_override_font_sizes/font_size = 20
text = "Changes apply to this play-test immediately"
horizontal_alignment = 1
[node name="CloseButton" type="Control" parent="Content"]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -140.0
offset_top = 20.0
offset_right = -24.0
offset_bottom = 136.0
script = ExtResource("icon_button")
icon = 1
glow_color = Color(0.55, 0.6, 1, 1)
[node name="RuleTop" type="ColorRect" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 32.0
offset_top = 151.0
offset_right = -32.0
offset_bottom = 153.0
color = Color(0.25, 0.85, 1, 0.55)
[node name="FleetSummary" type="Label" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 40.0
offset_top = 162.0
offset_right = -40.0
offset_bottom = 202.0
theme_override_font_sizes/font_size = 24
text = "CURRENT FLEET"
horizontal_alignment = 1
vertical_alignment = 1
[node name="FleetRows" type="VBoxContainer" parent="Content"]
anchor_right = 1.0
offset_left = 42.0
offset_top = 208.0
offset_right = -42.0
offset_bottom = 820.0
theme_override_constants/separation = 7
[node name="RuleBottom" type="ColorRect" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 32.0
offset_top = 836.0
offset_right = -32.0
offset_bottom = 838.0
color = Color(0.45, 0.9, 0.55, 0.55)
[node name="ToolsTitle" type="Label" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
offset_top = 850.0
offset_bottom = 888.0
theme_override_font_sizes/font_size = 23
text = "SANDBOX TOOLS"
horizontal_alignment = 1
[node name="ToolButtons" type="HBoxContainer" parent="Content"]
anchor_right = 1.0
offset_left = 70.0
offset_top = 896.0
offset_right = -70.0
offset_bottom = 1012.0
theme_override_constants/separation = 56
alignment = 1
[node name="UnlockSkills" type="Control" parent="Content/ToolButtons"]
custom_minimum_size = Vector2(116, 116)
layout_mode = 2
script = ExtResource("icon_button")
icon = 18
glow_color = Color(0.45, 0.9, 0.55, 1)
[node name="UnlimitedResources" type="Control" parent="Content/ToolButtons"]
custom_minimum_size = Vector2(116, 116)
layout_mode = 2
script = ExtResource("icon_button")
icon = 29
glow_color = Color(1, 0.78, 0.25, 1)
[node name="StartWave" type="Control" parent="Content/ToolButtons"]
custom_minimum_size = Vector2(116, 116)
layout_mode = 2
script = ExtResource("icon_button")
icon = 24
glow_color = Color(1, 0.45, 0.2, 1)
[node name="ToolLabels" type="HBoxContainer" parent="Content"]
anchor_right = 1.0
offset_left = 28.0
offset_top = 1018.0
offset_right = -28.0
offset_bottom = 1098.0
theme_override_constants/separation = 8
alignment = 1
[node name="UnlockLabel" type="Label" parent="Content/ToolLabels"]
custom_minimum_size = Vector2(210, 76)
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "UNLOCK ALL\nSKILLS"
horizontal_alignment = 1
vertical_alignment = 1
[node name="UnlimitedLabel" type="Label" parent="Content/ToolLabels"]
custom_minimum_size = Vector2(210, 76)
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "UNLIMITED RESOURCES\nOFF"
horizontal_alignment = 1
vertical_alignment = 1
[node name="WaveLabel" type="Label" parent="Content/ToolLabels"]
custom_minimum_size = Vector2(210, 76)
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "START ENEMY\nWAVE"
horizontal_alignment = 1
vertical_alignment = 1
[node name="StatusLabel" type="Label" parent="Content"]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 32.0
offset_top = -158.0
offset_right = -32.0
offset_bottom = -40.0
theme_override_colors/font_color = Color(0.55, 0.9, 1, 1)
theme_override_font_sizes/font_size = 23
text = ""
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=44 format=3]
[gd_scene load_steps=45 format=3]
[ext_resource type="PackedScene" path="res://ui/menus/BuildMenu.tscn" id="menu_build"]
[ext_resource type="PackedScene" path="res://ui/menus/HangarMenu.tscn" id="menu_hangar"]
[ext_resource type="PackedScene" path="res://ui/menus/LabMenu.tscn" id="menu_lab"]
@ -8,6 +8,7 @@
[ext_resource type="PackedScene" path="res://ui/menus/PlanningMenu.tscn" id="menu_planning"]
[ext_resource type="PackedScene" path="res://ui/menus/GarrisonMenu.tscn" id="menu_garrison"]
[ext_resource type="PackedScene" path="res://ui/menus/SkillMenu.tscn" id="menu_skill"]
[ext_resource type="PackedScene" path="res://ui/menus/DevMenu.tscn" id="menu_dev"]
[ext_resource type="Script" path="res://world/Starfield.gd" id="1"]
@ -30,7 +31,7 @@
[ext_resource type="Script" path="res://vessels/GarrisonField.gd" id="34"]
[ext_resource type="Script" path="res://ui/NarrationBanner.gd" id="36"]
[ext_resource type="Script" path="res://world/IntroDirector.gd" id="37"]
[ext_resource type="Script" path="res://ui/DevWaveButton.gd" id="38"]
[ext_resource type="Script" path="res://ui/DevMenuButton.gd" id="38"]
[ext_resource type="Script" path="res://logistics/RepairDroneField.gd" id="40"]
[ext_resource type="Script" path="res://ui/BreachOverlay.gd" id="41"]
[ext_resource type="Script" path="res://logistics/TransporterField.gd" id="23"]
@ -236,7 +237,7 @@ offset_bottom = 172
script = ExtResource("10")
icon = 0
[node name="DevWaveButton" type="Control" parent="UI"]
[node name="DevMenuButton" type="Control" parent="UI"]
visible = false
mouse_filter = 2
anchor_left = 1.0
@ -244,15 +245,24 @@ anchor_right = 1.0
offset_left = -140.0
offset_right = -24.0
offset_top = 268.0
offset_bottom = 384.0
offset_bottom = 416.0
script = ExtResource("38")
[node name="Button" type="Control" parent="UI/DevWaveButton"]
[node name="Button" type="Control" parent="UI/DevMenuButton"]
anchor_right = 1.0
anchor_bottom = 1.0
offset_bottom = 116.0
script = ExtResource("10")
icon = 5
glow_color = Color(1, 0.45, 0.2, 1)
icon = 46
glow_color = Color(0.55, 0.9, 1, 1)
[node name="Label" type="Label" parent="UI/DevMenuButton"]
mouse_filter = 2
anchor_right = 1.0
offset_top = 118.0
offset_bottom = 148.0
theme_override_font_sizes/font_size = 18
text = "DEV"
horizontal_alignment = 1
[node name="BreachOverlay" type="ColorRect" parent="UI"]
visible = false
@ -498,6 +508,7 @@ icon = 1
[node name="PlanningMenu" parent="UI" instance=ExtResource("menu_planning")]
[node name="GarrisonMenu" parent="UI" instance=ExtResource("menu_garrison")]
[node name="SkillMenu" parent="UI" instance=ExtResource("menu_skill")]
[node name="DevMenu" parent="UI" instance=ExtResource("menu_dev")]
[node name="QuestPanel" type="Control" parent="UI" groups=["modal_menus"]]
visible = false
mouse_filter = 2