292 lines
14 KiB
GDScript
292 lines
14 KiB
GDScript
# 文件名: strategy.gd
|
||
# 脚本作用: 控制策略选择界面的显示和交互 (使用主题资源处理焦点视觉)
|
||
# 版本:已根据方案 A 修改,使用通用 GameState 接口,从父节点获取初始选择
|
||
|
||
extends NinePatchRect
|
||
|
||
# --- 节点引用 ---
|
||
@onready var options_container: VBoxContainer = $Option/Options/List_1
|
||
@onready var explanation_label: Label = $Option/Explaination
|
||
|
||
# --- 内部状态 ---
|
||
var option_buttons: Array[Button] = [] # 存储选项按钮节点的数组
|
||
var highlighted_button: Button = null # 当前逻辑上选中的按钮 (用于区分首次点击和确认点击)
|
||
var current_strategy_data: Dictionary = {} # 存储从 GameState 获取的原始策略数据
|
||
var initial_selection_key: String = "" # 由父节点传入的初始/当前选择的策略 Key (中文名)
|
||
|
||
# --- 初始化 ---
|
||
func _ready() -> void:
|
||
add_to_group(str(name)) # 便于父节点或其他系统查找
|
||
|
||
# --- 获取按钮引用 ---
|
||
option_buttons.clear()
|
||
for child in options_container.get_children():
|
||
if child is Button:
|
||
option_buttons.append(child)
|
||
# 断开旧连接以防万一 (虽然理论上 _ready 只执行一次)
|
||
if child.is_connected("pressed", _on_option_button_pressed):
|
||
child.disconnect("pressed", _on_option_button_pressed)
|
||
|
||
# --- 连接 GameState 通用值变化信号 ---
|
||
# 用于响应策略数据可能的动态变化 (如解锁新策略)
|
||
if GameState and not GameState.state_value_changed.is_connected(_on_game_state_value_changed):
|
||
GameState.state_value_changed.connect(_on_game_state_value_changed)
|
||
|
||
# --- 连接 visibility_changed 信号 ---
|
||
# 确保每次界面可见时都刷新列表内容
|
||
if not is_connected("visibility_changed", _on_visibility_changed):
|
||
visibility_changed.connect(_on_visibility_changed)
|
||
|
||
# _update_strategy_list() 不再在 _ready 中调用,移至 _on_visibility_changed
|
||
|
||
# --- 父节点调用接口 ---
|
||
# 由父节点 (task_development.gd) 在显示此弹窗前调用,传入当前的临时选择
|
||
func set_initial_selection(strategy_key: String) -> void:
|
||
initial_selection_key = strategy_key
|
||
print("Strategy: Initial selection key set by parent: '", initial_selection_key, "'")
|
||
# 注意:这里只存储值,实际的高亮逻辑仍在 _update_strategy_list 中处理
|
||
|
||
# --- 信号处理 ---
|
||
|
||
# 处理可见性变化的函数
|
||
func _on_visibility_changed() -> void:
|
||
print("Strategy: _on_visibility_changed called, visible = ", is_visible_in_tree())
|
||
# 当节点变为可见时,更新列表并设置初始焦点
|
||
if is_visible_in_tree():
|
||
print("Strategy popup became visible. Updating list and setting focus...")
|
||
# 确保在更新列表前,initial_selection_key 已被父节点设置
|
||
_update_strategy_list()
|
||
# else: # 可选:如果需要在隐藏时做清理,可以在这里添加逻辑
|
||
# print("Strategy popup became hidden.")
|
||
|
||
# 当 GameState 的任何值发生变化时调用
|
||
func _on_game_state_value_changed(key: String, new_value) -> void:
|
||
# 检查是否是 task_development 数据发生了变化,这可能影响可用策略
|
||
# 注意:这是一个比较宽泛的检查。如果 task_development 下其他不相关数据变化也会触发刷新。
|
||
if key == "task_development":
|
||
# 并且确保当前界面是可见的,避免在后台刷新不可见的UI
|
||
if is_visible_in_tree():
|
||
print("Strategy: Detected change in 'task_development', refreshing list...")
|
||
# 重新执行列表更新,它会使用最新的 GameState 数据
|
||
# 并尝试根据 initial_selection_key (由父节点设定,理论上不变) 恢复高亮
|
||
_update_strategy_list()
|
||
|
||
# --- 核心 UI 更新逻辑 ---
|
||
|
||
# 更新策略列表UI的函数
|
||
func _update_strategy_list() -> void:
|
||
# 0. 重置界面逻辑状态 (清除上次的高亮和说明)
|
||
_reset_highlight_state()
|
||
|
||
# 1. 从 GameState 获取最新的 task_development 数据
|
||
if not GameState:
|
||
printerr("Strategy: GameState not available during _update_strategy_list.")
|
||
for button in option_buttons: button.visible = false
|
||
explanation_label.text = "错误:无法访问游戏状态。"
|
||
return
|
||
|
||
var task_dev_data: Dictionary = GameState.get_value("task_development", {})
|
||
if task_dev_data.is_empty():
|
||
printerr("Strategy: Failed to get 'task_development' data from GameState.")
|
||
for button in option_buttons: button.visible = false
|
||
explanation_label.text = "错误:无法获取开发数据。"
|
||
return
|
||
|
||
# 提取策略子字典
|
||
current_strategy_data = task_dev_data.get("strategies", {})
|
||
if current_strategy_data.is_empty():
|
||
printerr("Strategy: 'strategies' data not found or empty in task_development.")
|
||
for button in option_buttons: button.visible = false
|
||
explanation_label.text = "没有可用的开发策略。"
|
||
return
|
||
|
||
# 2. 筛选出已启用的策略
|
||
var enabled_strategies = []
|
||
for strategy_name in current_strategy_data: # 遍历字典的键 (策略中文名)
|
||
var details: Dictionary = current_strategy_data[strategy_name]
|
||
# 确保 details 是字典并且 'enabled' 键存在且为 true
|
||
if typeof(details) == TYPE_DICTIONARY and details.get("enabled", false) == true:
|
||
enabled_strategies.append({"name": strategy_name, "details": details})
|
||
|
||
# 如果需要固定顺序,可能需要先对 enabled_strategies 排序 (基于某个字段或预设顺序)
|
||
# enabled_strategies.sort_custom(func(a, b): return a["name"] < b["name"]) # 示例:按名称排序
|
||
|
||
# 3. 填充按钮
|
||
var strategy_index: int = 0
|
||
var first_enabled_button: Button = null # 用于没有初始选择时的默认高亮
|
||
|
||
# --- 确保每次更新时正确设置按钮状态和信号连接 ---
|
||
for button in option_buttons: # 遍历场景中预设的按钮
|
||
button.visible = false # 默认隐藏
|
||
button.disabled = true # 默认禁用
|
||
# 断开旧的 pressed 信号连接,防止重复触发
|
||
if button.is_connected("pressed", _on_option_button_pressed):
|
||
button.disconnect("pressed", _on_option_button_pressed)
|
||
# 清除可能存在的旧元数据
|
||
if button.has_meta("strategy_name"):
|
||
button.remove_meta("strategy_name")
|
||
|
||
# 使用筛选出的启用策略数据填充按钮
|
||
while strategy_index < enabled_strategies.size() and strategy_index < option_buttons.size():
|
||
var button: Button = option_buttons[strategy_index]
|
||
var strategy_data: Dictionary = enabled_strategies[strategy_index]
|
||
var strategy_name: String = strategy_data["name"]
|
||
var strategy_details: Dictionary = strategy_data["details"]
|
||
|
||
# 获取按钮内部的标签节点
|
||
var title_label: Label = button.find_child("Title", true, false) as Label
|
||
var cost_label: Label = button.find_child("Cost", true, false) as Label
|
||
|
||
if title_label and cost_label:
|
||
# 设置策略名称
|
||
title_label.text = strategy_name
|
||
|
||
# 设置成本显示 (格式化为百分比)
|
||
var cost_value = strategy_details.get("Cost", null) # 尝试获取 Cost 值
|
||
if typeof(cost_value) == TYPE_FLOAT or typeof(cost_value) == TYPE_INT:
|
||
var percentage: int = int(cost_value * 100)
|
||
cost_label.text = "+%d%%" % percentage # GDScript 中 % 需要转义
|
||
else:
|
||
cost_label.text = "N/A" # 如果 Cost 不存在或不是数字
|
||
|
||
# 将策略名称存储在按钮元数据中,方便点击时获取
|
||
button.set_meta("strategy_name", strategy_name)
|
||
button.visible = true # 显示按钮
|
||
button.disabled = false # 启用按钮
|
||
# 重新连接 pressed 信号,并绑定按钮自身作为参数
|
||
button.pressed.connect(_on_option_button_pressed.bind(button))
|
||
|
||
# 记录第一个可用的按钮,作为没有初始选择时的备选高亮目标
|
||
if first_enabled_button == null:
|
||
first_enabled_button = button
|
||
else:
|
||
printerr("Strategy: 在按钮 '%s' 下未找到 Title 或 Cost 标签。" % button.name)
|
||
# 保持按钮隐藏和禁用
|
||
|
||
strategy_index += 1
|
||
|
||
# 4. 设置初始逻辑选中状态和焦点
|
||
print("--- Updating Strategy List ---")
|
||
print("Initial selection key from parent: '", initial_selection_key, "'")
|
||
var initial_highlight_button: Button = null
|
||
|
||
# 尝试根据父节点传入的 initial_selection_key 找到对应的按钮
|
||
if not initial_selection_key.is_empty():
|
||
print("Searching for matching button for key: '", initial_selection_key, "'")
|
||
for button in option_buttons:
|
||
# 检查按钮是否可见、启用,并且其元数据中的策略名与初始选择键匹配
|
||
if button.visible and not button.disabled:
|
||
var meta_name = button.get_meta("strategy_name", "")
|
||
# print(" Checking button: '", button.name, "' with meta: '", meta_name, "'") # Debug
|
||
if meta_name == initial_selection_key:
|
||
print(" Match found!: ", button.name)
|
||
initial_highlight_button = button
|
||
break # 找到后即可停止搜索
|
||
|
||
# 如果没有找到匹配的按钮 (例如初始选择为空,或对应的策略不再可用)
|
||
if initial_highlight_button == null:
|
||
print("No match found for initial key or key was empty. Trying first enabled button.")
|
||
initial_highlight_button = first_enabled_button # 使用第一个可用的按钮作为默认高亮
|
||
|
||
# 如果找到了要高亮的按钮 (无论是匹配到的还是第一个可用的)
|
||
if initial_highlight_button:
|
||
print("Button to initially highlight: ", initial_highlight_button.name)
|
||
# 设置逻辑高亮状态并尝试让其获得焦点
|
||
_set_initial_highlight(initial_highlight_button)
|
||
else:
|
||
# 如果连第一个可用的按钮都没有 (例如所有策略都未启用)
|
||
print("No button available to highlight.")
|
||
explanation_label.text = "没有可用的开发策略。" # 更新说明文本
|
||
print("--- Finished Updating Strategy List ---")
|
||
|
||
|
||
# --- 交互处理 ---
|
||
|
||
# 处理选项按钮点击事件的函数
|
||
func _on_option_button_pressed(button_node: Button) -> void:
|
||
# 从按钮元数据获取对应的策略名称
|
||
var strategy_name: String = button_node.get_meta("strategy_name", "")
|
||
if strategy_name.is_empty():
|
||
printerr("Strategy: 无法从按钮 %s 获取 strategy_name 元数据。" % button_node.name)
|
||
return
|
||
|
||
# 检查点击的按钮是否就是当前已逻辑高亮的按钮
|
||
if button_node == highlighted_button:
|
||
# --- 第二次点击 (确认选择) ---
|
||
print("Strategy: Confirmed selection - ", strategy_name)
|
||
|
||
# 1. 通知父节点更新临时选择状态
|
||
var parent_node = get_parent() # 获取父节点 (应该是 task_development)
|
||
if parent_node and parent_node.has_method("update_task_options"):
|
||
# 调用父节点的接口,传递更新信息 (使用中文键名)
|
||
parent_node.update_task_options({"开发策略": strategy_name})
|
||
print("Strategy: Updated parent task options with 开发策略 = ", strategy_name)
|
||
else:
|
||
printerr("Strategy: Parent node not found or missing 'update_task_options' method.")
|
||
|
||
# 2. 请求父节点关闭当前弹窗并返回主界面
|
||
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
|
||
parent_node._close_child_popup_and_return(self)
|
||
else:
|
||
# 作为备选方案,如果找不到父节点或方法,直接隐藏自己
|
||
printerr("Strategy: Parent node not found or missing '_close_child_popup_and_return' method. Hiding self.")
|
||
self.hide()
|
||
|
||
else:
|
||
# --- 第一次点击 或 点击了不同的按钮 (更新逻辑选择和说明) ---
|
||
print("Strategy: Logically selected - ", strategy_name)
|
||
# 更新当前逻辑高亮的按钮引用
|
||
highlighted_button = button_node
|
||
|
||
# 更新底部的说明文本
|
||
# 确保 current_strategy_data 是最新的 (理论上 _update_strategy_list 刚更新过)
|
||
if current_strategy_data.has(strategy_name):
|
||
var details: Dictionary = current_strategy_data[strategy_name]
|
||
explanation_label.text = details.get("Explaination", "暂无说明。") # 使用 .get 提供默认值
|
||
else:
|
||
# 如果因为某些原因数据不同步,显示错误
|
||
explanation_label.text = "错误:找不到策略详情。"
|
||
printerr("Strategy: Could not find details for strategy '", strategy_name, "' in current_strategy_data.")
|
||
|
||
# 注意:此时按钮的视觉焦点可能还未改变,依赖于 Button 主题的 focus 样式
|
||
# _set_initial_highlight 内部会处理 grab_focus
|
||
|
||
# --- 辅助函数 ---
|
||
|
||
# (辅助函数) 重置界面逻辑高亮状态
|
||
func _reset_highlight_state() -> void:
|
||
highlighted_button = null # 清除逻辑高亮引用
|
||
explanation_label.text = "" # 清空说明文本
|
||
|
||
# (辅助函数) 设置初始逻辑选中按钮并赋予焦点
|
||
func _set_initial_highlight(button_to_highlight: Button) -> void:
|
||
print("--- Setting Initial Highlight ---")
|
||
if not is_instance_valid(button_to_highlight):
|
||
printerr("Strategy: Invalid button passed to _set_initial_highlight.")
|
||
return
|
||
|
||
print("Button received: ", button_to_highlight.name)
|
||
# 设置逻辑高亮引用
|
||
highlighted_button = button_to_highlight
|
||
print("highlighted_button variable set to: ", highlighted_button.name)
|
||
|
||
# 更新说明文本
|
||
var strategy_name = highlighted_button.get_meta("strategy_name", "")
|
||
if not strategy_name.is_empty() and current_strategy_data.has(strategy_name):
|
||
var details: Dictionary = current_strategy_data[strategy_name]
|
||
explanation_label.text = details.get("Explaination", "暂无说明。")
|
||
elif not strategy_name.is_empty():
|
||
explanation_label.text = "错误:找不到策略详情。"
|
||
printerr("Strategy: Could not find details for highlighted strategy '", strategy_name, "'")
|
||
else:
|
||
explanation_label.text = "错误:无法获取策略名称。" # 如果连元数据都没有
|
||
|
||
# 尝试让按钮获得实际输入焦点 (使用 call_deferred 确保在安全的时机执行)
|
||
# 这会让按钮应用其主题中的 "focus" 样式
|
||
if highlighted_button.is_inside_tree() and highlighted_button.visible and not highlighted_button.disabled:
|
||
print("Attempting call_deferred('grab_focus') for: ", highlighted_button.name)
|
||
highlighted_button.call_deferred("grab_focus")
|
||
else:
|
||
print("Button '%s' not valid, not in tree, hidden, or disabled. Cannot grab focus." % highlighted_button.name)
|
||
print("--- Finished Setting Initial Highlight ---")
|