388 lines
17 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.

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()