370 lines
15 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# theme.gd (更新版 - 对齐 gameplay.gd 逻辑)
extends NinePatchRect
# UI Node References
@onready var list_container = $Option/Info
@onready var previous_button = $Title/previous/Button
@onready var next_button = $Title/next/Button
@onready var title_label = $Title/Title
# Configuration
var items_per_page: int = 7 # 每页显示的选项数量 (保持与原 theme.gd 一致)
# Data Storage
var theme_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点
var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组
var all_enabled_themes: Array = [] # 存储筛选后的题材数据 { "name": String, "data": Dictionary }
# State
var total_pages: int = 0
var current_page_index: int = 0
var selected_theme_key: String = "" # 当前逻辑上选中的项的 Key (用于确认)
var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用
var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置)
func _ready():
# 1. 获取 List 节点 (与原版 theme.gd 类似,但使用 gameplay.gd 的错误处理)
for i in range(1, 7): # 假设最多有 List_6 (与原 theme.gd 匹配)
var list_node = list_container.get_node_or_null("List_" + str(i))
if list_node:
theme_list_nodes.append(list_node)
else:
break # 假设 List 节点是连续的
if theme_list_nodes.is_empty():
printerr("错误:在 theme.tscn 中未找到 List 节点!无法显示选项。")
set_process(false) # 禁用处理因为没有UI元素
return
# 2. 获取每个 List 内的 Option 节点 (与原版 theme.gd 类似)
for list_node in theme_list_nodes:
var current_list_options: Array = []
for i in range(1, items_per_page + 1):
var option_node = list_node.get_node_or_null("option_" + str(i))
if option_node and option_node is Button:
current_list_options.append(option_node)
else:
# 使用 gameplay.gd 的警告格式
push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i])
option_nodes_per_list.append(current_list_options)
# 3. 连接按钮信号 (与 gameplay.gd 一致)
if previous_button:
if not previous_button.pressed.is_connected(_on_previous_pressed):
previous_button.pressed.connect(_on_previous_pressed)
else: printerr("错误:在 theme.tscn 中未找到 Previous 按钮")
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
else: printerr("错误:在 theme.tscn 中未找到 Next 按钮")
# 4. 连接 visibility_changed 信号 (与 gameplay.gd 一致)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# 5. 移除 _ready 中的初始数据加载和显示更新
# 这些逻辑现在完全由 _on_visibility_changed -> reset_display 处理
# --- Visibility Change Handler (与 gameplay.gd 一致) ---
func _on_visibility_changed():
if is_visible_in_tree():
print("Theme: 弹窗变为可见,调用 reset_display()")
reset_display() # 当变为可见时调用 reset
# else: # 可选:隐藏时的清理逻辑
# print("Theme: 弹窗变为隐藏。")
# --- 新增:用于父节点设置初始选中项 (与 gameplay.gd 一致) ---
func set_initial_selection(key: String):
"""
由父节点 (task_development) 在显示此弹窗前调用,
传递当前在 task_info 中选中的题材 Key。
"""
initial_key_to_highlight = key
print("Theme: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight)
# --- Data Processing and Display Logic ---
func _process_theme_data(all_themes_data: Dictionary):
"""【修改】根据从 GameState 获取的题材数据,筛选出启用的题材,并计算总页数。"""
all_enabled_themes.clear()
if all_themes_data.is_empty():
push_warning("警告:接收到的题材数据为空。")
total_pages = 0
return
# 遍历从 GameState 获取的题材字典
for theme_name in all_themes_data:
var theme_info = all_themes_data[theme_name]
# 检查是否为字典且 "enabled" 字段为 true
if typeof(theme_info) == TYPE_DICTIONARY and theme_info.get("enabled", false) == true:
all_enabled_themes.append({"name": theme_name, "data": theme_info})
# else: # Debugging
# print("Theme: 过滤掉未启用或格式错误的题材: ", theme_name)
# 根据筛选结果计算总页数
if items_per_page > 0:
total_pages = ceil(float(all_enabled_themes.size()) / items_per_page)
else:
total_pages = 0
printerr("错误items_per_page 为零,无法计算页数。")
print("Theme: 处理完成,找到 %d 个启用的题材,共 %d 页。" % [all_enabled_themes.size(), total_pages])
func reset_display():
"""【修改】重置状态,从 GameState 重新加载数据,并准备更新显示。"""
print("Theme: 重置显示状态 (reset_display)。")
# 1. 重置本地状态变量
currently_selected_button = null
selected_theme_key = ""
# initial_key_to_highlight 由 set_initial_selection 设置,这里不清空
# 2. 【修改】从 GameState 重新加载并处理数据
if not GameState:
printerr("错误GameState 在 reset_display 期间不可用。")
total_pages = 0
all_enabled_themes.clear()
_update_display() # 即使没有数据也要更新显示(显示空状态)
return
# --- 获取题材数据 ---
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("错误:从 GameState 获取 'task_development' 数据失败。")
total_pages = 0
all_enabled_themes.clear()
else:
# --- 从 task_development 中获取 themes ---
var all_themes_data = task_dev_data.get("themes", {})
_process_theme_data(all_themes_data) # 重新处理数据,计算 total_pages
# 3. 【修改】根据 initial_key_to_highlight 确定初始页面
current_page_index = 0 # 默认为第一页
var found_initial_key = false
if total_pages > 0 and not initial_key_to_highlight.is_empty():
# 查找 initial_key_to_highlight 所在的索引和页面
for i in range(all_enabled_themes.size()):
if all_enabled_themes[i].name == initial_key_to_highlight:
current_page_index = int(floor(float(i) / items_per_page))
found_initial_key = true
print("Theme: 找到需要初始高亮的 Key '%s' 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
break # 找到了,停止循环
if not found_initial_key:
print("Theme: 需要初始高亮的 Key '%s' 未在启用的题材中找到,将显示第一页。" % initial_key_to_highlight)
# 保持 current_page_index 为 0
initial_key_to_highlight = "" # 清空,避免后续 _update_display 尝试高亮不存在的项
# 确保页面索引有效 (与 gameplay.gd 一致)
if total_pages == 0:
current_page_index = 0
elif current_page_index >= total_pages:
current_page_index = total_pages - 1
elif current_page_index < 0:
current_page_index = 0
# 4. 触发显示更新
_update_display()
func _update_display():
"""【更新版】更新列表的可见性并填充当前页面的内容。
现在将“经验”和“成本”显示为整数。
"""
# --- 处理无数据情况 (与 gameplay.gd 一致) ---
if theme_list_nodes.is_empty():
print("Theme: 无可用的 List 节点。")
if title_label: title_label.text = "题材选择"
return
if total_pages == 0:
for list_node in theme_list_nodes: list_node.visible = false
print("Theme: 没有启用的题材选项可供显示。")
if title_label: title_label.text = "题材选择 (无可用)"
if previous_button: previous_button.disabled = true
if next_button: next_button.disabled = true
return
else:
if previous_button: previous_button.disabled = (total_pages <= 1)
if next_button: next_button.disabled = (total_pages <= 1)
# --- 更新页面索引 (循环) (与 gameplay.gd 一致) ---
if total_pages > 0 :
current_page_index = current_page_index % total_pages
if current_page_index < 0:
current_page_index += total_pages
else:
current_page_index = 0
# --- 更新标题 (与 gameplay.gd 一致) ---
if title_label:
var display_page_number = current_page_index + 1
title_label.text = "题材选择 %d/%d" % [display_page_number, total_pages]
# --- 更新 List 可见性 (与 gameplay.gd 一致) ---
for i in range(theme_list_nodes.size()):
theme_list_nodes[i].visible = (i == current_page_index)
# --- 填充可见列表并查找要高亮的按钮 (与 gameplay.gd 类似) ---
var button_to_highlight: Button = null
var first_enabled_button_on_page: Button = null
if current_page_index < option_nodes_per_list.size():
var current_options = option_nodes_per_list[current_page_index]
var start_index = current_page_index * items_per_page
for i in range(current_options.size()):
var option_button = current_options[i]
var item_index = start_index + i
# --- 断开旧信号 ---
if option_button.pressed.is_connected(_on_option_selected):
option_button.pressed.disconnect(_on_option_selected)
if item_index < all_enabled_themes.size():
var theme_entry = all_enabled_themes[item_index]
var theme_name = theme_entry.name
var theme_data = theme_entry.data # 包含 Cost, Experience, Popularity 等
var row = option_button.get_node_or_null("Row")
if row:
# 填充标签文本
var title_label_in_row = row.get_node_or_null("Title")
var exp_label = row.get_node_or_null("Experience")
var pop_label = row.get_node_or_null("Popularity")
var cost_label = row.get_node_or_null("Cost") if row.has_node("Cost") else row.get_node_or_null("cost")
if title_label_in_row: title_label_in_row.text = theme_name
# 【修改】设置经验值,显示为整数
if exp_label:
var experience_value = theme_data.get("Experience", 0.0)
exp_label.text = str(int(experience_value))
# 【保持】设置流行度,保持原始字符串显示
if pop_label:
var popularity_value = theme_data.get("Popularity", "未知")
pop_label.text = str(popularity_value)
# 【修改】设置成本,显示为整数
if cost_label:
var cost_value = theme_data.get("Cost", 0.0)
cost_label.text = str(int(cost_value))
option_button.visible = true
option_button.disabled = false
option_button.set_meta("theme_name", theme_name) # 存储题材名称
# --- 连接新信号 (使用 bind) ---
option_button.pressed.connect(_on_option_selected.bind(theme_name, option_button))
if first_enabled_button_on_page == null:
first_enabled_button_on_page = option_button
if not initial_key_to_highlight.is_empty() and theme_name == initial_key_to_highlight:
button_to_highlight = option_button
# print("Theme: 找到需要初始高亮的按钮: ", button_to_highlight.name) # Debug
else: # 未找到 Row 节点
push_warning("警告:在按钮 %s 中未找到 Row 节点" % option_button.get_path())
option_button.visible = false
option_button.disabled = true
else: # 此槽位没有对应的启用题材数据
option_button.visible = false
option_button.disabled = true
else:
printerr("错误:当前页面索引 %d 超出 option_nodes_per_list 的范围 (大小 %d)" % [current_page_index, option_nodes_per_list.size()])
# --- 设置初始高亮和焦点 (与 gameplay.gd 一致) ---
if button_to_highlight:
_set_initial_highlight(button_to_highlight)
elif first_enabled_button_on_page:
print("Theme: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name])
_set_initial_highlight(first_enabled_button_on_page)
else:
print("Theme: 本页没有可供高亮的按钮。")
currently_selected_button = null
selected_theme_key = ""
# (Helper) 设置初始高亮和焦点 (与 gameplay.gd 一致)
func _set_initial_highlight(button_to_highlight: Button):
if not is_instance_valid(button_to_highlight):
printerr("错误:传递给 _set_initial_highlight 的按钮无效。")
currently_selected_button = null
selected_theme_key = ""
return
# print("Theme: 设置高亮按钮: ", button_to_highlight.name) # Debug
currently_selected_button = button_to_highlight
# --- 修改元数据键名 ---
selected_theme_key = button_to_highlight.get_meta("theme_name", "") # 同时设置 Key
# print("Theme: currently_selected_button 设置为: ", currently_selected_button.name) # Debug
# print("Theme: selected_theme_key 设置为: '", selected_theme_key, "'") # Debug
if currently_selected_button and currently_selected_button.is_inside_tree():
# print("Theme: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug
currently_selected_button.call_deferred("grab_focus")
else:
print("Theme: 按钮无效或不在场景树中,无法获取焦点。")
# --- Signal Handlers ---
# (与 gameplay.gd 一致)
func _on_previous_pressed():
if total_pages > 1:
current_page_index -= 1
_update_display()
# (与 gameplay.gd 一致)
func _on_next_pressed():
if total_pages > 1:
current_page_index += 1
_update_display()
# 【修改】处理选项按钮按下事件 (与 gameplay.gd 一致)
func _on_option_selected(theme_name: String, button_node: Button):
"""当选项按钮被按下时调用。"""
if not is_instance_valid(button_node):
# --- 修改错误信息中的 Key 类型 ---
printerr("错误:在 _on_option_selected 中收到无效按钮节点Key: " + theme_name)
return
if button_node == currently_selected_button:
# --- 双击 (或点击已选中的按钮) ---
print("Theme: 检测到双击或确认点击: ", theme_name)
_confirm_and_close(theme_name) # 确认此选择
else:
# --- 首次点击或点击不同按钮 ---
print("Theme: 选中 (高亮) 题材: ", theme_name)
currently_selected_button = button_node
selected_theme_key = theme_name # 更新 Key
# 更新视觉焦点
button_node.grab_focus()
# 【修改】确认选择并关闭弹窗的逻辑 (与 gameplay.gd 一致)
func _confirm_and_close(key_to_confirm: String):
"""更新父节点并关闭此弹窗。"""
if key_to_confirm.is_empty():
printerr("错误:尝试确认一个空的题材 Key。")
return
print("Theme: 确认选择: ", key_to_confirm)
# --- 更新父节点 (Task Development Popup) ---
var parent_node = get_parent()
if parent_node and parent_node.has_method("update_task_options"):
# --- 修改传递给父节点的字典键名 ---
parent_node.update_task_options({"题材": key_to_confirm})
print("Theme: 已更新父节点的 task_options: 题材 = ", key_to_confirm)
else:
printerr("错误Theme: 父节点未找到或缺少 'update_task_options' 方法。")
# --- 关闭此弹窗 (与 gameplay.gd 一致) ---
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
parent_node._close_child_popup_and_return(self)
else:
printerr("错误Theme: 父节点未找到或缺少 '_close_child_popup_and_return' 方法。")
self.hide() # 备选方案