Turn dev mode into sandbox menu
This commit is contained in:
parent
68985316e5
commit
c325501999
19 changed files with 651 additions and 54 deletions
75
ui/DevMenuButton.gd
Normal file
75
ui/DevMenuButton.gd
Normal 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
1
ui/DevMenuButton.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c52odygv78ilg
|
||||
|
|
@ -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()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://blhthsbawd7vg
|
||||
|
|
@ -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
149
ui/menus/DevMenu.gd
Normal 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
1
ui/menus/DevMenu.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bvme310rk20ip
|
||||
182
ui/menus/DevMenu.tscn
Normal file
182
ui/menus/DevMenu.tscn
Normal 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
|
||||
Loading…
Reference in a new issue