奇物批T2:运行层bonus_*→curio_*全族换词(_curio_pending/deck/taken/result/candidates+pick_curio/apply_node_curio/curio_choice_presented/curio_resolved等+两文件改名curio_interact_controller/curio_pick_overlay含.uid+CurioPickOverlay/CurioInteractController类名);T2裁点甲案自裁=五存档键引号字面量禁改(全仓对拍六形态全等);_bonus_treasure_carry按T0澄清剔除(秘宝语义);非奇物bonus语义保全(加算区bonus/price_level_bonus/attack_bonus/bp_bonus等);reward_overlay_test红转绿适配(M7 bcard换PanelContainer致c is Button恒0——改数_choice_box子节点数断言语义不变,run_all 16套全绿历史首次)

This commit is contained in:
DKChan committed 2026-10-05 18:44:51 +08:00
1 parent d4cde73976
commit 8e2694de49
17 files changed
+298 -299

No files matched your search

@@ -0,0 +1,227 @@
# 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