292 lines
14 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.

# 文件名: 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 ---")