extends CanvasLayer class_name WelcomeDialog const GUIDE_PAGES: Array[Dictionary] = [ { "text": "欢迎来到 [color=#3399ff]Datawhale Town[/color]!\n\n这里是开源学习者的家园。在这里,我们一同探索知识,分享成长。\n\n[center]WhaleTown[/center]", "image_path": "res://assets/maps/square/v1/props/bottom_entrance_left_task_props_v2_hd_clean.png", }, { "text": "最新活动:\n\n- 镇长在公会大厅附近接待新伙伴。\n- 码头区域已经开放,可以和码头 NPC 交流。\n- 广场公告板后续会同步线上新闻动态。", "image_path": "res://assets/maps/square/v1/props/center_whale_fountain_v2_hd_clean.png", }, { "text": "操作提示:\n\n- 按 [color=#ffaa00]E[/color] 键可以与 NPC、公告板和信息板互动。\n- 靠近目标后面向它,再按互动键。\n- 按 [color=#ffaa00]Esc[/color] 可以关闭当前界面或结束当前互动。\n- 输入框获得焦点时,角色移动会暂停响应。", "image_path": "res://assets/maps/square/v1/props/bottom_entrance_right_service_props_v2_hd_clean.png", }, ] @onready var panelContainer: PanelContainer = $CenterContainer/PanelContainer @onready var vboxContainer: VBoxContainer = $CenterContainer/PanelContainer/VBoxContainer @onready var titleLabel: Label = $CenterContainer/PanelContainer/VBoxContainer/Header/Title @onready var logoContainer: PanelContainer = $CenterContainer/PanelContainer/VBoxContainer/LogoContainer @onready var bodyText: Label = $CenterContainer/PanelContainer/VBoxContainer/BodyText @onready var actionContainer: CenterContainer = $CenterContainer/PanelContainer/VBoxContainer/ActionContainer var _chatUi: Control var _chatUiPrevMouseFilter: Control.MouseFilter = Control.MOUSE_FILTER_STOP var _chatUiMouseDisabled: bool = false var _guideContentContainer: HBoxContainer var _guideImageRect: TextureRect var _guideImageLabel: Label var _guideContentLabel: RichTextLabel var _guideFooter: HBoxContainer var _guidePrevButton: Button var _guideNextButton: Button var _guideDotsContainer: HBoxContainer var _guideTaskBookButton: Button var _guideCurrentPage: int = 0 var _guideTween: Tween func _ready() -> void: add_to_group("whaletown_escape_dismissible") _disableChatUiMouseInput() var closeButton := find_child("CloseButton", true, false) as Button if closeButton != null: closeButton.pressed.connect(_onClosePressed) var startButton := find_child("StartButton", true, false) as Button if startButton != null: startButton.pressed.connect(_onStartPressed) func _exit_tree() -> void: _restoreChatUiMouseInput() func _input(event: InputEvent) -> void: if get_viewport().is_input_handled(): return if event.is_action_pressed("ui_cancel"): queue_free() get_viewport().set_input_as_handled() func is_escape_dismissible() -> bool: return is_inside_tree() func get_escape_priority() -> int: return 980 func request_escape_close() -> void: queue_free() func _onClosePressed() -> void: queue_free() func _onStartPressed() -> void: _showGuide() func _showGuide() -> void: var taskManager := get_node_or_null("/root/TaskManager") if taskManager != null and taskManager.has_method("report_activity"): taskManager.call("report_activity", "guide_opened") panelContainer.custom_minimum_size = Vector2(720, 560) titleLabel.text = "新人引导手册" logoContainer.visible = false bodyText.visible = false actionContainer.visible = false if _guideContentContainer == null: _buildGuideUi() _guideContentContainer.visible = true _guideFooter.visible = true _guideCurrentPage = 0 _setupGuideDots() _updateGuideUi(false) func _buildGuideUi() -> void: _guideContentContainer = HBoxContainer.new() _guideContentContainer.name = "GuideContentContainer" _guideContentContainer.custom_minimum_size = Vector2(0, 360) _guideContentContainer.size_flags_vertical = Control.SIZE_EXPAND_FILL _guideContentContainer.add_theme_constant_override("separation", 22) vboxContainer.add_child(_guideContentContainer) var imagePanel := PanelContainer.new() imagePanel.name = "ImagePanel" imagePanel.custom_minimum_size = Vector2(322, 0) imagePanel.size_flags_vertical = Control.SIZE_EXPAND_FILL imagePanel.add_theme_stylebox_override("panel", _createImageStyle()) _guideContentContainer.add_child(imagePanel) _guideImageRect = TextureRect.new() _guideImageRect.name = "ImageRect" _guideImageRect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE _guideImageRect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED imagePanel.add_child(_guideImageRect) _guideImageLabel = Label.new() _guideImageLabel.name = "ImageLabel" _guideImageLabel.text = "暂无图片" _guideImageLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _guideImageLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _guideImageLabel.add_theme_color_override("font_color", Color(0.43, 0.48, 0.45, 1.0)) _guideImageLabel.add_theme_font_size_override("font_size", 18) imagePanel.add_child(_guideImageLabel) var textPanel := PanelContainer.new() textPanel.name = "TextPanel" textPanel.size_flags_horizontal = Control.SIZE_EXPAND_FILL textPanel.size_flags_vertical = Control.SIZE_EXPAND_FILL textPanel.add_theme_stylebox_override("panel", _createTextStyle()) _guideContentContainer.add_child(textPanel) _guideContentLabel = RichTextLabel.new() _guideContentLabel.name = "ContentLabel" _guideContentLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL _guideContentLabel.size_flags_vertical = Control.SIZE_EXPAND_FILL _guideContentLabel.bbcode_enabled = true _guideContentLabel.fit_content = true _guideContentLabel.scroll_active = false _guideContentLabel.add_theme_color_override("default_color", Color(0.18, 0.2, 0.18, 1.0)) _guideContentLabel.add_theme_font_size_override("normal_font_size", 21) textPanel.add_child(_guideContentLabel) _guideFooter = HBoxContainer.new() _guideFooter.name = "GuideFooter" _guideFooter.custom_minimum_size = Vector2(0, 58) _guideFooter.alignment = BoxContainer.ALIGNMENT_CENTER _guideFooter.add_theme_constant_override("separation", 22) vboxContainer.add_child(_guideFooter) _guidePrevButton = _createGuideButton("<") _guidePrevButton.name = "PrevButton" _guidePrevButton.pressed.connect(_onGuidePrevPressed) _guideFooter.add_child(_guidePrevButton) _guideDotsContainer = HBoxContainer.new() _guideDotsContainer.name = "DotsContainer" _guideDotsContainer.custom_minimum_size = Vector2(88, 0) _guideDotsContainer.alignment = BoxContainer.ALIGNMENT_CENTER _guideDotsContainer.add_theme_constant_override("separation", 10) _guideFooter.add_child(_guideDotsContainer) _guideNextButton = _createGuideButton(">") _guideNextButton.name = "NextButton" _guideNextButton.pressed.connect(_onGuideNextPressed) _guideFooter.add_child(_guideNextButton) _guideTaskBookButton = _createGuideButton("任务书") _guideTaskBookButton.name = "TaskBookButton" _guideTaskBookButton.custom_minimum_size = Vector2(96, 44) _guideTaskBookButton.pressed.connect(_onGuideTaskBookPressed) _guideFooter.add_child(_guideTaskBookButton) func _createGuideButton(text: String) -> Button: var button := Button.new() button.custom_minimum_size = Vector2(56, 44) button.text = text button.add_theme_color_override("font_color", Color.WHITE) button.add_theme_font_size_override("font_size", 22) button.add_theme_stylebox_override("normal", _createButtonStyle(Color(0.2, 0.43, 0.48, 1.0))) button.add_theme_stylebox_override("hover", _createButtonStyle(Color(0.28, 0.52, 0.55, 1.0))) button.add_theme_stylebox_override("pressed", _createButtonStyle(Color(0.28, 0.52, 0.55, 1.0))) button.add_theme_stylebox_override("disabled", _createButtonStyle(Color(0.72, 0.76, 0.72, 1.0))) return button func _createImageStyle() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.content_margin_left = 10.0 style.content_margin_top = 10.0 style.content_margin_right = 10.0 style.content_margin_bottom = 10.0 style.bg_color = Color(0.875, 0.93, 0.92, 1.0) style.border_width_left = 2 style.border_width_top = 2 style.border_width_right = 2 style.border_width_bottom = 2 style.border_color = Color(0.55, 0.68, 0.64, 1.0) style.corner_radius_top_left = 10 style.corner_radius_top_right = 10 style.corner_radius_bottom_right = 10 style.corner_radius_bottom_left = 10 return style func _createTextStyle() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.content_margin_left = 20.0 style.content_margin_top = 18.0 style.content_margin_right = 20.0 style.content_margin_bottom = 18.0 style.bg_color = Color(1.0, 0.985, 0.935, 1.0) style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(0.78, 0.7, 0.55, 1.0) style.corner_radius_top_left = 10 style.corner_radius_top_right = 10 style.corner_radius_bottom_right = 10 style.corner_radius_bottom_left = 10 return style func _createButtonStyle(color: Color) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.content_margin_left = 16.0 style.content_margin_top = 8.0 style.content_margin_right = 16.0 style.content_margin_bottom = 8.0 style.bg_color = color style.corner_radius_top_left = 8 style.corner_radius_top_right = 8 style.corner_radius_bottom_right = 8 style.corner_radius_bottom_left = 8 return style func _setupGuideDots() -> void: for child in _guideDotsContainer.get_children(): child.queue_free() for index in range(GUIDE_PAGES.size()): var dot := ColorRect.new() dot.custom_minimum_size = Vector2(12, 12) _guideDotsContainer.add_child(dot) func _updateGuideUi(animate: bool = true) -> void: _guidePrevButton.disabled = _guideCurrentPage == 0 _guideNextButton.disabled = _guideCurrentPage == GUIDE_PAGES.size() - 1 var dots := _guideDotsContainer.get_children() for index in range(dots.size()): var dot := dots[index] as ColorRect if dot == null: continue if index == _guideCurrentPage: dot.color = Color(0.15, 0.36, 0.44, 1.0) dot.custom_minimum_size = Vector2(18, 12) else: dot.color = Color(0.72, 0.78, 0.72, 1.0) dot.custom_minimum_size = Vector2(12, 12) if animate: _animateGuideContentChange() else: _setGuideContentImmediate() func _setGuideContentImmediate() -> void: var page := GUIDE_PAGES[_guideCurrentPage] _guideContentLabel.text = page.get("text", "") as String var imagePath := page.get("image_path", "") as String if imagePath != "" and ResourceLoader.exists(imagePath): _guideImageRect.texture = load(imagePath) as Texture2D _guideImageLabel.visible = false else: _guideImageRect.texture = null _guideImageLabel.visible = true _guideImageLabel.text = "暂无图片" func _animateGuideContentChange() -> void: if _guideTween != null and _guideTween.is_valid(): _guideTween.kill() _guideTween = create_tween() _guideTween.tween_property(_guideContentContainer, "modulate:a", 0.0, 0.15) _guideTween.tween_callback(_setGuideContentImmediate) _guideTween.tween_property(_guideContentContainer, "modulate:a", 1.0, 0.15) func _onGuidePrevPressed() -> void: if _guideCurrentPage > 0: _guideCurrentPage -= 1 _updateGuideUi() func _onGuideNextPressed() -> void: if _guideCurrentPage < GUIDE_PAGES.size() - 1: _guideCurrentPage += 1 _updateGuideUi() func _onGuideTaskBookPressed() -> void: var taskBook := get_node_or_null("/root/TaskBookPanel") if taskBook != null and taskBook.has_method("show_panel"): taskBook.call("show_panel") queue_free() func _disableChatUiMouseInput() -> void: var currentScene := get_tree().current_scene if currentScene != null: _chatUi = currentScene.get_node_or_null("UILayer/ChatUI") as Control if _chatUi == null: _chatUi = get_tree().root.find_child("ChatUI", true, false) as Control if _chatUi != null: _chatUiPrevMouseFilter = _chatUi.mouse_filter _chatUi.mouse_filter = Control.MOUSE_FILTER_IGNORE _chatUiMouseDisabled = true func _restoreChatUiMouseInput() -> void: if _chatUiMouseDisabled and is_instance_valid(_chatUi): _chatUi.mouse_filter = _chatUiPrevMouseFilter _chatUiMouseDisabled = false _chatUi = null