Files
gplan-2/game/presentation/views/curio_pick_overlay.gd
T

228 lines
9.8 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.
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-10-01(迭代 10 奖励重构批——level-clear.md v2 三选一选择面承载)
# 提升三选一覆盖层(模态):非末关通关的选择停点呈现——部分回血反馈 + 三卡点选 + 确认兑付 +
# 同调位替换子交互。高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发;
# 视图零核心引用。交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。
# 状态机:pick(三卡点选→确认)→ [replace(满位 N+1 选 N)] → result(兑付反馈→继续)。
class_name CurioPickOverlay
extends Control
signal pick_confirmed(item_id: String)
signal replace_chosen(discard_index: int) # -1 = 放弃新件不获取
signal continue_pressed
signal refresh_requested # 三选一刷新意图(迭代 11 第①批 T5b——P1 甲 X/Y 双源;重抽归组合根)
var _label: Label
var _choice_box: HBoxContainer # 三卡区(pick 态——M7 bcard 竖卡)
var _replace_box: VBoxContainer # 替换子面板(replace 态)
var _confirm_btn: Button # 确认钮(点选后点亮——两段式第二拍)
var _continue_btn: Button # 继续钮(兑付反馈态唯一出口)
var _refresh_btn: Button # 刷新钮(迭代 11 第①批 T5b——余量>0 才显示;M7 升顶条图标钮)
var _refresh_badge: Label # M7 余量角标(图标钮右上——demo ⑥ 同构)
var _refresh_l: Label # 刷新余量读数(X 优先 + Y 冒险池)
var _refresh_allowance := 0 # 组合根投喂余量(本视图零核心引用)
var _selected := "" # 点选暂存(确认前可改选)
var _header := "" # 停点头部文案(选择态保持显示)
var _card_meta: Array = [] # open_pick 缓存 [{id, text}]——高亮还原读面
var _bcards: Array = [] # M7 bcard 引用列(select 高亮=set_selected)
func _init() -> void:
# M7(UI 落码批):与商店同构(demo ⑥ 用户令两屏一致)——通栏顶条(标题+刷新图标钮/角标)+
# 居中入账信息行 + bcard 三竖卡 + 底部唯一主钮「确认选择」;差异白名单=免费选择/✓章/无价签
var box: VBoxContainer = UiKit.modal_shell(self, Color(0, 0, 0, 0.6)).box
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 12)
box.add_child(top)
top.add_child(UiKit.label("战利品三选一", 22, true, UiKit.WARM))
var top_sp := Control.new()
top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(top_sp)
_refresh_btn = UiKit.button("", 0.0) # 图标钮自设 44×44——豁免宽度基准
_refresh_btn.icon = UiKit.load_tex(UiKit.TEX_ICON_REFRESH)
_refresh_btn.custom_minimum_size = Vector2(44, 44)
_refresh_btn.tooltip_text = "刷新候选——装配刷新件 X 优先,Y 为冒险池;不消耗 AP"
_refresh_btn.pressed.connect(_emit_refresh_requested)
_refresh_btn.visible = false
top.add_child(_refresh_btn)
_refresh_badge = UiKit.label("0", 12, true, UiKit.INK_ON_GOLD)
_refresh_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
_refresh_badge.set_anchors_preset(Control.PRESET_TOP_RIGHT)
_refresh_badge.offset_left = -22.0
_refresh_badge.offset_top = -6.0
_refresh_btn.add_child(_refresh_badge)
_refresh_l = UiKit.label("", 12, false, UiKit.WARM_SOFT)
_refresh_l.visible = false
top.add_child(_refresh_l)
_label = UiKit.label("", 15, false, UiKit.WARM_SOFT)
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_label)
_choice_box = HBoxContainer.new()
_choice_box.add_theme_constant_override("separation", 20)
_choice_box.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_child(_choice_box)
_confirm_btn = UiKit.primary_button("确认选择")
_confirm_btn.pressed.connect(_on_confirm_pressed)
_confirm_btn.visible = false
box.add_child(_confirm_btn)
_replace_box = VBoxContainer.new()
_replace_box.add_theme_constant_override("separation", 8)
_replace_box.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_child(_replace_box)
_continue_btn = UiKit.button("继续")
_continue_btn.pressed.connect(_emit_continue_pressed)
_continue_btn.visible = false
box.add_child(_continue_btn)
visible = false
# 选择态:header = 停点头部(回血+钥石增量行,Narrator 装配);cards = [{id, text}]
#(组合根组装——视图纯呈现)。确认前可改选;确认经 pick_confirmed 外发。
func open_pick(header: String, cards: Array) -> void:
_header = header
_label.text = header
_selected = ""
_card_meta = (cards as Array).duplicate(true)
for c in _choice_box.get_children():
c.queue_free()
_bcards.clear()
for ch in cards:
var cid := String(ch.get("id", ""))
var full := String(ch.get("text", ""))
var lines := full.split("\n")
var title := lines[0]
var desc := "\n".join(lines.slice(1)) if lines.size() > 1 else ""
var idl := cid.to_lower()
var band := Color8(194, 178, 100) # 通用增益(默认——§4.2 类型微光族色)
if idl.contains("heal"):
band = Color8(102, 164, 87)
elif idl.contains("magic") or idl.contains("blast"):
band = Color8(138, 114, 196)
elif idl.contains("pierce"):
band = Color8(87, 160, 160)
var card := UiKit.bcard(title, desc, band, _icon_for_id(idl))
card.gui_input.connect(func(ev: InputEvent) -> void:
if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT:
_select_card(cid))
card.mouse_filter = Control.MOUSE_FILTER_STOP
_choice_box.add_child(card)
_bcards.append(card)
_choice_box.visible = true
_confirm_btn.visible = false
_replace_box.visible = false
_continue_btn.visible = false
_refresh_btn.visible = false # 刷新余量随 set_refresh_info 投喂后现形(open 先归零防残留)
_refresh_l.text = ""
visible = true
# 三选一刷新余量投喂(组合根读 adventure.attune_refresh_left()——0 = 刷新面隐藏)
func set_refresh_info(allowance: int) -> void:
_refresh_allowance = maxi(0, int(allowance))
_refresh_btn.visible = _refresh_allowance > 0
_refresh_badge.text = str(_refresh_allowance)
_refresh_l.text = "免费 · 不消耗 AP" if _refresh_allowance > 0 else ""
_refresh_l.visible = _refresh_allowance > 0
func _emit_refresh_requested() -> void:
refresh_requested.emit()
func _select_card(id: String) -> void:
_selected = id
# 选中态=木描边+✓章(demo ⑥;set_selected 逐卡置真值防改选残留)
for i in _bcards.size():
var meta: Dictionary = _card_meta[i] if i < _card_meta.size() else {}
(_bcards[i] as UiKit.BCard).set_selected(String(meta.get("id", "")) == id)
_confirm_btn.visible = true
func _on_confirm_pressed() -> void:
if _selected != "":
pick_confirmed.emit(_selected)
# 探针/测试复用:以真实按钮路径触发点选、确认与继续(树序遮挡纪律——断言走真实信号路径)
func press_card(index: int) -> void:
# M7:卡载体 Button→bcard(gui_input 点选)——真实路径=同一 _select_card 处理器,探针语义不变
if index >= 0 and index < _card_meta.size():
_select_card(String(_card_meta[index].get("id", "")))
func press_confirm() -> void:
_confirm_btn.pressed.emit()
func press_continue() -> void:
_continue_btn.pressed.emit()
func selected_id() -> String:
return _selected
# 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
func show_result(text: String) -> void:
_label.text = _header + "\n" + text
_choice_box.visible = false
_confirm_btn.visible = false
_replace_box.visible = false
_continue_btn.visible = true
visible = true
# 同调位替换子态(treasure_replace_presented 到达):items = [{index, item_id, display_name}](组合根
# 喂显示名)。两路出口:丢旧收新(逐件钮)/ 放弃新件——终态恒 ≤ 同调位由核心受理面保证。
func open_replace(items: Array, incoming_name: String, holding: int, slots: int) -> void:
_label.text = "同调位已满(%d/%d)——收下「%s」需丢弃一件已有道具,或放弃新件" % [holding, slots, incoming_name]
for c in _replace_box.get_children():
c.queue_free()
for it in items:
var tag: int = int(it.get("index", it.get("handle", -1))) # 通关渠道=携带序号 / 节点渠道=库存句柄(核心分发面裁决)
var btn := UiKit.button("丢弃「%s」换新" % String(it.get("display_name", it.get("item_id", ""))))
btn.pressed.connect(_emit_replace_chosen.bind(tag))
_replace_box.add_child(btn)
var decline := UiKit.button("放弃新件(不获取)")
decline.pressed.connect(_emit_replace_chosen.bind(-1))
_replace_box.add_child(decline)
_choice_box.visible = false
_confirm_btn.visible = false
_replace_box.visible = true
_continue_btn.visible = false
visible = true
# 收屏(组合根在 continue_pressed 处理毕调——推进下一关同拍)
func close_pick() -> void:
_choice_box.visible = false
_replace_box.visible = false
_continue_btn.visible = false
_confirm_btn.visible = false
visible = false
func _emit_continue_pressed() -> void:
continue_pressed.emit()
func _emit_replace_chosen(tag: int) -> void:
replace_chosen.emit(tag)
# M7:id → 手绘图标键(子串映射容错 id 词表演进;缺位=BCard 空图标槽不炸)
func _icon_for_id(idl: String) -> String:
if idl.contains("heal"):
return UiKit.TEX_ICON_MEDKIT
if idl.contains("blast") or idl.contains("magic"):
return UiKit.TEX_ICON_BLAST
if idl.contains("gp") or idl.contains("coin"):
return UiKit.TEX_ICON_COINBAG
if idl.contains("attack"):
return UiKit.TEX_ICON_SWORD
if idl.contains("armor"):
return UiKit.TEX_ICON_SHIELD
if idl.contains("ap") or idl.contains("action"):
return UiKit.TEX_ICON_POUCH
return UiKit.TEX_ICON_COINBAG