iter10 T3-T9: 奖励重构实装——adventure表扩列(回血20/池六种/权重/上限/同调位过渡键)+boss_keystone重锚3-2+rng扩签reward_pick+E-S加权洗牌+三选一停点(零自动推进/可中断)+加算区四轴AP(BattleOrchestrator排队兑现)+钥石逐杀掉落(收束无二次派生)+同调位约束(停点替换两路/战斗沉没/节点拒绝)+BonusPickOverlay+Narrator文案族+save v3迁移+冒烟619/0(+23断言)+断点P5往返单测+run_all 15套全绿
This commit is contained in:
1 parent
49b263fbc8
commit
71502ceecd
18 files changed
+992
-169
No files matched your search
@@ -77,12 +77,48 @@ static func boss_name(runner, db) -> String:
|
||||
return "遗迹守卫"
|
||||
|
||||
|
||||
# 大奖励屏文案(冒险结构实现批——兑付三件:上限提升/血回满/钥石入账;读数 = big_reward_paid 载荷快照)
|
||||
static func big_reward_text(reward: Dictionary) -> String:
|
||||
return "第 %d 层通过!大奖励:生命上限 +%d(新上限 %d),生命回满,钥石 +%d(累计 %d)" % [
|
||||
int(reward.get("from_level", 0)), int(reward.get("hp_bonus", 0)),
|
||||
int(reward.get("new_max_hp", 0)), int(reward.get("keystone_gained", 0)),
|
||||
int(reward.get("keystone_total", 0))]
|
||||
# ---- 提升三选一文案族(迭代 10 奖励重构批——big_reward_text 随三件套退役)----
|
||||
|
||||
# 三选一停点头部行(回血反馈 + Boss 掉落钥石增量;读数 = bonus_choice_presented 载荷快照)
|
||||
static func bonus_pick_header(payload: Dictionary) -> String:
|
||||
var lines: Array[String] = ["第 %d 层通过!" % int(payload.get("level_index", 0))]
|
||||
var heal := int(payload.get("heal_gained", 0))
|
||||
if heal > 0:
|
||||
lines.append("通关回血 +%d(生命 %d → %d)" % [heal, int(payload.get("hp_before", 0)), int(payload.get("hp_after", 0))])
|
||||
else:
|
||||
lines.append("生命已满——通关回血 0(合法态)")
|
||||
lines.append("Boss 掉落钥石 +%d(累计 %d)" % [int(payload.get("keystone_gained", 0)), int(payload.get("keystone_total", 0))])
|
||||
lines.append("选择一件提升带走(另两件丢弃不可反悔):")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# 三选一选项卡文案(件名 + 效果读数;值 = 冒险表行值,组合根喂入)
|
||||
static func bonus_card_text(id: String, value: int) -> String:
|
||||
match id:
|
||||
"attack":
|
||||
return "攻击力提升\n攻击 +%d" % value
|
||||
"max_hp":
|
||||
return "生命上限提升\n生命上限 +%d" % value
|
||||
"armor":
|
||||
return "护甲等级提升\n护甲 +%d" % value
|
||||
"gp":
|
||||
return "金币袋\n金币 +%d" % value
|
||||
"aux_item":
|
||||
return "魔法道具\n随机获得一件"
|
||||
"ap":
|
||||
return "行动点提升\n每战首轮 AP +%d" % value
|
||||
return id
|
||||
|
||||
|
||||
# 三选一兑付反馈行(选中件效果 + 丢弃提示)
|
||||
static func bonus_result_text(result: Dictionary) -> String:
|
||||
var name := bonus_card_text(String(result.get("picked", "")), int(result.get("value", 0))).replace("\n", " · ")
|
||||
return "已选择:%s\n其余选项已丢弃(不可反悔)" % name
|
||||
|
||||
|
||||
# Boss 胜利战斗总结结算行(settlement-summary v1.1——钥石增量行;组合根拼 GP 行后追加)
|
||||
static func bonus_keystone_line(payload: Dictionary) -> String:
|
||||
return "钥石 +%d · 累计 %d" % [int(payload.get("keystone_gained", 0)), int(payload.get("keystone_total", 0))]
|
||||
|
||||
|
||||
# 冒险结算屏文案(摘要五件事:打穿没有 / 通关几关 / 止步第几关 / 钥石合计 / 主种子)
|
||||
@@ -97,14 +133,14 @@ static func adventure_summary_text(summary: Dictionary) -> String:
|
||||
int(summary.get("keystone_total", 0)), int(summary.get("master_seed", 0))]
|
||||
|
||||
|
||||
# 钥石入账明细文案(元进度实现批——settle_adventure 明细的结算屏追加段:大奖励 / 逐关 Boss
|
||||
# 首杀或重复两档 / 新解锁道具;name_lookup = item_id → 显示名投影,由组合根自 meta 表喂入)
|
||||
# 钥石入账明细文案(元进度实现批;迭代 10 改口径——钥石全源 = Boss 逐杀掉落,「大奖励」行退役;
|
||||
# name_lookup = item_id → 显示名投影,由组合根自 meta 表喂入)
|
||||
static func keystone_settle_text(detail: Dictionary, name_lookup: Dictionary = {}) -> String:
|
||||
var parts: Array = ["大奖励 +%d" % int(detail.get("big_reward", 0))]
|
||||
var parts: Array = []
|
||||
for d in detail.get("boss_drops", []):
|
||||
parts.append("第 %d 关 Boss %s +%d" % [int(d.get("level", 0)),
|
||||
"首杀" if bool(d.get("first", false)) else "重复杀", int(d.get("amount", 0))])
|
||||
var line := "钥石入账:" + " · ".join(parts)
|
||||
var line := "钥石入账:" + (" · ".join(parts) if not parts.is_empty() else "本局无掉落")
|
||||
var unlocks: Array = detail.get("new_unlocks", [])
|
||||
if not unlocks.is_empty():
|
||||
var names: Array[String] = []
|
||||
|
||||
+95
-10
@@ -44,6 +44,8 @@ var confirm_dialog: ConfirmDialog # 通用确认弹窗(二轮修改批 A
|
||||
var _settlement_stop_pending := false # 卡⑥ 战斗胜利总结结算框挂起态(确认「下一节点」后进选路页)
|
||||
var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
||||
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
||||
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载)
|
||||
var _start_overlay: Control
|
||||
var _end_overlay: BattleEndOverlay
|
||||
var _inter_overlay: Control
|
||||
@@ -191,8 +193,15 @@ func _build_ui() -> void:
|
||||
add_child(confirm_dialog)
|
||||
confirm_dialog.confirmed.connect(_on_confirm_new_adventure)
|
||||
confirm_dialog.cancelled.connect(_on_confirm_cancelled)
|
||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
||||
bonus_overlay = BonusPickOverlay.new()
|
||||
add_child(bonus_overlay)
|
||||
bonus_overlay.pick_confirmed.connect(_on_bonus_pick_confirmed)
|
||||
bonus_overlay.replace_chosen.connect(_on_bonus_replace_chosen)
|
||||
bonus_overlay.continue_pressed.connect(_on_bonus_continue)
|
||||
move_child(bonus_overlay, _end_overlay.get_index())
|
||||
_modals = ModalStack.new()
|
||||
for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay, confirm_dialog]:
|
||||
for m: Control in [legend_overlay, review_overlay, node_overlay, bonus_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay, confirm_dialog]:
|
||||
_register_modal(m)
|
||||
_tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp))
|
||||
|
||||
@@ -329,7 +338,10 @@ func _wire_adventure() -> void:
|
||||
adventure.node_entered.connect(_on_runner_node)
|
||||
adventure.choices_presented.connect(_on_choices_presented)
|
||||
adventure.wallet_changed.connect(_on_wallet_changed)
|
||||
adventure.big_reward_paid.connect(_on_big_reward)
|
||||
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
|
||||
adventure.bonus_choice_presented.connect(_on_bonus_presented)
|
||||
adventure.bonus_resolved.connect(_on_bonus_resolved)
|
||||
adventure.aux_replace_presented.connect(_on_aux_replace_presented)
|
||||
adventure.adventure_finished.connect(_on_adventure_finished)
|
||||
adventure.level_started.connect(_on_adventure_level_started)
|
||||
adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径)
|
||||
@@ -402,18 +414,87 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
# ---- 冒险模式终局面(大奖励屏 / 冒险结算屏——冒险结构实现批)----
|
||||
# ---- 冒险模式终局面(三选一选择面 / 冒险结算屏——迭代 10 奖励重构批)----
|
||||
|
||||
# 大奖励兑付(非末关通关恰一次):复用 _inter_overlay(label + 确认按钮,零新增交互范式);
|
||||
# 文案装配归 Narrator
|
||||
func _on_big_reward(reward: Dictionary) -> void:
|
||||
if int(reward.get("aux_cleared", 0)) > 0:
|
||||
board_zone.toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(reward.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
||||
_inter_label.text = Narrator.big_reward_text(reward)
|
||||
_inter_overlay.visible = true
|
||||
# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
||||
# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约)
|
||||
func _on_bonus_presented(payload: Dictionary) -> void:
|
||||
if int(payload.get("aux_cleared", 0)) > 0:
|
||||
board_zone.toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
||||
_pending_bonus = payload.duplicate(true)
|
||||
if bool(payload.get("replace_pending", false)):
|
||||
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
|
||||
_show_bonus_pick()
|
||||
return
|
||||
_end_overlay.open_victory(Narrator.battle_end_text("victory", false), "继续",
|
||||
board_view.get_global_rect(),
|
||||
"%s\n%s" % [_gp_summary_text({"gp_dropped": _last_gain_amount}), Narrator.bonus_keystone_line(payload)])
|
||||
_open_modal(_end_overlay)
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态)
|
||||
func _show_bonus_pick() -> void:
|
||||
var payload: Dictionary = _pending_bonus
|
||||
_open_modal(bonus_overlay)
|
||||
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array))
|
||||
_refresh_presentations()
|
||||
|
||||
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷)
|
||||
var _last_replace_incoming := {} # 同上——incoming 记录
|
||||
|
||||
|
||||
func _bonus_card_texts(candidates: Array) -> Array:
|
||||
var out: Array = []
|
||||
for id in candidates:
|
||||
out.append({"id": String(id), "text": Narrator.bonus_card_text(String(id), int(db.adventure.bonus_value(String(id))))})
|
||||
return out
|
||||
|
||||
|
||||
func _on_bonus_pick_confirmed(item_id: String) -> void:
|
||||
if adventure == null or not adventure.pick_bonus(item_id):
|
||||
push_error("[组合根] 三选一兑付被拒——见输出日志")
|
||||
|
||||
|
||||
func _on_aux_replace_presented(items: Array, incoming: Dictionary) -> void:
|
||||
_last_replace_items = items.duplicate(true)
|
||||
_last_replace_incoming = incoming.duplicate(true)
|
||||
if not bonus_overlay.visible:
|
||||
_open_modal(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
||||
_open_bonus_replace(items, incoming)
|
||||
|
||||
|
||||
func _open_bonus_replace(items: Array, incoming: Dictionary) -> void:
|
||||
var named: Array = []
|
||||
for it in items:
|
||||
var rec: Dictionary = it.duplicate()
|
||||
rec["display_name"] = String(_item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", ""))))
|
||||
named.append(rec)
|
||||
bonus_overlay.open_replace(named, String(_item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
||||
named.size(), int(db.adventure.transitional_sync_slots))
|
||||
|
||||
|
||||
func _on_bonus_replace_chosen(discard_index: int) -> void:
|
||||
if adventure == null or not adventure.resolve_aux_replace(discard_index):
|
||||
push_error("[组合根] 同调位替换受理被拒——见输出日志")
|
||||
|
||||
|
||||
# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续)
|
||||
func _on_bonus_resolved(result: Dictionary) -> void:
|
||||
_pending_bonus = {}
|
||||
bonus_overlay.show_result(Narrator.bonus_result_text(result))
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
# 继续钮:收三选一面 → 推进下一关(零自动推进——兑付后受理)
|
||||
func _on_bonus_continue() -> void:
|
||||
_close_modal(bonus_overlay)
|
||||
bonus_overlay.close_pick()
|
||||
if _adventure_mode and adventure != null:
|
||||
if not adventure.continue_to_next_level():
|
||||
_fatal_screen("关卡推进失败——见输出日志")
|
||||
|
||||
|
||||
# 冒险结算(completed/failed/abandoned):复用 BattleEndOverlay——摘要五件事 + 钥石入账明细进文案
|
||||
func _on_adventure_finished(summary: Dictionary) -> void:
|
||||
_level_active = false
|
||||
@@ -457,6 +538,10 @@ func _on_battle_end_continue() -> void:
|
||||
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石已在收束时入存档——存档序列化批)
|
||||
_close_modal(_end_overlay)
|
||||
_end_overlay.close()
|
||||
# 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
||||
if not _pending_bonus.is_empty():
|
||||
_show_bonus_pick()
|
||||
return
|
||||
# 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
||||
if _settlement_stop_pending:
|
||||
_settlement_stop_pending = false
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# 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 BonusPickOverlay
|
||||
extends Control
|
||||
|
||||
signal pick_confirmed(item_id: String)
|
||||
signal replace_chosen(discard_index: int) # -1 = 放弃新件不获取
|
||||
signal continue_pressed
|
||||
|
||||
var _label: Label
|
||||
var _choice_box: HBoxContainer # 三卡按钮区(pick 态)
|
||||
var _replace_box: VBoxContainer # 替换子面板(replace 态)
|
||||
var _confirm_btn: Button # 确认钮(点选后点亮——两段式第二拍)
|
||||
var _continue_btn: Button # 继续钮(兑付反馈态唯一出口)
|
||||
var _selected := "" # 点选暂存(确认前可改选)
|
||||
var _header := "" # 停点头部文案(选择态保持显示)
|
||||
var _card_meta: Array = [] # open_pick 缓存 [{id, text}]——高亮还原读面
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var dim := ColorRect.new()
|
||||
dim.color = Color(0, 0, 0, 0.6)
|
||||
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(dim)
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
dim.add_child(center)
|
||||
var box := VBoxContainer.new()
|
||||
box.add_theme_constant_override("separation", 12)
|
||||
center.add_child(box)
|
||||
_label = UiKit.label("", 20, true)
|
||||
_label.add_theme_color_override("font_color", UiKit.WARM)
|
||||
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_label)
|
||||
_choice_box = HBoxContainer.new()
|
||||
_choice_box.add_theme_constant_override("separation", 12)
|
||||
_choice_box.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
box.add_child(_choice_box)
|
||||
_confirm_btn = UiKit.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(func(): continue_pressed.emit())
|
||||
_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()
|
||||
for ch in cards:
|
||||
var cid := String(ch.get("id", ""))
|
||||
var btn := UiKit.button(String(ch.get("text", "")))
|
||||
btn.custom_minimum_size = Vector2(170, 88)
|
||||
btn.pressed.connect(func(): _select_card(cid))
|
||||
_choice_box.add_child(btn)
|
||||
_choice_box.visible = true
|
||||
_confirm_btn.visible = false
|
||||
_replace_box.visible = false
|
||||
_continue_btn.visible = false
|
||||
visible = true
|
||||
|
||||
|
||||
func _select_card(id: String) -> void:
|
||||
_selected = id
|
||||
for i in _choice_box.get_child_count():
|
||||
var btn := _choice_box.get_child(i) as Button
|
||||
var meta: Dictionary = _card_meta[i] if i < _card_meta.size() else {}
|
||||
var base_text := String(meta.get("text", ""))
|
||||
btn.text = ("✓ " + base_text) if String(meta.get("id", "")) == id else base_text
|
||||
_confirm_btn.visible = true
|
||||
|
||||
|
||||
func _on_confirm_pressed() -> void:
|
||||
if _selected != "":
|
||||
pick_confirmed.emit(_selected)
|
||||
|
||||
|
||||
# 探针/测试复用:以真实按钮路径触发点选、确认与继续(树序遮挡纪律——断言走真实信号路径)
|
||||
func press_card(index: int) -> void:
|
||||
if index >= 0 and index < _choice_box.get_child_count():
|
||||
(_choice_box.get_child(index) as Button).pressed.emit()
|
||||
|
||||
|
||||
func press_confirm() -> void:
|
||||
_confirm_btn.pressed.emit()
|
||||
|
||||
|
||||
func press_continue() -> void:
|
||||
_continue_btn.pressed.emit()
|
||||
|
||||
|
||||
func selected_id() -> String:
|
||||
return _selected
|
||||
|
||||
|
||||
# 兑付反馈态(bonus_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
|
||||
|
||||
|
||||
# 同调位替换子态(aux_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 idx := int(it.get("index", -1))
|
||||
var btn := UiKit.button("丢弃「%s」换新" % String(it.get("display_name", it.get("item_id", ""))))
|
||||
btn.pressed.connect(func(): replace_chosen.emit(idx))
|
||||
_replace_box.add_child(btn)
|
||||
var decline := UiKit.button("放弃新件(不获取)")
|
||||
decline.pressed.connect(func(): replace_chosen.emit(-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
|
||||
@@ -0,0 +1 @@
|
||||
uid://dqlecn1dxpg0i
|
||||
Reference in new issue
Block a user