338 lines
13 KiB
GDScript
338 lines
13 KiB
GDScript
extends CanvasLayer
|
|
|
|
const PANEL_SIZE := Vector2(980, 860)
|
|
const TEXT_COLOR := Color(0.10, 0.18, 0.27)
|
|
const MUTED_COLOR := Color(0.38, 0.48, 0.58)
|
|
const ACCENT_COLOR := Color(0.10, 0.49, 0.85)
|
|
const COIN_COLOR := Color(0.94, 0.58, 0.08)
|
|
const WELCOME_DIALOG_SCENE: PackedScene = preload("res://scenes/ui/welcome_dialog.tscn")
|
|
const WELCOME_DIALOG_NAME := "WelcomeDialog"
|
|
|
|
var _root: Control
|
|
var _panel: PanelContainer
|
|
var _content: VBoxContainer
|
|
var _status_label: Label
|
|
var _is_open: bool = false
|
|
|
|
func _ready() -> void:
|
|
layer = 32
|
|
add_to_group("whaletown_escape_dismissible")
|
|
_build_ui()
|
|
_connect_services()
|
|
|
|
func _exit_tree() -> void:
|
|
var event_system := get_node_or_null("/root/EventSystem")
|
|
if event_system != null:
|
|
event_system.call("disconnect_event", EventNames.HUD_TASK_BOOK_REQUESTED, _on_task_book_requested, self)
|
|
var task_manager := get_node_or_null("/root/TaskManager")
|
|
if task_manager != null:
|
|
var board_callback := Callable(self, "_on_board_changed")
|
|
if task_manager.has_signal("board_changed") and task_manager.is_connected("board_changed", board_callback):
|
|
task_manager.disconnect("board_changed", board_callback)
|
|
var failed_callback := Callable(self, "_on_board_failed")
|
|
if task_manager.has_signal("board_failed") and task_manager.is_connected("board_failed", failed_callback):
|
|
task_manager.disconnect("board_failed", failed_callback)
|
|
|
|
func show_panel() -> void:
|
|
_is_open = true
|
|
_root.visible = true
|
|
var task_manager := get_node_or_null("/root/TaskManager")
|
|
if task_manager != null:
|
|
if task_manager.has_method("is_loaded") and bool(task_manager.call("is_loaded")):
|
|
var board_variant: Variant = task_manager.call("get_board")
|
|
if board_variant is Dictionary:
|
|
_render_board(board_variant as Dictionary)
|
|
if task_manager.has_method("refresh_board"):
|
|
task_manager.call("refresh_board")
|
|
|
|
func hide_panel() -> void:
|
|
_is_open = false
|
|
_root.visible = false
|
|
|
|
func is_escape_dismissible() -> bool:
|
|
return _is_open
|
|
|
|
func get_escape_priority() -> int:
|
|
return 760
|
|
|
|
func request_escape_close() -> void:
|
|
hide_panel()
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if not _is_open or get_viewport().is_input_handled():
|
|
return
|
|
if event.is_action_pressed("ui_cancel"):
|
|
hide_panel()
|
|
get_viewport().set_input_as_handled()
|
|
|
|
func _connect_services() -> void:
|
|
var event_system := get_node_or_null("/root/EventSystem")
|
|
if event_system != null:
|
|
event_system.call("connect_event", EventNames.HUD_TASK_BOOK_REQUESTED, _on_task_book_requested, self)
|
|
var task_manager := get_node_or_null("/root/TaskManager")
|
|
if task_manager != null:
|
|
if task_manager.has_signal("board_changed"):
|
|
task_manager.connect("board_changed", _on_board_changed)
|
|
if task_manager.has_signal("board_failed"):
|
|
task_manager.connect("board_failed", _on_board_failed)
|
|
|
|
func _build_ui() -> void:
|
|
_root = Control.new()
|
|
_root.name = "TaskBookRoot"
|
|
_root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
_root.visible = false
|
|
add_child(_root)
|
|
|
|
var dim := ColorRect.new()
|
|
dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
dim.color = Color(0.02, 0.05, 0.09, 0.68)
|
|
dim.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
dim.gui_input.connect(func(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton and (event as InputEventMouseButton).pressed:
|
|
hide_panel()
|
|
)
|
|
_root.add_child(dim)
|
|
|
|
_panel = PanelContainer.new()
|
|
_panel.custom_minimum_size = PANEL_SIZE
|
|
_panel.set_anchors_preset(Control.PRESET_CENTER)
|
|
_panel.offset_left = -PANEL_SIZE.x * 0.5
|
|
_panel.offset_top = -PANEL_SIZE.y * 0.5
|
|
_panel.offset_right = PANEL_SIZE.x * 0.5
|
|
_panel.offset_bottom = PANEL_SIZE.y * 0.5
|
|
_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
_panel.add_theme_stylebox_override("panel", _panel_style(Color(0.96, 0.985, 1.0, 1.0), 28))
|
|
_root.add_child(_panel)
|
|
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 28)
|
|
margin.add_theme_constant_override("margin_top", 24)
|
|
margin.add_theme_constant_override("margin_right", 28)
|
|
margin.add_theme_constant_override("margin_bottom", 24)
|
|
_panel.add_child(margin)
|
|
var layout := VBoxContainer.new()
|
|
layout.add_theme_constant_override("separation", 16)
|
|
margin.add_child(layout)
|
|
layout.add_child(_build_header())
|
|
_status_label = Label.new()
|
|
_status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
_status_label.add_theme_color_override("font_color", MUTED_COLOR)
|
|
_status_label.add_theme_font_size_override("font_size", 15)
|
|
layout.add_child(_status_label)
|
|
var scroll := ScrollContainer.new()
|
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
|
layout.add_child(scroll)
|
|
_content = VBoxContainer.new()
|
|
_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_content.add_theme_constant_override("separation", 12)
|
|
scroll.add_child(_content)
|
|
var footer := Label.new()
|
|
footer.text = "[Esc] 关闭任务书"
|
|
footer.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
footer.add_theme_color_override("font_color", MUTED_COLOR)
|
|
footer.add_theme_font_size_override("font_size", 15)
|
|
layout.add_child(footer)
|
|
|
|
func _build_header() -> Control:
|
|
var header := PanelContainer.new()
|
|
header.custom_minimum_size = Vector2(0, 96)
|
|
header.add_theme_stylebox_override("panel", _panel_style(ACCENT_COLOR, 20))
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 24)
|
|
margin.add_theme_constant_override("margin_top", 14)
|
|
margin.add_theme_constant_override("margin_right", 16)
|
|
margin.add_theme_constant_override("margin_bottom", 14)
|
|
header.add_child(margin)
|
|
var row := HBoxContainer.new()
|
|
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
margin.add_child(row)
|
|
var title_box := VBoxContainer.new()
|
|
title_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
row.add_child(title_box)
|
|
var title := Label.new()
|
|
title.text = "鲸镇任务书"
|
|
title.add_theme_color_override("font_color", Color.WHITE)
|
|
title.add_theme_font_size_override("font_size", 30)
|
|
title_box.add_child(title)
|
|
var subtitle := Label.new()
|
|
subtitle.text = "完成探索、学习与社交任务,领取鲸币奖励"
|
|
subtitle.add_theme_color_override("font_color", Color(0.88, 0.95, 1.0, 1.0))
|
|
subtitle.add_theme_font_size_override("font_size", 16)
|
|
title_box.add_child(subtitle)
|
|
var guide := Button.new()
|
|
guide.text = "新人引导"
|
|
guide.focus_mode = Control.FOCUS_NONE
|
|
guide.custom_minimum_size = Vector2(104, 42)
|
|
guide.add_theme_stylebox_override("normal", _panel_style(Color(1, 1, 1, 0.18), 14))
|
|
guide.add_theme_stylebox_override("hover", _panel_style(Color(1, 1, 1, 0.30), 14))
|
|
guide.add_theme_color_override("font_color", Color.WHITE)
|
|
guide.pressed.connect(_on_guide_pressed)
|
|
row.add_child(guide)
|
|
var close := Button.new()
|
|
close.text = "关闭"
|
|
close.focus_mode = Control.FOCUS_NONE
|
|
close.custom_minimum_size = Vector2(84, 42)
|
|
close.add_theme_stylebox_override("normal", _panel_style(Color(1, 1, 1, 0.18), 14))
|
|
close.add_theme_stylebox_override("hover", _panel_style(Color(1, 1, 1, 0.30), 14))
|
|
close.add_theme_color_override("font_color", Color.WHITE)
|
|
close.pressed.connect(hide_panel)
|
|
row.add_child(close)
|
|
return header
|
|
|
|
func _on_task_book_requested(_data: Dictionary = {}) -> void:
|
|
show_panel()
|
|
|
|
func _on_guide_pressed() -> void:
|
|
hide_panel()
|
|
var root: Window = get_tree().root
|
|
var dialog: Node = root.get_node_or_null(WELCOME_DIALOG_NAME)
|
|
if dialog == null:
|
|
dialog = WELCOME_DIALOG_SCENE.instantiate()
|
|
dialog.name = WELCOME_DIALOG_NAME
|
|
root.add_child(dialog)
|
|
if dialog.has_method("_showGuide"):
|
|
dialog.call_deferred("_showGuide")
|
|
|
|
func _on_board_changed(board: Dictionary) -> void:
|
|
if _is_open:
|
|
_render_board(board)
|
|
|
|
func _on_board_failed(message: String) -> void:
|
|
if _is_open and is_instance_valid(_status_label):
|
|
_status_label.text = message
|
|
_status_label.add_theme_color_override("font_color", Color(0.86, 0.28, 0.24))
|
|
|
|
func _render_board(board: Dictionary) -> void:
|
|
_clear_content()
|
|
if board.is_empty():
|
|
_status_label.text = "正在读取任务书…"
|
|
return
|
|
_status_label.add_theme_color_override("font_color", MUTED_COLOR)
|
|
var cycle_variant: Variant = board.get("weekly_cycle", {})
|
|
var cycle: Dictionary = cycle_variant as Dictionary if cycle_variant is Dictionary else {}
|
|
_status_label.text = "周常重置:%s" % _format_reset(str(cycle.get("ends_at", "")))
|
|
_add_section("新人主线", "可自由完成,首次购买皮肤为可跳过任务")
|
|
_add_tasks(board.get("newbie_tasks", []))
|
|
_add_section("本周任务", "四项全部完成后可领取额外 200 鲸币")
|
|
_add_tasks(board.get("weekly_tasks", []))
|
|
var bonus_variant: Variant = board.get("weekly_bonus", {})
|
|
if bonus_variant is Dictionary:
|
|
_content.add_child(_build_task_card(bonus_variant as Dictionary))
|
|
|
|
func _add_section(title_text: String, description: String) -> void:
|
|
var section := VBoxContainer.new()
|
|
section.add_theme_constant_override("separation", 2)
|
|
var title := Label.new()
|
|
title.text = title_text
|
|
title.add_theme_color_override("font_color", TEXT_COLOR)
|
|
title.add_theme_font_size_override("font_size", 23)
|
|
section.add_child(title)
|
|
var subtitle := Label.new()
|
|
subtitle.text = description
|
|
subtitle.add_theme_color_override("font_color", MUTED_COLOR)
|
|
subtitle.add_theme_font_size_override("font_size", 15)
|
|
section.add_child(subtitle)
|
|
_content.add_child(section)
|
|
|
|
func _add_tasks(tasks_variant: Variant) -> void:
|
|
if not (tasks_variant is Array):
|
|
return
|
|
for task_variant: Variant in tasks_variant as Array:
|
|
if task_variant is Dictionary:
|
|
_content.add_child(_build_task_card(task_variant as Dictionary))
|
|
|
|
func _build_task_card(task: Dictionary) -> Control:
|
|
var completed: bool = bool(task.get("completed", false))
|
|
var claimed: bool = bool(task.get("claimed", false))
|
|
var optional: bool = bool(task.get("optional", false))
|
|
var card := PanelContainer.new()
|
|
card.add_theme_stylebox_override("panel", _panel_style(Color(0.90, 0.96, 1.0, 0.92) if completed else Color.WHITE, 18))
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 18)
|
|
margin.add_theme_constant_override("margin_top", 14)
|
|
margin.add_theme_constant_override("margin_right", 18)
|
|
margin.add_theme_constant_override("margin_bottom", 14)
|
|
card.add_child(margin)
|
|
var row := HBoxContainer.new()
|
|
row.add_theme_constant_override("separation", 14)
|
|
margin.add_child(row)
|
|
var state := Label.new()
|
|
state.custom_minimum_size = Vector2(30, 0)
|
|
state.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
state.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
state.text = "✓" if claimed else ("●" if completed else "○")
|
|
state.add_theme_color_override("font_color", Color(0.17, 0.66, 0.41) if completed else MUTED_COLOR)
|
|
state.add_theme_font_size_override("font_size", 24)
|
|
row.add_child(state)
|
|
var detail := VBoxContainer.new()
|
|
detail.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
detail.add_theme_constant_override("separation", 4)
|
|
row.add_child(detail)
|
|
var title := Label.new()
|
|
title.text = "%s%s" % [str(task.get("title", "任务")), "(可跳过)" if optional else ""]
|
|
title.add_theme_color_override("font_color", TEXT_COLOR)
|
|
title.add_theme_font_size_override("font_size", 19)
|
|
detail.add_child(title)
|
|
var description := Label.new()
|
|
description.text = str(task.get("description", ""))
|
|
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
description.add_theme_color_override("font_color", MUTED_COLOR)
|
|
description.add_theme_font_size_override("font_size", 15)
|
|
detail.add_child(description)
|
|
var progress: int = int(task.get("progress", 0))
|
|
var target: int = max(1, int(task.get("target", 1)))
|
|
var progress_label := Label.new()
|
|
progress_label.text = "进度 %d / %d" % [min(progress, target), target]
|
|
progress_label.add_theme_color_override("font_color", ACCENT_COLOR)
|
|
progress_label.add_theme_font_size_override("font_size", 14)
|
|
detail.add_child(progress_label)
|
|
var reward := Label.new()
|
|
reward.text = "+%d 鲸币" % int(task.get("reward", 0))
|
|
reward.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
reward.add_theme_color_override("font_color", COIN_COLOR)
|
|
reward.add_theme_font_size_override("font_size", 17)
|
|
row.add_child(reward)
|
|
var action := Button.new()
|
|
action.focus_mode = Control.FOCUS_NONE
|
|
action.custom_minimum_size = Vector2(88, 42)
|
|
if claimed:
|
|
action.text = "已领取"
|
|
action.disabled = true
|
|
elif completed:
|
|
action.text = "领取"
|
|
action.pressed.connect(func() -> void:
|
|
var task_manager := get_node_or_null("/root/TaskManager")
|
|
if task_manager != null and task_manager.has_method("claim_task"):
|
|
task_manager.call("claim_task", str(task.get("id", "")))
|
|
)
|
|
else:
|
|
action.text = "进行中"
|
|
action.disabled = true
|
|
action.add_theme_stylebox_override("normal", _panel_style(ACCENT_COLOR if completed and not claimed else Color(0.80, 0.85, 0.89), 12))
|
|
action.add_theme_stylebox_override("hover", _panel_style(Color(0.06, 0.39, 0.71), 12))
|
|
action.add_theme_color_override("font_color", Color.WHITE)
|
|
row.add_child(action)
|
|
return card
|
|
|
|
func _clear_content() -> void:
|
|
for child: Node in _content.get_children():
|
|
child.queue_free()
|
|
|
|
func _format_reset(value: String) -> String:
|
|
if value.is_empty():
|
|
return "下周一 00:00"
|
|
return value.replace("T", " ").left(16)
|
|
|
|
func _panel_style(color: Color, radius: int) -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = color
|
|
style.corner_radius_top_left = radius
|
|
style.corner_radius_top_right = radius
|
|
style.corner_radius_bottom_left = radius
|
|
style.corner_radius_bottom_right = radius
|
|
style.shadow_color = Color(0.04, 0.13, 0.22, 0.24)
|
|
style.shadow_size = 16
|
|
style.shadow_offset = Vector2(0, 6)
|
|
return style
|