388 lines
17 KiB
GDScript
388 lines
17 KiB
GDScript
extends UIPage
|
||
|
||
# UI Node References
|
||
@onready var list_container = $BG/Option/Info
|
||
@onready var previous_button = $BG/Title/previous/Button
|
||
@onready var next_button = $BG/Title/next/Button
|
||
@onready var title_label = $BG/Title/Title
|
||
|
||
# Configuration
|
||
var items_per_page: int = 7 # 每页显示的选项数量
|
||
|
||
# Data Storage
|
||
var gameplay_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点
|
||
var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组
|
||
var all_enabled_gameplays: Array = [] # 存储筛选后的玩法数据 { "name": String, "data": Dictionary }
|
||
|
||
# State
|
||
var total_pages: int = 0
|
||
var current_page_index: int = 0
|
||
var selected_gameplay_key: String = "" # 当前逻辑上选中的项的 Key (用于确认)
|
||
var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用
|
||
var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置)
|
||
|
||
func _ready():
|
||
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
|
||
# 1. 获取 List 节点 (与原版一致)
|
||
for i in range(1, 4): # 检查 List_1, List_2, List_3
|
||
var list_node = list_container.get_node_or_null("List_" + str(i))
|
||
if list_node:
|
||
gameplay_list_nodes.append(list_node)
|
||
else:
|
||
break
|
||
|
||
if gameplay_list_nodes.is_empty():
|
||
printerr("错误:在 gameplay.tscn 中未找到 List 节点!无法显示选项。")
|
||
set_process(false)
|
||
return
|
||
|
||
# 2. 获取每个 List 内的 Option 节点 (与原版一致)
|
||
for list_node in gameplay_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:
|
||
push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i])
|
||
option_nodes_per_list.append(current_list_options)
|
||
|
||
# 3. 连接按钮信号 (与原版一致)
|
||
if previous_button:
|
||
if not previous_button.pressed.is_connected(_on_previous_pressed):
|
||
previous_button.pressed.connect(_on_previous_pressed)
|
||
else: printerr("错误:在 gameplay.tscn 中未找到 Previous 按钮")
|
||
|
||
if next_button:
|
||
if not next_button.pressed.is_connected(_on_next_pressed):
|
||
next_button.pressed.connect(_on_next_pressed)
|
||
else: printerr("错误:在 gameplay.tscn 中未找到 Next 按钮")
|
||
|
||
# 4. 连接 visibility_changed 信号 (与原版一致)
|
||
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 ---
|
||
func _on_visibility_changed():
|
||
if is_visible_in_tree():
|
||
print("Gameplay: 弹窗变为可见,调用 reset_display()")
|
||
reset_display() # 当变为可见时调用 reset
|
||
# else: # 可选:隐藏时的清理逻辑
|
||
# print("Gameplay: 弹窗变为隐藏。")
|
||
|
||
# --- 新增:用于父节点设置初始选中项 ---
|
||
func set_initial_selection(key: String):
|
||
"""
|
||
由父节点 (task_development) 在显示此弹窗前调用,
|
||
传递当前在 task_info 中选中的玩法 Key。
|
||
"""
|
||
initial_key_to_highlight = key
|
||
print("Gameplay: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight)
|
||
|
||
|
||
# --- Data Processing and Display Logic ---
|
||
|
||
func _process_gameplay_data(all_gameplays_data: Dictionary):
|
||
"""【修改】根据从 GameState 获取的玩法数据,筛选出启用的玩法,并计算总页数。"""
|
||
all_enabled_gameplays.clear()
|
||
if all_gameplays_data.is_empty():
|
||
push_warning("警告:接收到的玩法数据为空。")
|
||
total_pages = 0
|
||
return
|
||
|
||
# 遍历从 GameState 获取的玩法字典
|
||
for gameplay_name in all_gameplays_data:
|
||
var gameplay_info = all_gameplays_data[gameplay_name]
|
||
# 检查是否为字典且 "enabled" 字段为 true
|
||
if typeof(gameplay_info) == TYPE_DICTIONARY and gameplay_info.get("enabled", false) == true:
|
||
all_enabled_gameplays.append({"name": gameplay_name, "data": gameplay_info})
|
||
# else: # Debugging: 可以取消注释来看哪些被过滤了
|
||
# print("Gameplay: 过滤掉未启用或格式错误的玩法: ", gameplay_name)
|
||
|
||
# 根据筛选结果计算总页数
|
||
if items_per_page > 0:
|
||
total_pages = ceil(float(all_enabled_gameplays.size()) / items_per_page)
|
||
else:
|
||
total_pages = 0
|
||
printerr("错误:items_per_page 为零,无法计算页数。")
|
||
|
||
print("Gameplay: 处理完成,找到 %d 个启用的玩法,共 %d 页。" % [all_enabled_gameplays.size(), total_pages])
|
||
|
||
func reset_display():
|
||
# 1. 重置本地状态变量
|
||
currently_selected_button = null
|
||
selected_gameplay_key = ""
|
||
|
||
# 1b. 【修改】从 MainController 的共享数据中获取初始应选中的玩法 Key
|
||
# 这个值将覆盖之前可能通过 set_initial_selection 设置的值(如果该方法在 reset_display 前被调用)。
|
||
# 请确保 "initial_selected_gameplay" 是您在 shared_workflow_data 中用于此目的的正确键名。
|
||
initial_key_to_highlight = get_main_data("玩法", "")
|
||
if not initial_key_to_highlight.is_empty():
|
||
print("Gameplay (reset_display): 从 get_main_data 获取到初始高亮 Key: '%s'" % initial_key_to_highlight)
|
||
else:
|
||
# 如果 get_main_data 没有提供有效的 Key (返回空字符串或默认值),
|
||
# initial_key_to_highlight 将是空字符串。
|
||
# 如果之前 set_initial_selection 被调用并设置了一个非空值,该值已被这里的空字符串覆盖。
|
||
# 如果希望保留 set_initial_selection 的值作为后备,逻辑需要调整,
|
||
# 但根据“来自get_main_data”的表述,这里直接取用 get_main_data 的结果。
|
||
print("Gameplay (reset_display): 未从 get_main_data 获取到初始高亮 Key,或 Key 为空。")
|
||
|
||
|
||
# 2. 【保持不变】从 GameState 加载并处理所有可显示的玩法数据
|
||
if not GameState:
|
||
printerr("错误:GameState 在 reset_display 期间不可用。")
|
||
total_pages = 0
|
||
all_enabled_gameplays.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_gameplays.clear()
|
||
else:
|
||
var all_gameplays_data = task_dev_data.get("gameplays", {})
|
||
_process_gameplay_data(all_gameplays_data) # 重新处理数据,计算 total_pages
|
||
|
||
# 3. 【逻辑不变,但使用来自 get_main_data 的 initial_key_to_highlight】
|
||
# 根据 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_gameplays.size()):
|
||
if all_enabled_gameplays[i].name == initial_key_to_highlight:
|
||
current_page_index = int(floor(float(i) / items_per_page))
|
||
found_initial_key = true
|
||
print("Gameplay: 找到需要初始高亮的 Key '%s' (来自 get_main_data 或后续处理) 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
|
||
break # 找到了,停止循环
|
||
if not found_initial_key:
|
||
print("Gameplay: 需要初始高亮的 Key '%s' (来自 get_main_data) 未在启用的玩法中找到,将显示第一页。" % initial_key_to_highlight)
|
||
# 保持 current_page_index 为 0
|
||
initial_key_to_highlight = "" # 清空,避免后续 _update_display 尝试高亮不存在的项
|
||
|
||
# 确保页面索引有效
|
||
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():
|
||
"""【更新版】更新列表的可见性并填充当前页面的内容。
|
||
现在将“经验”和“成本”显示为整数。
|
||
"""
|
||
# --- 处理无数据情况 ---
|
||
if gameplay_list_nodes.is_empty():
|
||
print("Gameplay: 无可用的 List 节点。")
|
||
if title_label: title_label.text = "玩法选择"
|
||
return
|
||
|
||
if total_pages == 0:
|
||
for list_node in gameplay_list_nodes: list_node.visible = false
|
||
print("Gameplay: 没有启用的玩法选项可供显示。")
|
||
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)
|
||
|
||
|
||
# --- 更新页面索引 (循环) ---
|
||
# 注意: reset_display 设置了初始页, 取模主要用于翻页
|
||
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
|
||
|
||
|
||
# --- 更新标题 ---
|
||
if title_label:
|
||
var display_page_number = current_page_index + 1 # 显示给用户的页码从1开始
|
||
title_label.text = "玩法选择 %d/%d" % [display_page_number, total_pages]
|
||
|
||
# --- 更新 List 可见性 ---
|
||
# 确保只有一个 List 节点是可见的,即当前页对应的 List
|
||
for i in range(gameplay_list_nodes.size()):
|
||
gameplay_list_nodes[i].visible = (i == current_page_index)
|
||
|
||
# --- 填充可见列表并查找要高亮的按钮 ---
|
||
var button_to_highlight: Button = null
|
||
var first_enabled_button_on_page: Button = null # 如果 initial_key 不在本页,则使用此按钮
|
||
|
||
# 检查当前页索引是否在有效范围内
|
||
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 # 计算当前页数据在 all_enabled_gameplays 中的起始索引
|
||
|
||
# 遍历当前页的按钮槽位
|
||
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_gameplays.size():
|
||
# 获取对应的玩法数据
|
||
var gameplay_entry = all_enabled_gameplays[item_index]
|
||
var gameplay_name = gameplay_entry.name
|
||
var gameplay_data = gameplay_entry.data # 这是包含 Cost, Experience, Popularity 等信息的字典
|
||
|
||
# 获取按钮内部用于显示信息的 Row 节点
|
||
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")
|
||
# 查找成本标签,兼容 "Cost" 和 "cost" 两种可能的节点名
|
||
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 = gameplay_name
|
||
|
||
# 【修改】设置经验值,显示为整数
|
||
if exp_label:
|
||
var experience_value = gameplay_data.get("Experience", 0.0) # 获取原始值
|
||
exp_label.text = str(int(experience_value)) # 转为整数再转为字符串
|
||
|
||
# 【保持】设置流行度,保持原始字符串显示
|
||
if pop_label:
|
||
var popularity_value = gameplay_data.get("Popularity", "未知") # 获取原始值
|
||
pop_label.text = str(popularity_value) # 直接转为字符串
|
||
|
||
# 【修改】设置成本,显示为整数
|
||
if cost_label:
|
||
var cost_value = gameplay_data.get("Cost", 0.0) # 获取原始值
|
||
cost_label.text = str(int(cost_value)) # 转为整数再转为字符串
|
||
|
||
# 设置按钮可见、可用,并存储玩法名称到元数据
|
||
option_button.visible = true
|
||
option_button.disabled = false
|
||
option_button.set_meta("gameplay_name", gameplay_name)
|
||
|
||
# --- 连接新信号 (使用 bind) ---
|
||
# 传递玩法名称和按钮节点本身给处理函数
|
||
option_button.pressed.connect(_on_option_selected.bind(gameplay_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 gameplay_name == initial_key_to_highlight:
|
||
button_to_highlight = option_button
|
||
# print("Gameplay: 找到需要初始高亮的按钮: ", 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()])
|
||
|
||
# --- 设置初始高亮和焦点 ---
|
||
# 优先高亮 initial_key_to_highlight 对应的按钮
|
||
if button_to_highlight:
|
||
# print("Gameplay: 尝试高亮指定按钮: ", button_to_highlight.name) # Debug
|
||
_set_initial_highlight(button_to_highlight)
|
||
# 如果没有指定按钮或指定按钮不在当前页,则高亮当前页的第一个可用按钮
|
||
elif first_enabled_button_on_page:
|
||
print("Gameplay: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name])
|
||
_set_initial_highlight(first_enabled_button_on_page)
|
||
# 如果当前页没有任何可用按钮
|
||
else:
|
||
print("Gameplay: 本页没有可供高亮的按钮。")
|
||
currently_selected_button = null # 确保没有按钮被标记为选中
|
||
selected_gameplay_key = "" # 确保没有 Key 被标记为选中
|
||
|
||
# (Helper) 设置初始高亮和焦点 (与原版类似,增加日志)
|
||
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_gameplay_key = ""
|
||
return
|
||
|
||
# print("Gameplay: 设置高亮按钮: ", button_to_highlight.name) # Debug
|
||
currently_selected_button = button_to_highlight
|
||
selected_gameplay_key = button_to_highlight.get_meta("gameplay_name", "") # 同时设置 Key
|
||
# print("Gameplay: currently_selected_button 设置为: ", currently_selected_button.name) # Debug
|
||
# print("Gameplay: selected_gameplay_key 设置为: '", selected_gameplay_key, "'") # Debug
|
||
|
||
# 延迟调用 grab_focus 确保按钮已准备好接收焦点
|
||
if currently_selected_button and currently_selected_button.is_inside_tree():
|
||
# print("Gameplay: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug
|
||
currently_selected_button.call_deferred("grab_focus")
|
||
else:
|
||
print("Gameplay: 按钮无效或不在场景树中,无法获取焦点。")
|
||
|
||
|
||
# --- Signal Handlers ---
|
||
|
||
func _on_previous_pressed():
|
||
if total_pages > 1: # 仅当有多页时才翻页
|
||
current_page_index -= 1
|
||
# 循环由 _update_display 处理
|
||
_update_display()
|
||
|
||
func _on_next_pressed():
|
||
if total_pages > 1: # 仅当有多页时才翻页
|
||
current_page_index += 1
|
||
# 循环由 _update_display 处理
|
||
_update_display()
|
||
|
||
# 【修改】处理选项按钮按下事件 (与原版类似,增加日志)
|
||
func _on_option_selected(gameplay_name: String, button_node: Button):
|
||
"""当选项按钮被按下时调用。"""
|
||
if not is_instance_valid(button_node):
|
||
printerr("错误:在 _on_option_selected 中收到无效按钮节点,Key: " + gameplay_name)
|
||
return
|
||
|
||
if button_node == currently_selected_button:
|
||
# --- 双击 (或点击已选中的按钮) ---
|
||
print("Gameplay: 检测到双击或确认点击: ", gameplay_name)
|
||
_confirm_and_close(gameplay_name) # 确认此选择
|
||
else:
|
||
# --- 首次点击或点击不同按钮 ---
|
||
print("Gameplay: 选中 (高亮) 玩法: ", gameplay_name)
|
||
currently_selected_button = button_node
|
||
selected_gameplay_key = gameplay_name # 更新 Key,以便后续可能的确认
|
||
|
||
# 更新视觉焦点
|
||
button_node.grab_focus()
|
||
|
||
# 【修改】确认选择并关闭弹窗的逻辑
|
||
func _confirm_and_close(key_to_confirm: String):
|
||
"""更新父节点并关闭此弹窗。"""
|
||
if key_to_confirm.is_empty():
|
||
printerr("错误:尝试确认一个空的玩法 Key。")
|
||
return
|
||
|
||
print("Gameplay: 确认选择: ", key_to_confirm)
|
||
|
||
set_main_data("玩法", key_to_confirm)
|
||
go_next()
|