From a84ce19982bf2a151984c0daee7277c268f3eff1 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sat, 28 Mar 2026 21:31:36 -0700 Subject: [PATCH] =?UTF-8?q?ui(menus):=20=F0=9F=92=84=20Update=20scene=20as?= =?UTF-8?q?sets=20and=20logic=20for=20main=20menu,=20game=20setup,=20and?= =?UTF-8?q?=20throne=20room=20scenes=20with=20new=20UI=20elements,=20anima?= =?UTF-8?q?tions,=20and=20interactions=20(including=20spoils=20variant)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- engine/scenes/menus/game_setup.gd | 86 ++++++++ engine/scenes/menus/game_setup.tscn | 177 ++++++++++++++++ engine/scenes/menus/main_menu.gd | 31 +++ engine/scenes/menus/main_menu.tscn | 17 ++ engine/scenes/menus/throne_room.gd | 211 ++++++++++++++++++++ engine/scenes/menus/throne_room.tscn | 105 ++++++++++ engine/scenes/menus/throne_room_spoils.gd | 66 ++++++ engine/scenes/menus/throne_room_spoils.tscn | 79 ++++++++ 8 files changed, 772 insertions(+) create mode 100644 engine/scenes/menus/game_setup.gd create mode 100644 engine/scenes/menus/game_setup.tscn create mode 100644 engine/scenes/menus/throne_room.gd create mode 100644 engine/scenes/menus/throne_room.tscn create mode 100644 engine/scenes/menus/throne_room_spoils.gd create mode 100644 engine/scenes/menus/throne_room_spoils.tscn diff --git a/engine/scenes/menus/game_setup.gd b/engine/scenes/menus/game_setup.gd new file mode 100644 index 00000000..37414cab --- /dev/null +++ b/engine/scenes/menus/game_setup.gd @@ -0,0 +1,86 @@ +extends Control +## Game setup screen — configure world seed, map type, and map size before starting. + +signal game_started(settings: Dictionary) + +const MAP_TYPE_IDS: Array[String] = [ + "continents", "pangaea", "team_continents", "ffa_cross", "ffa_plus" +] +const MAP_SIZE_IDS: Array[String] = [ + "duel", "tiny", "small", "standard", "large", "huge" +] + + +func _ready() -> void: + _populate_option_button(%MapTypeOption, ["Continents", "Pangaea", "Team Continents", "FFA Cross", "FFA Plus"]) + _populate_option_button(%MapSizeOption, ["Duel (2)", "Tiny (4)", "Small (4)", "Standard (8)", "Large (10)", "Huge (12)"]) + %RandomButton.pressed.connect(_on_random_pressed) + %StartButton.pressed.connect(_on_start_pressed) + %BackButton.pressed.connect(_on_back_pressed) + for i in range(6): + var spinbox: SpinBox = get_node("%%%s" % ("Digit%d" % i)) + spinbox.value_changed.connect(_on_seed_changed.bind()) + _set_seed(42) + + +func _populate_option_button(option: OptionButton, items: Array[String]) -> void: + option.clear() + for item: String in items: + option.add_item(item) + + +func _compose_seed() -> int: + var result: int = 0 + var multipliers: Array[int] = [100000, 10000, 1000, 100, 10, 1] + for i in range(6): + var spinbox: SpinBox = get_node("%%%s" % ("Digit%d" % i)) + result += int(spinbox.value) * multipliers[i] + return max(1, result) + + +func _set_seed(seed_val: int) -> void: + var padded: String = str(seed_val).pad_zeros(6) + for i in range(6): + var spinbox: SpinBox = get_node("%%%s" % ("Digit%d" % i)) + spinbox.value = int(padded[i]) + _update_easter_egg(seed_val) + + +func _on_seed_changed(_value: float = 0.0) -> void: + _update_easter_egg(_compose_seed()) + + +func _update_easter_egg(seed_val: int) -> void: + var egg: Dictionary = DataLoader.get_seed_easter_egg(seed_val) + if egg.is_empty(): + %EasterEggLabel.visible = false + %EasterEggFlavor.visible = false + else: + %EasterEggLabel.text = egg.get("name", "") + %EasterEggLabel.visible = true + %EasterEggFlavor.text = egg.get("flavor", "") + %EasterEggFlavor.visible = true + + +func _on_random_pressed() -> void: + _set_seed(randi_range(1, 999999)) + + +func _on_start_pressed() -> void: + var seed_val: int = _compose_seed() + var map_type_idx: int = %MapTypeOption.selected + var map_size_idx: int = %MapSizeOption.selected + var easter_egg: Dictionary = DataLoader.get_seed_easter_egg(seed_val) + var effective_map_type: String = easter_egg.get("map_type", MAP_TYPE_IDS[map_type_idx]) + var param_overrides: Dictionary = easter_egg.get("param_overrides", {}) + var settings: Dictionary = { + "seed": seed_val, + "map_type": effective_map_type, + "map_size": MAP_SIZE_IDS[map_size_idx], + "param_overrides": param_overrides, + } + game_started.emit(settings) + + +func _on_back_pressed() -> void: + get_tree().change_scene_to_file("res://engine/scenes/menus/main_menu.tscn") diff --git a/engine/scenes/menus/game_setup.tscn b/engine/scenes/menus/game_setup.tscn new file mode 100644 index 00000000..8b3ee420 --- /dev/null +++ b/engine/scenes/menus/game_setup.tscn @@ -0,0 +1,177 @@ +[gd_scene load_steps=3 format=3 uid="uid://d3e4f5g6h7i8j9"] + +[ext_resource type="Script" path="res://engine/scenes/menus/game_setup.gd" id="1"] +[ext_resource type="Theme" path="res://games/age-of-dwarves/ui_theme.tres" id="2_theme"] + +[node name="GameSetup" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("2_theme") +script = ExtResource("1") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.055, 0.04, 0.09, 1) + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(480, 0) +theme_override_constants/separation = 8 + +[node name="TitleLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 36 +theme_override_colors/font_color = Color(0.95, 0.82, 0.3, 1) +text = "New Game" +horizontal_alignment = 1 + +[node name="SubtitleLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +theme_override_colors/font_color = Color(0.7, 0.62, 0.42, 1) +text = "Age of Dwarves" +horizontal_alignment = 1 + +[node name="TitleRule" type="ColorRect" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(0, 1) +color = Color(0.6, 0.45, 0.12, 0.7) + +[node name="SeedSectionLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 11 +theme_override_colors/font_color = Color(0.7, 0.62, 0.42, 1) +text = "WORLD SEED" + +[node name="SeedRow" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Digit0" type="SpinBox" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(42, 36) +min_value = 0.0 +max_value = 9.0 +step = 1.0 + +[node name="Digit1" type="SpinBox" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(42, 36) +min_value = 0.0 +max_value = 9.0 +step = 1.0 + +[node name="Digit2" type="SpinBox" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(42, 36) +min_value = 0.0 +max_value = 9.0 +step = 1.0 + +[node name="Digit3" type="SpinBox" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(42, 36) +min_value = 0.0 +max_value = 9.0 +step = 1.0 + +[node name="Digit4" type="SpinBox" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(42, 36) +min_value = 0.0 +max_value = 9.0 +step = 1.0 + +[node name="Digit5" type="SpinBox" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(42, 36) +min_value = 0.0 +max_value = 9.0 +step = 1.0 + +[node name="RandomButton" type="Button" parent="CenterContainer/VBoxContainer/SeedRow"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(70, 36) +text = "Random" + +[node name="EasterEggLabel" type="Label" parent="CenterContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +theme_override_colors/font_color = Color(0.95, 0.82, 0.3, 1) +visible = false + +[node name="EasterEggFlavor" type="Label" parent="CenterContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 11 +theme_override_colors/font_color = Color(0.65, 0.58, 0.42, 0.8) +autowrap_mode = 3 +visible = false + +[node name="EggSpacer" type="Control" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(0, 8) + +[node name="MapTypeSectionLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 11 +theme_override_colors/font_color = Color(0.7, 0.62, 0.42, 1) +text = "MAP TYPE" + +[node name="MapTypeOption" type="OptionButton" parent="CenterContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(320, 36) + +[node name="MapSizeSectionLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 11 +theme_override_colors/font_color = Color(0.7, 0.62, 0.42, 1) +text = "MAP SIZE" + +[node name="MapSizeOption" type="OptionButton" parent="CenterContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(320, 36) + +[node name="BottomButtons" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="BackButton" type="Button" parent="CenterContainer/VBoxContainer/BottomButtons"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(140, 48) +text = "Back" + +[node name="StartButton" type="Button" parent="CenterContainer/VBoxContainer/BottomButtons"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(200, 48) +theme_override_font_sizes/font_size = 17 +text = "Start Game" diff --git a/engine/scenes/menus/main_menu.gd b/engine/scenes/menus/main_menu.gd index 1a2f68c2..56e2e7a9 100644 --- a/engine/scenes/menus/main_menu.gd +++ b/engine/scenes/menus/main_menu.gd @@ -1,13 +1,17 @@ extends Control ## Main menu screen. Entry point for the game. +const ThroneRoomScene = preload("res://engine/scenes/menus/throne_room.tscn") + @onready var _new_game_button: Button = %NewGameButton @onready var _load_game_button: Button = %LoadGameButton @onready var _options_button: Button = %OptionsButton +@onready var _throne_room_button: Button = %ThroneRoomButton @onready var _quit_button: Button = %QuitButton @onready var _bug_report_button: Button = %BugReportButton @onready var _title_label: Label = %TitleLabel @onready var _subtitle_label: Label = %SubtitleLabel +@onready var _throne_room_bg: Control = $ThroneRoomBg func _ready() -> void: @@ -19,15 +23,26 @@ func _ready() -> void: _new_game_button.text = ThemeVocabulary.lookup("new_game") _load_game_button.text = ThemeVocabulary.lookup("load_game") _options_button.text = ThemeVocabulary.lookup("options") + _throne_room_button.text = ThemeVocabulary.lookup("throne_room") _quit_button.text = ThemeVocabulary.lookup("quit") _new_game_button.pressed.connect(_on_new_game_pressed) _load_game_button.pressed.connect(_on_load_game_pressed) _options_button.pressed.connect(_on_options_pressed) + _throne_room_button.pressed.connect(_on_throne_room_pressed) _quit_button.pressed.connect(_on_quit_pressed) _bug_report_button.pressed.connect(_on_bug_report_pressed) _new_game_button.grab_focus() + call_deferred("_render_throne_room_bg") + if not ThroneRoomProfile.pending_spoils.is_empty(): + call_deferred("_show_pending_spoils") + + +func _render_throne_room_bg() -> void: + var room: Control = ThroneRoomScene.instantiate() + room.show_close = false + _throne_room_bg.add_child(room) func _on_new_game_pressed() -> void: @@ -48,12 +63,28 @@ func _on_options_pressed() -> void: main.change_scene("res://engine/scenes/menus/options.tscn") +func _on_throne_room_pressed() -> void: + var main: Node = get_tree().root.get_node_or_null("Main") + if main != null and main.has_method("push_overlay"): + main.push_overlay("res://engine/scenes/menus/throne_room.tscn") + + func _on_bug_report_pressed() -> void: var main: Node = get_tree().root.get_node_or_null("Main") if main != null and main.has_method("push_overlay"): main.push_overlay("res://engine/scenes/ui/bug_report.tscn") +func _show_pending_spoils() -> void: + var main: Node = get_tree().root.get_node_or_null("Main") + if main == null or not main.has_method("push_overlay"): + return + var spoils: Node = main.push_overlay("res://engine/scenes/menus/throne_room_spoils.tscn") + if spoils != null: + spoils.newly_earned_ids = ThroneRoomProfile.pending_spoils.duplicate() + ThroneRoomProfile.pending_spoils.clear() + + func _on_quit_pressed() -> void: get_tree().quit() diff --git a/engine/scenes/menus/main_menu.tscn b/engine/scenes/menus/main_menu.tscn index a4c74185..56f876ec 100644 --- a/engine/scenes/menus/main_menu.tscn +++ b/engine/scenes/menus/main_menu.tscn @@ -22,6 +22,16 @@ grow_horizontal = 2 grow_vertical = 2 color = Color(0.055, 0.04, 0.09, 1) +[node name="ThroneRoomBg" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +modulate = Color(1, 1, 1, 0.55) +mouse_filter = 2 + [node name="GradientOverlay" type="ColorRect" parent="."] layout_mode = 1 anchors_preset = 15 @@ -95,6 +105,13 @@ custom_minimum_size = Vector2(320, 52) text = "Options" theme_override_font_sizes/font_size = 17 +[node name="ThroneRoomButton" type="Button" parent="CenterContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(320, 52) +text = "Throne Room" +theme_override_font_sizes/font_size = 17 + [node name="BugReportButton" type="Button" parent="CenterContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 diff --git a/engine/scenes/menus/throne_room.gd b/engine/scenes/menus/throne_room.gd new file mode 100644 index 00000000..24d940e0 --- /dev/null +++ b/engine/scenes/menus/throne_room.gd @@ -0,0 +1,211 @@ +extends Control +## Full-screen throne room overlay. Shows accumulated decorations and game records. + +const ThroneRoomBannerScript = preload( + "res://engine/src/modules/empire/throne_room_banner.gd" +) + +## Layer-to-position mapping. Rect2 in normalized (0–1) canvas coordinates. +const LAYER_LAYOUT: Dictionary = { + "background": Rect2(0.00, 0.00, 1.00, 1.00), + "ceiling": Rect2(0.00, 0.00, 1.00, 0.18), + "walls": Rect2(0.00, 0.08, 1.00, 0.70), + "floor": Rect2(0.00, 0.78, 1.00, 0.22), + "throne_backdrop": Rect2(0.30, 0.08, 0.40, 0.28), + "throne": Rect2(0.35, 0.18, 0.30, 0.42), + "trophy_wall": Rect2(0.00, 0.15, 0.22, 0.38), + "honor_display": Rect2(0.78, 0.15, 0.22, 0.38), + "armory": Rect2(0.01, 0.53, 0.20, 0.32), + "forge_display": Rect2(0.01, 0.32, 0.20, 0.22), + "shrine": Rect2(0.79, 0.32, 0.20, 0.32), + "ancestor_monument": Rect2(0.35, 0.56, 0.30, 0.20), + "gemstone_vault": Rect2(0.76, 0.55, 0.23, 0.28), + "brewery": Rect2(0.01, 0.73, 0.20, 0.22), + "map_table": Rect2(0.32, 0.72, 0.36, 0.18), + "garden": Rect2(0.79, 0.72, 0.20, 0.22), + "court": Rect2(0.13, 0.52, 0.14, 0.30), +} + +const PEDESTAL_RECTS: Array = [ + Rect2(0.22, 0.42, 0.12, 0.20), + Rect2(0.44, 0.44, 0.12, 0.20), + Rect2(0.66, 0.42, 0.12, 0.20), +] + +## Layer draw order: background first, foreground items last. +const DRAW_ORDER: Array = [ + "background", "ceiling", "walls", "floor", + "throne_backdrop", "throne", + "forge_display", "armory", "trophy_wall", + "shrine", "honor_display", + "ancestor_monument", "gemstone_vault", + "brewery", "map_table", "garden", "court", +] + +## Set false when used as the main menu background (hides close button). +var show_close: bool = true + +@onready var _room_canvas: Control = $RoomCanvas +@onready var _banner_rack: HBoxContainer = $BannerRack +@onready var _title_label: Label = %TitleLabel +@onready var _close_button: Button = %CloseButton +@onready var _tooltip_panel: PanelContainer = %TooltipPanel +@onready var _tooltip_title: Label = %TooltipTitle +@onready var _tooltip_desc: Label = %TooltipDesc + + +func _ready() -> void: + _title_label.text = ThemeVocabulary.lookup("throne_room") + _close_button.text = ThemeVocabulary.lookup("throne_room_close") + _close_button.visible = show_close + _close_button.pressed.connect(_on_close_pressed) + _tooltip_panel.visible = false + call_deferred("_build_room") + call_deferred("_build_banner_rack") + + +func _build_room() -> void: + var canvas_size: Vector2 = _room_canvas.size + var active: Array[Dictionary] = ThroneRoomProfile.get_active_decorations() + var by_layer: Dictionary = {} + var special_decorations: Array[Dictionary] = [] + for dec: Dictionary in active: + var layer: String = dec.get("layer", "") + if layer.begins_with("special_"): + special_decorations.append(dec) + else: + by_layer[layer] = dec + for layer: String in DRAW_ORDER: + if not by_layer.has(layer): + continue + if LAYER_LAYOUT.has(layer): + _add_decoration_node(by_layer[layer], LAYER_LAYOUT[layer], canvas_size) + _build_wonder_pedestals(canvas_size) + for i: int in special_decorations.size(): + var rect: Rect2 = Rect2(0.88, 0.10 + i * 0.12, 0.11, 0.10) + _add_decoration_node(special_decorations[i], rect, canvas_size) + + +func _build_wonder_pedestals(canvas_size: Vector2) -> void: + var wonder_ids: Array[String] = ThroneRoomProfile.get_wonder_trophies() + if wonder_ids.is_empty(): + return + var all_decorations: Array = DataLoader.get_all_throne_room_decorations() + for i: int in mini(wonder_ids.size(), PEDESTAL_RECTS.size()): + var wonder_id: String = wonder_ids[i] + for dec: Variant in all_decorations: + if dec is Dictionary and dec.get("wonder_id", "") == wonder_id: + _add_decoration_node(dec as Dictionary, PEDESTAL_RECTS[i], canvas_size) + break + + +func _add_decoration_node( + dec: Dictionary, + normalized_rect: Rect2, + canvas_size: Vector2, +) -> void: + var pixel_rect: Rect2 = Rect2( + normalized_rect.position * canvas_size, + normalized_rect.size * canvas_size, + ) + var node: Control = null + var sprite_path: String = dec.get("sprite", "") + if not sprite_path.is_empty(): + var texture: Texture2D = ThemeAssets.load_sprite(sprite_path) + if texture != null: + var tex: TextureRect = TextureRect.new() + tex.texture = texture + tex.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL + tex.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED + tex.mouse_filter = MOUSE_FILTER_PASS + node = tex + if node == null: + node = _build_placeholder(dec, pixel_rect.size) + node.position = pixel_rect.position + node.size = pixel_rect.size + node.mouse_entered.connect(_on_decoration_hovered.bind(dec, node)) + node.mouse_exited.connect(_on_decoration_unhovered) + _room_canvas.add_child(node) + + +func _build_placeholder(dec: Dictionary, slot_size: Vector2) -> Control: + var container: Control = Control.new() + container.mouse_filter = MOUSE_FILTER_PASS + var bg: ColorRect = ColorRect.new() + bg.color = _layer_to_color(dec.get("layer", "")) + bg.color.a = 0.35 + bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + bg.mouse_filter = MOUSE_FILTER_IGNORE + container.add_child(bg) + if slot_size.y > 32: + var lbl: Label = Label.new() + lbl.text = dec.get("name", "") + lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + lbl.add_theme_font_size_override("font_size", clampi(int(slot_size.y / 8), 8, 13)) + lbl.add_theme_color_override("font_color", Color(0.90, 0.82, 0.65, 0.65)) + lbl.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + lbl.mouse_filter = MOUSE_FILTER_IGNORE + container.add_child(lbl) + return container + + +func _build_banner_rack() -> void: + var clan_banner: Control = ThroneRoomBannerScript.build_clan_banner( + ThroneRoomProfile.lifetime_stats.get("victories", 0) + ) + _banner_rack.add_child(clan_banner) + var records: Array = ThroneRoomProfile.game_records.duplicate() + records.sort_custom(func(a: Variant, b: Variant) -> bool: + return (a as Dictionary).get("score", 0) > (b as Dictionary).get("score", 0) + ) + for record: Variant in records: + if record is Dictionary: + _banner_rack.add_child(ThroneRoomBannerScript.build_from_record(record)) + + +func _on_decoration_hovered(dec: Dictionary, node: Control) -> void: + _tooltip_title.text = dec.get("name", "") + _tooltip_desc.text = dec.get("description", "") + _tooltip_panel.visible = true + var viewport_size: Vector2 = get_viewport_rect().size + var pos: Vector2 = node.global_position + Vector2(node.size.x * 0.5, -_tooltip_panel.size.y - 8) + pos.x = clampf(pos.x, 8.0, viewport_size.x - _tooltip_panel.size.x - 8.0) + pos.y = clampf(pos.y, 8.0, viewport_size.y - _tooltip_panel.size.y - 8.0) + _tooltip_panel.global_position = pos + + +func _on_decoration_unhovered() -> void: + _tooltip_panel.visible = false + + +func _layer_to_color(layer: String) -> Color: + if layer in ["background", "walls", "ceiling", "floor"]: + return Color(0.30, 0.22, 0.14) + if layer in ["throne", "throne_backdrop"]: + return Color(0.55, 0.42, 0.10) + if layer in ["forge_display", "armory"]: + return Color(0.50, 0.28, 0.08) + if layer in ["trophy_wall", "honor_display"]: + return Color(0.50, 0.18, 0.10) + if layer in ["shrine", "ancestor_monument"]: + return Color(0.20, 0.28, 0.40) + if layer in ["brewery", "gemstone_vault"]: + return Color(0.30, 0.40, 0.15) + if layer == "map_table": + return Color(0.15, 0.30, 0.35) + if layer == "garden": + return Color(0.12, 0.32, 0.18) + if layer == "court": + return Color(0.40, 0.35, 0.28) + if layer == "pedestal": + return Color(0.55, 0.48, 0.20) + if layer.begins_with("special"): + return Color(0.45, 0.32, 0.55) + return Color(0.25, 0.22, 0.18) + + +func _on_close_pressed() -> void: + var main: Node = get_tree().root.get_node_or_null("Main") + if main != null and main.has_method("pop_overlay"): + main.pop_overlay() diff --git a/engine/scenes/menus/throne_room.tscn b/engine/scenes/menus/throne_room.tscn new file mode 100644 index 00000000..26acccfc --- /dev/null +++ b/engine/scenes/menus/throne_room.tscn @@ -0,0 +1,105 @@ +[gd_scene load_steps=3 format=3 uid="uid://bq9s2n7maf3x"] + +[ext_resource type="Script" path="res://engine/scenes/menus/throne_room.gd" id="1"] +[ext_resource type="Theme" path="res://games/age-of-dwarves/ui_theme.tres" id="2_theme"] + +[node name="ThroneRoom" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("2_theme") +script = ExtResource("1") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.06, 0.04, 0.03, 1) + +[node name="RoomCanvas" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 0 + +[node name="BannerRack" type="HBoxContainer" parent="."] +layout_mode = 3 +anchors_preset = 0 +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 0.0 +offset_left = 12.0 +offset_top = 56.0 +offset_right = -12.0 +offset_bottom = 196.0 +grow_horizontal = 2 +theme_override_constants/separation = 10 + +[node name="Header" type="HBoxContainer" parent="."] +layout_mode = 3 +anchors_preset = 0 +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 0.0 +offset_left = 0.0 +offset_top = 0.0 +offset_right = 0.0 +offset_bottom = 52.0 +grow_horizontal = 2 +theme_override_constants/separation = 0 + +[node name="TitleLabel" type="Label" parent="Header"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 22 +theme_override_colors/font_color = Color(0.95, 0.82, 0.3, 1) +text = "Throne Room" + +[node name="CloseButton" type="Button" parent="Header"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(110, 52) +text = "Return" +theme_override_font_sizes/font_size = 14 + +[node name="TooltipPanel" type="PanelContainer" parent="."] +unique_name_in_owner = true +layout_mode = 0 +offset_left = 0.0 +offset_top = 0.0 +offset_right = 300.0 +offset_bottom = 110.0 +visible = false +mouse_filter = 2 + +[node name="TooltipVBox" type="VBoxContainer" parent="TooltipPanel"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="TooltipTitle" type="Label" parent="TooltipPanel/TooltipVBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +theme_override_colors/font_color = Color(0.95, 0.82, 0.3, 1) +text = "" + +[node name="TooltipDesc" type="Label" parent="TooltipPanel/TooltipVBox"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(280, 0) +theme_override_font_sizes/font_size = 11 +theme_override_colors/font_color = Color(0.72, 0.66, 0.54, 1) +text = "" +autowrap_mode = 2 diff --git a/engine/scenes/menus/throne_room_spoils.gd b/engine/scenes/menus/throne_room_spoils.gd new file mode 100644 index 00000000..cd9d6935 --- /dev/null +++ b/engine/scenes/menus/throne_room_spoils.gd @@ -0,0 +1,66 @@ +extends Control +## Post-game overlay showing newly acquired throne room decorations. +## Receives the list of newly-earned decoration IDs and displays them with +## name and description. Dismissed by the player before returning to main menu. + +@onready var _title_label: Label = %TitleLabel +@onready var _spoils_list: VBoxContainer = %SpoilsList +@onready var _continue_button: Button = %ContinueButton +@onready var _empty_label: Label = %EmptyLabel + +## Set before adding to scene tree. Pass the result of ThroneRoomHarvest.harvest(). +var newly_earned_ids: Array[String] = [] + + +func _ready() -> void: + _title_label.text = ThemeVocabulary.lookup("throne_room_new_acquisitions") + _continue_button.text = ThemeVocabulary.lookup("throne_room_close") + _continue_button.pressed.connect(_on_continue_pressed) + _continue_button.grab_focus() + _populate(newly_earned_ids) + + +func _populate(ids: Array[String]) -> void: + if ids.is_empty(): + _empty_label.visible = true + _spoils_list.visible = false + _empty_label.text = ThemeVocabulary.lookup("throne_room_empty_hint") + return + _empty_label.visible = false + _spoils_list.visible = true + for id: String in ids: + var dec: Dictionary = DataLoader.get_throne_room_decoration(id) + if dec.is_empty(): + continue + var row: HBoxContainer = HBoxContainer.new() + row.theme_override_constants_separation = 12 + var icon: Label = Label.new() + icon.text = "✦" + icon.add_theme_color_override("font_color", Color(0.95, 0.82, 0.3, 1)) + icon.add_theme_font_size_override("font_size", 16) + icon.custom_minimum_size = Vector2(24, 0) + row.add_child(icon) + var text_col: VBoxContainer = VBoxContainer.new() + text_col.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(text_col) + var name_label: Label = Label.new() + name_label.text = dec.get("name", id) + name_label.add_theme_font_size_override("font_size", 15) + name_label.add_theme_color_override("font_color", Color(0.92, 0.86, 0.70, 1)) + text_col.add_child(name_label) + var desc_label: Label = Label.new() + desc_label.text = dec.get("description", "") + desc_label.add_theme_font_size_override("font_size", 11) + desc_label.add_theme_color_override("font_color", Color(0.68, 0.62, 0.50, 1)) + desc_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + text_col.add_child(desc_label) + _spoils_list.add_child(row) + var separator: HSeparator = HSeparator.new() + separator.add_theme_constant_override("separation", 8) + _spoils_list.add_child(separator) + + +func _on_continue_pressed() -> void: + var main: Node = get_tree().root.get_node_or_null("Main") + if main != null and main.has_method("pop_overlay"): + main.pop_overlay() diff --git a/engine/scenes/menus/throne_room_spoils.tscn b/engine/scenes/menus/throne_room_spoils.tscn new file mode 100644 index 00000000..bb7e5657 --- /dev/null +++ b/engine/scenes/menus/throne_room_spoils.tscn @@ -0,0 +1,79 @@ +[gd_scene load_steps=3 format=3 uid="uid://cr8t3p5lbe2m"] + +[ext_resource type="Script" path="res://engine/scenes/menus/throne_room_spoils.gd" id="1"] +[ext_resource type="Theme" path="res://games/age-of-dwarves/ui_theme.tres" id="2_theme"] + +[node name="ThroneRoomSpoils" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("2_theme") +script = ExtResource("1") + +[node name="Backdrop" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.0, 0.0, 0.0, 0.72) +mouse_filter = 2 + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Panel" type="PanelContainer" parent="CenterContainer"] +layout_mode = 2 +custom_minimum_size = Vector2(540, 0) + +[node name="VBox" type="VBoxContainer" parent="CenterContainer/Panel"] +layout_mode = 2 +theme_override_constants/separation = 16 + +[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 26 +theme_override_colors/font_color = Color(0.95, 0.82, 0.3, 1) +text = "New Acquisitions" +horizontal_alignment = 1 + +[node name="TitleRule" type="ColorRect" parent="CenterContainer/Panel/VBox"] +layout_mode = 2 +custom_minimum_size = Vector2(0, 1) +color = Color(0.6, 0.45, 0.12, 0.7) + +[node name="ScrollContainer" type="ScrollContainer" parent="CenterContainer/Panel/VBox"] +layout_mode = 2 +custom_minimum_size = Vector2(0, 320) + +[node name="SpoilsList" type="VBoxContainer" parent="CenterContainer/Panel/VBox/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 8 + +[node name="EmptyLabel" type="Label" parent="CenterContainer/Panel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 13 +theme_override_colors/font_color = Color(0.65, 0.60, 0.48, 0.8) +text = "Your hall awaits its first glory." +horizontal_alignment = 1 +visible = false + +[node name="ContinueButton" type="Button" parent="CenterContainer/Panel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(0, 48) +text = "Return" +theme_override_font_sizes/font_size = 16