|
@@ -0,0 +1,123 @@
|
|
|
|
|
+extends Control
|
|
|
|
|
+## Level map: a tile per level with its best stars. Levels up to the saved one can be
|
|
|
|
|
+## played (replays keep the best stars); a few locked ones show what is ahead, and a dot
|
|
|
|
|
+## marks levels where a new power or segment unlocks.
|
|
|
|
|
+
|
|
|
|
|
+const COLUMNS := 5
|
|
|
|
|
+const LOCKED_AHEAD := 10 # locked tiles shown past the saved level
|
|
|
|
|
+const TILE := Vector2(88, 96)
|
|
|
|
|
+
|
|
|
|
|
+var _saved := 0
|
|
|
|
|
+var _unlocks := {} # level -> Array of { name, color }
|
|
|
|
|
+
|
|
|
|
|
+@onready var grid: GridContainer = $Scroll/Margin/Grid
|
|
|
|
|
+@onready var scroll: ScrollContainer = $Scroll
|
|
|
|
|
+@onready var info: Label = $Info
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+func _ready() -> void:
|
|
|
|
|
+ Music.play("menu")
|
|
|
|
|
+ _saved = Progress.load_level()[0]
|
|
|
|
|
+ $Header/Totals/Count.text = str(Progress.total_stars())
|
|
|
|
|
+ $Header/Home.pressed.connect(func() -> void: get_tree().change_scene_to_file("res://scenes/title.tscn"))
|
|
|
|
|
+ _collect_unlocks()
|
|
|
|
|
+ grid.columns = COLUMNS
|
|
|
|
|
+ var last := maxi(_saved, Progress.highest_won()) + LOCKED_AHEAD
|
|
|
|
|
+ var current: Control
|
|
|
|
|
+ for level in last + 1:
|
|
|
|
|
+ var tile := _make_tile(level)
|
|
|
|
|
+ grid.add_child(tile)
|
|
|
|
|
+ if level == _saved:
|
|
|
|
|
+ current = tile
|
|
|
|
|
+ info.text = "Tap a level to play it. Replays keep your best stars."
|
|
|
|
|
+ # Wait for layout, then centre the current level.
|
|
|
|
|
+ await get_tree().process_frame
|
|
|
|
|
+ await get_tree().process_frame
|
|
|
|
|
+ scroll.scroll_vertical = int(current.position.y + TILE.y / 2.0 - scroll.size.y / 2.0)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+func _collect_unlocks() -> void:
|
|
|
|
|
+ for info_dict: Dictionary in PowerUp.KINDS.values() + Block.KINDS.values():
|
|
|
|
|
+ var unlock: int = info_dict.unlock
|
|
|
|
|
+ if unlock > 0:
|
|
|
|
|
+ if not _unlocks.has(unlock):
|
|
|
|
|
+ _unlocks[unlock] = []
|
|
|
|
|
+ _unlocks[unlock].append(info_dict)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+func _make_tile(level: int) -> Button:
|
|
|
|
|
+ var playable := level <= _saved
|
|
|
|
|
+ var tile := Button.new()
|
|
|
|
|
+ tile.custom_minimum_size = TILE
|
|
|
|
|
+ tile.focus_mode = Control.FOCUS_NONE
|
|
|
|
|
+ var style := _style(level)
|
|
|
|
|
+ for state in ["normal", "hover", "pressed", "disabled"]:
|
|
|
|
|
+ tile.add_theme_stylebox_override(state, style)
|
|
|
|
|
+ tile.pressed.connect(_on_tile_pressed.bind(level))
|
|
|
|
|
+
|
|
|
|
|
+ var box := VBoxContainer.new()
|
|
|
|
|
+ box.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
+ box.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
|
|
|
+ box.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
+ tile.add_child(box)
|
|
|
|
|
+ var number := Label.new()
|
|
|
|
|
+ number.text = str(level)
|
|
|
|
|
+ number.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
+ number.add_theme_font_size_override("font_size", 30)
|
|
|
|
|
+ var ink := Params.COLOR_BACK if level == _saved else Color(1, 1, 1, 1.0 if playable else 0.3)
|
|
|
|
|
+ number.add_theme_color_override("font_color", ink)
|
|
|
|
|
+ box.add_child(number)
|
|
|
|
|
+ var stars := Stars.new()
|
|
|
|
|
+ stars.star_size = 15.0
|
|
|
|
|
+ stars.spacing = 3.0
|
|
|
|
|
+ stars.filled = Progress.stars(level)
|
|
|
|
|
+ stars.modulate.a = 1.0 if playable else 0.3
|
|
|
|
|
+ box.add_child(stars)
|
|
|
|
|
+ for child in box.get_children():
|
|
|
|
|
+ child.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
+
|
|
|
|
|
+ if _unlocks.has(level):
|
|
|
|
|
+ var dot := UnlockDot.new()
|
|
|
|
|
+ dot.color = _unlocks[level][0].color
|
|
|
|
|
+ dot.position = Vector2(TILE.x - 16, 8)
|
|
|
|
|
+ tile.add_child(dot)
|
|
|
|
|
+ return tile
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+func _style(level: int) -> StyleBoxFlat:
|
|
|
|
|
+ var style := StyleBoxFlat.new()
|
|
|
|
|
+ style.set_corner_radius_all(14)
|
|
|
|
|
+ style.anti_aliasing_size = 1.5
|
|
|
|
|
+ if level == _saved:
|
|
|
|
|
+ style.bg_color = Params.COLOR_ORANGE
|
|
|
|
|
+ elif level < _saved:
|
|
|
|
|
+ style.bg_color = Color("#3e3f37")
|
|
|
|
|
+ else:
|
|
|
|
|
+ style.bg_color = Color("#30312b")
|
|
|
|
|
+ return style
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+func _on_tile_pressed(level: int) -> void:
|
|
|
|
|
+ if level <= _saved:
|
|
|
|
|
+ Progress.current_level = level
|
|
|
|
|
+ get_tree().change_scene_to_file("res://scenes/game.tscn")
|
|
|
|
|
+ return
|
|
|
|
|
+ var names := []
|
|
|
|
|
+ for unlock: Dictionary in _unlocks.get(level, []):
|
|
|
|
|
+ names.append(unlock.name)
|
|
|
|
|
+ info.text = "Level %d is locked. Reach it to play" % level
|
|
|
|
|
+ info.text += (": %s unlocks there." % ", ".join(names)) if not names.is_empty() else "."
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+## The coloured dot on a tile where something new unlocks.
|
|
|
|
|
+class UnlockDot:
|
|
|
|
|
+ extends Control
|
|
|
|
|
+
|
|
|
|
|
+ var color := Color.WHITE
|
|
|
|
|
+
|
|
|
|
|
+ func _ready() -> void:
|
|
|
|
|
+ mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
+ size = Vector2(10, 10)
|
|
|
|
|
+
|
|
|
|
|
+ func _draw() -> void:
|
|
|
|
|
+ draw_circle(Vector2(5, 5), 5.0, color, true, -1.0, true)
|