奇物批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:
1 parent
d4cde73976
commit
8e2694de49
17 files changed
+298
-299
No files matched your search
+113
-113
@@ -41,9 +41,9 @@ class_name AdventureRunner
|
||||
extends RefCounted
|
||||
|
||||
signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行
|
||||
signal bonus_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
||||
signal bonus_resolved(result: Dictionary) # 三选一兑付完成(pick_bonus/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
|
||||
signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_bonus 道具件满位时)——items=现有持有件记录、incoming=新件记录
|
||||
signal curio_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
||||
signal curio_resolved(result: Dictionary) # 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
|
||||
signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_curio 道具件满位时)——items=现有持有件记录、incoming=新件记录
|
||||
signal adventure_finished(summary: Dictionary) # 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops 迭代 10)
|
||||
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;ADR-0001 S6 经 _forward_* 方法)----
|
||||
signal node_entered(node: Dictionary, info: Dictionary)
|
||||
@@ -79,16 +79,16 @@ var attune_charges := {} # 同调主动件冒险充能账本(
|
||||
var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省)
|
||||
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
||||
# ---- 三选一停点态(迭代 10 奖励重构批)----
|
||||
var _bonus_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_bonus_deck)
|
||||
var _bonus_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
|
||||
var _curio_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_curio_deck)
|
||||
var _curio_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
|
||||
var _relics: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
|
||||
var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
||||
var _bonus_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
|
||||
var _bonus_result := {} # 已兑付待推进态(bonus_resolved 载荷副本——中断续玩不重兑付)
|
||||
var _curio_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
|
||||
var _curio_result := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付)
|
||||
var _bonus_treasure_carry: Array[String] = [] # 道具件跨关携带(兑付后注入下一关库存——清库后授予的体验窗)
|
||||
var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源)
|
||||
var _last_draw_treasure: Array[String] = [] # 最近一次抽签掷定的道具件(掷定前置——卡片直显具体道具;追加批二)
|
||||
var _node_bonus_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
||||
var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
||||
var _shop_mode := "legacy" # 商店模式(迭代 11 第②批 T3——"legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
purchased = {}
|
||||
keystone = 0
|
||||
summary = {}
|
||||
_reset_bonus_state()
|
||||
_reset_curio_state()
|
||||
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
|
||||
level_index = 1
|
||||
_pending_next_level = false
|
||||
@@ -174,10 +174,10 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
|
||||
level_index = int(bp.get("level_index", 1))
|
||||
_pending_next_level = false
|
||||
_reset_bonus_state()
|
||||
_reset_curio_state()
|
||||
var bp_taken: Dictionary = bp.get("bonus_taken", {}) as Dictionary
|
||||
for id in _bonus_taken.keys():
|
||||
_bonus_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
||||
for id in _curio_taken.keys():
|
||||
_curio_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
||||
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
||||
# 迭代 12 T1——relic 令牌 relic_id 随还原)
|
||||
var bp_deck = bp.get("bonus_deck", null)
|
||||
@@ -189,7 +189,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
if String(rtok["kind"]) == "relic":
|
||||
rtok["relic_id"] = String(tok.get("relic_id", ""))
|
||||
restored.append(rtok)
|
||||
_bonus_deck = restored
|
||||
_curio_deck = restored
|
||||
# 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集)
|
||||
_relics = []
|
||||
for r in (bp.get("relics", []) as Array):
|
||||
@@ -223,28 +223,28 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
migrated.append({"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))})
|
||||
else:
|
||||
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
|
||||
_bonus_pending = {"candidates": migrated}
|
||||
_curio_pending = {"candidates": migrated}
|
||||
if bp_pending.has("aux_detail"):
|
||||
_bonus_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接
|
||||
_curio_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接
|
||||
var payload: Dictionary = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
if bp_pending.has("incoming"):
|
||||
_bonus_pending["chosen"] = "treasure_item"
|
||||
_curio_pending["chosen"] = "treasure_item"
|
||||
var incoming: Array = (bp_pending.get("incoming", []) as Array).duplicate(true)
|
||||
_bonus_pending["incoming"] = incoming
|
||||
_curio_pending["incoming"] = incoming
|
||||
state = "running"
|
||||
bonus_choice_presented.emit(payload)
|
||||
curio_choice_presented.emit(payload)
|
||||
# 替换在途续发(呈现层直落替换子态——持有件记录按恢复后 carry 重建)
|
||||
treasure_replace_presented.emit(_carry_records(),
|
||||
{"index": -1, "item_id": String(incoming[0]) if not incoming.is_empty() else "", "count": incoming.size()})
|
||||
return true
|
||||
state = "running"
|
||||
bonus_choice_presented.emit(payload)
|
||||
curio_choice_presented.emit(payload)
|
||||
return true
|
||||
if not bp_result.is_empty():
|
||||
_pending_next_level = true
|
||||
_bonus_result = bp_result.duplicate(true)
|
||||
_curio_result = bp_result.duplicate(true)
|
||||
state = "running"
|
||||
bonus_resolved.emit(_bonus_result.duplicate(true))
|
||||
curio_resolved.emit(_curio_result.duplicate(true))
|
||||
return true
|
||||
_resume_bp = bp.duplicate(true)
|
||||
# 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
||||
@@ -256,21 +256,21 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
|
||||
|
||||
# 三选一停点态归零(冒险开始/结算收束共用;令牌池按表配额重建——池消耗即上限,账本随池重生)
|
||||
func _reset_bonus_state() -> void:
|
||||
_bonus_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0}
|
||||
_bonus_deck = _build_bonus_deck()
|
||||
func _reset_curio_state() -> void:
|
||||
_curio_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0}
|
||||
_curio_deck = _build_curio_deck()
|
||||
_relics = []
|
||||
_bonus_pending = {}
|
||||
_bonus_result = {}
|
||||
_curio_pending = {}
|
||||
_curio_result = {}
|
||||
_bonus_treasure_carry = []
|
||||
_keystone_drops = []
|
||||
_node_bonus_pending = {}
|
||||
_node_curio_pending = {}
|
||||
|
||||
|
||||
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = CURIO_KINDS×TIERS 逐层,
|
||||
# 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:规则改写型特性带=relic 活行每件 1 张追加
|
||||
# (rarity common/rare→稀有令牌档、rarest→罕见令牌档——relic 永不入普通档;空表=零追加零扰动))
|
||||
func _build_bonus_deck() -> Array:
|
||||
func _build_curio_deck() -> Array:
|
||||
var deck: Array = []
|
||||
for kind in db.adventure.CURIO_KINDS:
|
||||
for tier in db.adventure.TIERS:
|
||||
@@ -326,11 +326,11 @@ func continue_to_next_level() -> bool:
|
||||
if not _pending_next_level:
|
||||
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
|
||||
return false
|
||||
if not _bonus_pending.is_empty():
|
||||
if not _curio_pending.is_empty():
|
||||
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
|
||||
return false
|
||||
_pending_next_level = false
|
||||
_bonus_result = {}
|
||||
_curio_result = {}
|
||||
level_index += 1
|
||||
return _start_current_level()
|
||||
|
||||
@@ -353,22 +353,22 @@ func _find_token(candidates: Array, kind: String, tier: String, relic_id: String
|
||||
# 另须 relic_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、
|
||||
# relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。
|
||||
# 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。
|
||||
func pick_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
|
||||
if state != "running" or _bonus_pending.is_empty():
|
||||
push_error("[冒险结构] pick_bonus 拒绝——无待选停点(state=%s)" % state)
|
||||
func pick_curio(kind: String, tier: String = "", relic_id: String = "") -> bool:
|
||||
if state != "running" or _curio_pending.is_empty():
|
||||
push_error("[冒险结构] pick_curio 拒绝——无待选停点(state=%s)" % state)
|
||||
return false
|
||||
var candidates: Array = _bonus_pending.get("candidates", []) as Array
|
||||
var candidates: Array = _curio_pending.get("candidates", []) as Array
|
||||
if _find_token(candidates, kind, tier, relic_id).is_empty():
|
||||
push_error("[冒险结构] pick_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
|
||||
push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
|
||||
return false
|
||||
var value: int = int(db.adventure.curio_value(kind, tier))
|
||||
var hp_before := int(ledger.get("hero_hp", 0))
|
||||
if kind == "treasure_item":
|
||||
return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达)
|
||||
if not _apply_bonus_pick(kind, tier, value, relic_id):
|
||||
push_error("[冒险结构] pick_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
|
||||
if not _apply_curio_pick(kind, tier, value, relic_id):
|
||||
push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
|
||||
return false
|
||||
_finish_bonus_pick(kind, value, hp_before, tier, relic_id)
|
||||
_finish_curio_pick(kind, value, hp_before, tier, relic_id)
|
||||
return true
|
||||
|
||||
|
||||
@@ -383,9 +383,9 @@ func _relic_payload_keys(relic_id: String) -> Dictionary:
|
||||
# 三选一件兑付共享体(2026-10-02 修复批双胞胎收口;TP0 改令牌消费——摘牌单点;迭代 12 T1 relic 分支):
|
||||
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
|
||||
# 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
||||
# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _bonus_deck 摘除该令牌(选中踢池;
|
||||
# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池;
|
||||
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
|
||||
func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool:
|
||||
func _apply_curio_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool:
|
||||
if kind == "relic" and db.relic_row(relic_id).is_empty():
|
||||
push_error("[冒险结构] relic 兑付失败——表无此件: %s" % relic_id)
|
||||
return false # 先校验后摘牌(令牌零丢失)
|
||||
@@ -395,28 +395,28 @@ func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String
|
||||
if kind == "relic":
|
||||
if not _relics.has(relic_id):
|
||||
_relics.append(relic_id)
|
||||
_bonus_taken["relic"] = int(_bonus_taken.get("relic", 0)) + 1
|
||||
_curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1
|
||||
return true
|
||||
match kind:
|
||||
"attack":
|
||||
bonus["atk"] = int(bonus.get("atk", 0)) + value
|
||||
_bonus_taken["attack"] = int(_bonus_taken.get("attack", 0)) + 1
|
||||
_curio_taken["attack"] = int(_curio_taken.get("attack", 0)) + 1
|
||||
"max_hp":
|
||||
bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value
|
||||
_bonus_taken["max_hp"] = int(_bonus_taken.get("max_hp", 0)) + 1
|
||||
_curio_taken["max_hp"] = int(_curio_taken.get("max_hp", 0)) + 1
|
||||
"armor":
|
||||
bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value
|
||||
_bonus_taken["armor"] = int(_bonus_taken.get("armor", 0)) + 1
|
||||
_curio_taken["armor"] = int(_curio_taken.get("armor", 0)) + 1
|
||||
"ap":
|
||||
bonus["ap"] = int(bonus.get("ap", 0)) + value
|
||||
_bonus_taken["ap"] = int(_bonus_taken.get("ap", 0)) + 1
|
||||
_curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1
|
||||
"gp":
|
||||
if _runner != null:
|
||||
_runner.grant_bonus_gp(value)
|
||||
_bonus_taken["gp"] = int(_bonus_taken.get("gp", 0)) + 1
|
||||
_curio_taken["gp"] = int(_curio_taken.get("gp", 0)) + 1
|
||||
"heal":
|
||||
ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)) + value, effective_max_hp())
|
||||
_bonus_taken["heal"] = int(_bonus_taken.get("heal", 0)) + 1
|
||||
_curio_taken["heal"] = int(_curio_taken.get("heal", 0)) + 1
|
||||
_:
|
||||
return false
|
||||
return true
|
||||
@@ -424,13 +424,13 @@ func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String
|
||||
|
||||
# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 relic_id 精确摘;找不到 = false)
|
||||
func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> bool:
|
||||
for i in _bonus_deck.size():
|
||||
var tok: Dictionary = _bonus_deck[i] as Dictionary
|
||||
for i in _curio_deck.size():
|
||||
var tok: Dictionary = _curio_deck[i] as Dictionary
|
||||
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
||||
continue
|
||||
if kind == "relic" and String(tok.get("relic_id", "")) != relic_id:
|
||||
continue
|
||||
_bonus_deck.remove_at(i)
|
||||
_curio_deck.remove_at(i)
|
||||
return true
|
||||
return false
|
||||
|
||||
@@ -443,27 +443,27 @@ func relics_held() -> Array:
|
||||
# ---- 商店共享池消费面(迭代 11 第②批 T3——P1 共享池单点账本;TP0 令牌池换代——库存=牌库剩余令牌)----
|
||||
|
||||
# 库存余量(TP0:stock = 牌库内该 kind 剩余令牌数——「池消耗即上限」,种类次数上限退役;
|
||||
# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _bonus_deck 承载——两渠道跨关共库,全冒险一次性)。
|
||||
# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _curio_deck 承载——两渠道跨关共库,全冒险一次性)。
|
||||
func pool_stock(kind: String) -> int:
|
||||
var n := 0
|
||||
for t in _bonus_deck:
|
||||
for t in _curio_deck:
|
||||
if String((t as Dictionary).get("kind", "")) == kind:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
# 商店购买消费 handler(LevelRunner.buy_pool_item 经 Callable 调用——ADR-0001 S6 方法引用):
|
||||
# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_bonus_pick 同口
|
||||
# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_curio_pick 同口
|
||||
# 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。
|
||||
# 未知 kind 拒绝(货架词表封闭的前置防御)。
|
||||
func _consume_pool_purchase(kind: String, tier: String) -> bool:
|
||||
if not db.adventure.CURIO_KINDS.has(kind):
|
||||
push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind)
|
||||
return false
|
||||
return _apply_bonus_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
|
||||
return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
|
||||
|
||||
|
||||
# 终局池快照(_settle 清账前调用——_reset_bonus_state 会重置 _bonus_taken,摘要 additive 键
|
||||
# 终局池快照(_settle 清账前调用——_reset_curio_state 会重置 _curio_taken,摘要 additive 键
|
||||
# pool_stock 须在此前取数;货架四种 stock=cap−taken,读数面 = 商店买走 vs 奖励领走 vs 枯竭)
|
||||
func _pool_stock_snapshot() -> Dictionary:
|
||||
var out := {}
|
||||
@@ -474,30 +474,30 @@ func _pool_stock_snapshot() -> Dictionary:
|
||||
|
||||
|
||||
# 道具件兑付(同调位约束):有位直接入跨关携带;满位转替换子交互(incoming 暂存、等 resolve_treasure_replace)。
|
||||
# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _bonus_pending.aux_detail)——本函数消费预掷值,
|
||||
# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _curio_pending.aux_detail)——本函数消费预掷值,
|
||||
# 断点续玩/无预掷值时回退当场掷定(流序不同但消费数不变,确定性由调用序保证)。
|
||||
# 持有容器 = 跨关携带 _bonus_treasure_carry(与节点渠道的关卡域库存分轨——容器差异保守不合,注记在案)。
|
||||
func _pick_treasure_item(count: int) -> bool:
|
||||
var picked: Array = _draw_pending_treasure(_bonus_pending, count, true)
|
||||
var picked: Array = _draw_pending_treasure(_curio_pending, count, true)
|
||||
if picked.is_empty():
|
||||
push_error("[冒险结构] pick_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||
push_error("[冒险结构] pick_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||
return false
|
||||
var hp_before := int(ledger.get("hero_hp", 0))
|
||||
# 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸
|
||||
# 正主 = 营地装配面 Attunement.slots 满位禁选)
|
||||
for iid in picked:
|
||||
_bonus_treasure_carry.append(String(iid))
|
||||
_finish_bonus_pick("treasure_item", picked.size(), hp_before)
|
||||
_finish_curio_pick("treasure_item", picked.size(), hp_before)
|
||||
return true
|
||||
|
||||
|
||||
# 替换子交互受理(discard_index = -1 放弃新件不获取;否则丢该件腾位收新)——终态恒 ≤ 同调位
|
||||
# 持有容器 = 跨关携带(序号寻址);与节点渠道(库存句柄寻址)的丢弃/授予/收尾三步保守不合。
|
||||
func resolve_treasure_replace(discard_index: int) -> bool:
|
||||
if not _replace_in_transit(_bonus_pending):
|
||||
if not _replace_in_transit(_curio_pending):
|
||||
push_error("[冒险结构] resolve_treasure_replace 拒绝——无替换在途")
|
||||
return false
|
||||
var incoming: Array = _bonus_pending.get("incoming", []) as Array
|
||||
var incoming: Array = _curio_pending.get("incoming", []) as Array
|
||||
var hp_before := int(ledger.get("hero_hp", 0))
|
||||
if discard_index >= 0:
|
||||
if discard_index >= _bonus_treasure_carry.size():
|
||||
@@ -507,15 +507,15 @@ func resolve_treasure_replace(discard_index: int) -> bool:
|
||||
for iid in incoming:
|
||||
_bonus_treasure_carry.append(String(iid))
|
||||
# 放弃新件(discard_index < 0)= 原持有集不动、新件不获取
|
||||
_finish_bonus_pick("treasure_item", incoming.size(), hp_before)
|
||||
_finish_curio_pick("treasure_item", incoming.size(), hp_before)
|
||||
return true
|
||||
|
||||
|
||||
# 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。
|
||||
# picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数)
|
||||
func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void:
|
||||
_bonus_pending = {}
|
||||
_bonus_result = {
|
||||
func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void:
|
||||
_curio_pending = {}
|
||||
_curio_result = {
|
||||
"picked": picked,
|
||||
"tier": tier,
|
||||
"value": value,
|
||||
@@ -523,8 +523,8 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String
|
||||
"hp_after": int(ledger.get("hero_hp", 0)),
|
||||
"level_index": level_index,
|
||||
}
|
||||
_bonus_result.merge(_relic_payload_keys(relic_id))
|
||||
bonus_resolved.emit(_bonus_result.duplicate(true))
|
||||
_curio_result.merge(_relic_payload_keys(relic_id))
|
||||
curio_resolved.emit(_curio_result.duplicate(true))
|
||||
_snapshot_breakpoint()
|
||||
|
||||
|
||||
@@ -533,30 +533,30 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String
|
||||
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
||||
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
||||
# 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。
|
||||
func node_bonus_candidates() -> Dictionary:
|
||||
var cands: Array = _draw_bonus_candidates()
|
||||
func node_curio_candidates() -> Dictionary:
|
||||
var cands: Array = _draw_curio_candidates()
|
||||
return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()}
|
||||
|
||||
|
||||
func present_node_bonus(candidates: Array, treasure_detail: Array = []) -> void:
|
||||
func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void:
|
||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||
_node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)}
|
||||
_node_curio_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)}
|
||||
|
||||
|
||||
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集)
|
||||
func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
|
||||
if _node_bonus_pending.is_empty():
|
||||
push_error("[冒险结构] apply_node_bonus 拒绝——无待选停点")
|
||||
func apply_node_curio(kind: String, tier: String = "", relic_id: String = "") -> bool:
|
||||
if _node_curio_pending.is_empty():
|
||||
push_error("[冒险结构] apply_node_curio 拒绝——无待选停点")
|
||||
return false
|
||||
var candidates: Array = _node_bonus_pending.get("candidates", []) as Array
|
||||
var candidates: Array = _node_curio_pending.get("candidates", []) as Array
|
||||
if _find_token(candidates, kind, tier, relic_id).is_empty():
|
||||
push_error("[冒险结构] apply_node_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
|
||||
push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
|
||||
return false
|
||||
var value: int = int(db.adventure.curio_value(kind, tier))
|
||||
if kind == "treasure_item":
|
||||
return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达)
|
||||
if not _apply_bonus_pick(kind, tier, value, relic_id):
|
||||
push_error("[冒险结构] apply_node_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
|
||||
if not _apply_curio_pick(kind, tier, value, relic_id):
|
||||
push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
|
||||
return false
|
||||
_finish_node_bonus(kind, value, tier, relic_id)
|
||||
return true
|
||||
@@ -564,11 +564,11 @@ func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") ->
|
||||
|
||||
# 节点渠道道具件(同调位约束):持有=关卡域库存+跨关携带,有位直接入本关库存(当关可用);
|
||||
# 满位转替换子交互(丢弃目标=库存件 handle——与通关渠道的 carry 序号分轨,容器语义保守不合)。
|
||||
# 掷定前置:消费 _node_bonus_pending.aux_detail 预掷值(缺省回退当场掷定)。
|
||||
# 掷定前置:消费 _node_curio_pending.aux_detail 预掷值(缺省回退当场掷定)。
|
||||
func _apply_node_treasure(count: int) -> bool:
|
||||
var picked: Array = _draw_pending_treasure(_node_bonus_pending, count, false)
|
||||
var picked: Array = _draw_pending_treasure(_node_curio_pending, count, false)
|
||||
if picked.is_empty():
|
||||
push_error("[冒险结构] apply_node_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||
push_error("[冒险结构] apply_node_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||
return false
|
||||
var records: Array = []
|
||||
if _runner != null and _runner.treasure_inventory != null:
|
||||
@@ -585,10 +585,10 @@ func _apply_node_treasure(count: int) -> bool:
|
||||
|
||||
|
||||
func _resolve_node_replace(discard_handle: int) -> bool:
|
||||
if not _replace_in_transit(_node_bonus_pending):
|
||||
if not _replace_in_transit(_node_curio_pending):
|
||||
push_error("[冒险结构] resolve_node_replace 拒绝——无替换在途")
|
||||
return false
|
||||
var incoming: Array = _node_bonus_pending.get("incoming", []) as Array
|
||||
var incoming: Array = _node_curio_pending.get("incoming", []) as Array
|
||||
if discard_handle >= 0:
|
||||
if _runner == null or _runner.treasure_inventory == null or not _runner.treasure_inventory.discard_item(discard_handle):
|
||||
push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle)
|
||||
@@ -629,40 +629,40 @@ func attune_refresh_left() -> int:
|
||||
|
||||
|
||||
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
|
||||
func node_bonus_pending_state() -> Dictionary:
|
||||
func node_curio_pending_state() -> Dictionary:
|
||||
return {
|
||||
"candidates": (_node_bonus_pending.get("candidates", []) as Array).duplicate(true),
|
||||
"aux_detail": (_node_bonus_pending.get("aux_detail", []) as Array).duplicate(true),
|
||||
"candidates": (_node_curio_pending.get("candidates", []) as Array).duplicate(true),
|
||||
"aux_detail": (_node_curio_pending.get("aux_detail", []) as Array).duplicate(true),
|
||||
}
|
||||
|
||||
|
||||
# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
|
||||
# bonus_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_bonus_pending_state 重 present。
|
||||
func refresh_bonus() -> bool:
|
||||
# curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。
|
||||
func refresh_curio() -> bool:
|
||||
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
|
||||
return false
|
||||
if not _bonus_pending.is_empty():
|
||||
if not _curio_pending.is_empty():
|
||||
if _attune_refresh_x > 0:
|
||||
_attune_refresh_x -= 1
|
||||
else:
|
||||
_attune_refresh_y -= 1
|
||||
var cands: Array = _draw_bonus_candidates()
|
||||
_bonus_pending["candidates"] = cands.duplicate(true)
|
||||
_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
||||
var payload: Dictionary = (_bonus_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
var cands: Array = _draw_curio_candidates()
|
||||
_curio_pending["candidates"] = cands.duplicate(true)
|
||||
_curio_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
||||
var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
payload["candidates"] = cands
|
||||
payload["aux_detail"] = _last_draw_treasure.duplicate()
|
||||
bonus_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||||
curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||||
_snapshot_breakpoint()
|
||||
return true
|
||||
if not _node_bonus_pending.is_empty():
|
||||
if not _node_curio_pending.is_empty():
|
||||
if _attune_refresh_x > 0:
|
||||
_attune_refresh_x -= 1
|
||||
else:
|
||||
_attune_refresh_y -= 1
|
||||
var cands2: Array = _draw_bonus_candidates()
|
||||
_node_bonus_pending["candidates"] = cands2.duplicate(true)
|
||||
_node_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
||||
var cands2: Array = _draw_curio_candidates()
|
||||
_node_curio_pending["candidates"] = cands2.duplicate(true)
|
||||
_node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
||||
return true
|
||||
return false
|
||||
|
||||
@@ -694,14 +694,14 @@ func _draw_pending_treasure(pending: Dictionary, count: int, allow_detail_fallba
|
||||
|
||||
|
||||
# 替换受理分发(单入口——节点停点在途优先按库存句柄,否则按通关停点的携带序号)
|
||||
func resolve_bonus_replace(discard: int) -> bool:
|
||||
if not _node_bonus_pending.is_empty() and _node_bonus_pending.has("incoming"):
|
||||
func resolve_curio_replace(discard: int) -> bool:
|
||||
if not _node_curio_pending.is_empty() and _node_curio_pending.has("incoming"):
|
||||
return _resolve_node_replace(discard)
|
||||
return resolve_treasure_replace(discard)
|
||||
|
||||
|
||||
func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: String = "") -> void:
|
||||
_node_bonus_pending = {}
|
||||
_node_curio_pending = {}
|
||||
var payload := {
|
||||
"context": "reward_node",
|
||||
"picked": picked,
|
||||
@@ -710,7 +710,7 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id:
|
||||
"level_index": level_index,
|
||||
}
|
||||
payload.merge(_relic_payload_keys(relic_id))
|
||||
bonus_resolved.emit(payload)
|
||||
curio_resolved.emit(payload)
|
||||
|
||||
|
||||
# 令牌池洗牌不放回抽 3(TP0 v2.2——Fisher-Yates 全库洗牌后按序收集;**同 kind 去重每 kind 至多
|
||||
@@ -719,9 +719,9 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id:
|
||||
# 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典
|
||||
# {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置
|
||||
# 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。
|
||||
func _draw_bonus_candidates() -> Array:
|
||||
func _draw_curio_candidates() -> Array:
|
||||
_last_draw_treasure = []
|
||||
var pool := _bonus_deck.duplicate()
|
||||
var pool := _curio_deck.duplicate()
|
||||
var rg: RandomNumberGenerator = rng.open_stream("reward_pick")
|
||||
for i in pool.size():
|
||||
var j := rg.randi_range(i, pool.size() - 1)
|
||||
@@ -832,7 +832,7 @@ func _start_current_level() -> bool:
|
||||
_runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费)
|
||||
if _shop_mode == "pool":
|
||||
# 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动)
|
||||
_runner.set_shop_pool(_bonus_deck, Callable(self, "_consume_pool_purchase"))
|
||||
_runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"))
|
||||
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集,
|
||||
# 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为)
|
||||
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
|
||||
@@ -876,7 +876,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||
var pick_payload: Dictionary = {
|
||||
"level_index": level_index,
|
||||
"candidates": _draw_bonus_candidates(),
|
||||
"candidates": _draw_curio_candidates(),
|
||||
"aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显)
|
||||
"heal_gained": heal,
|
||||
"hp_before": hp_before,
|
||||
@@ -886,10 +886,10 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
"gp": int(ledger.get("gp", 0)),
|
||||
"aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数)
|
||||
}
|
||||
_bonus_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
|
||||
_curio_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
|
||||
"aux_detail": _last_draw_treasure.duplicate(),
|
||||
"payload": pick_payload.duplicate(true)}
|
||||
bonus_choice_presented.emit(pick_payload)
|
||||
curio_choice_presented.emit(pick_payload)
|
||||
_snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失)
|
||||
return
|
||||
# 末关通关:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
|
||||
@@ -920,7 +920,7 @@ func _settle(result: String) -> void:
|
||||
}
|
||||
if _runner != null:
|
||||
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
|
||||
_reset_bonus_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
||||
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
||||
if _save_manager != null:
|
||||
_save_manager.clear_breakpoint() # 三结局清断点(迭代 8 卡①/T3——退出冒险=放弃流程+清除断点)
|
||||
_save_manager.write_to_disk()
|
||||
@@ -974,11 +974,11 @@ func _snapshot_breakpoint() -> void:
|
||||
"keystone": keystone,
|
||||
"visited_ids": _runner.visited_ids() if _runner != null else [],
|
||||
"aux_items": treasure_snapshot,
|
||||
"bonus_taken": _bonus_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
||||
"bonus_deck": _bonus_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
||||
"bonus_taken": _curio_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
||||
"bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
||||
"relics": _relics.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失)
|
||||
"bonus_pending": _bonus_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
||||
"bonus_result": _bonus_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||
"bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
||||
"bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||
"bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失)
|
||||
"keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10)
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func relic_row(relic_id: String) -> Dictionary:
|
||||
return relics.row_by_id(relic_id) if relics != null else {}
|
||||
|
||||
|
||||
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_bonus_deck 消费;行 rarity→投放档映射)
|
||||
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_curio_deck 消费;行 rarity→投放档映射)
|
||||
func relic_rows() -> Array:
|
||||
return relics.rows if relics != null else []
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ var _attune_charges: Dictionary = {} # 同调主动件充能账本(
|
||||
# 关卡末清空+快照计数入 level_finished 载荷)
|
||||
# ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)----
|
||||
var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传)
|
||||
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._bonus_deck——库存=剩余令牌数单点)
|
||||
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_bonus_pick 同口写加算区+账本+摘牌)
|
||||
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点)
|
||||
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌)
|
||||
var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
||||
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
|
||||
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
|
||||
@@ -640,7 +640,7 @@ func grant_treasure_item(item_id: String, source: String) -> Dictionary:
|
||||
# wallet_changed 载荷带 source 供呈现归因)
|
||||
func grant_bonus_gp(amount: int) -> void:
|
||||
gp = maxi(0, gp + amount)
|
||||
wallet_changed.emit("gain", gp, {"amount": amount, "source": "bonus_pick"})
|
||||
wallet_changed.emit("gain", gp, {"amount": amount, "source": "curio_pick"})
|
||||
|
||||
|
||||
# ---- 玩家输入转发(呈现层只与 runner 对话)----
|
||||
@@ -877,7 +877,7 @@ func shop_refresh() -> bool:
|
||||
|
||||
|
||||
# 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架
|
||||
# 令牌本体(kind+tier——_apply_bonus_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
|
||||
# 令牌本体(kind+tier——_apply_curio_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
|
||||
# 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作)
|
||||
func buy_pool_item(kind: String) -> bool:
|
||||
var shelf_tok := {}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# price_level_bonus = 关级定价加值行(level_index 连续从 1 递增)。
|
||||
# pool_shelf = 共享池货架行(M-4 迭代 11 第②批 T1——任务书 v1.3 P1 共享池/P4 ceil):键 kind_id 词表
|
||||
# 封闭 = 四切边后种(SHELF_KINDS:attack/max_hp/armor/ap——GP 袋负交易切边、规则改写型特性三轴 doctrine 切边);
|
||||
# 库存=上限即库存(账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格
|
||||
# 库存=上限即库存(账本在 冒险结构 _curio_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格
|
||||
# 因子(公式 ceil(base × b^(关序−1) × A^(本次进店已购次数)),provisional 投值——P5 甲研究拍板后置追认)。
|
||||
# products 行族 + 加法定价随 legacy 模式(shop=off 对照承载)保留,退役归 off 锚换代批。
|
||||
# 行值全部 provisional 归 难度曲线与Boss(用户拍板骨架先行、数值整体后置)。
|
||||
|
||||
@@ -81,7 +81,7 @@ static func boss_name(runner, db) -> String:
|
||||
|
||||
# 三选一停点头部行(回血反馈 + 选择引导;钥石增量行不上本面——单一展示面归战斗总结结算框,
|
||||
# level-clear.md v2 禁入清单口径)
|
||||
static func bonus_pick_header(payload: Dictionary) -> String:
|
||||
static func curio_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:
|
||||
|
||||
+40
-40
@@ -2,17 +2,17 @@
|
||||
# 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责):
|
||||
# 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态
|
||||
#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|relic_id" 复合键)+ 工坊显示名投影。
|
||||
# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——bonus_overlay
|
||||
# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——curio_overlay
|
||||
# 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/
|
||||
# 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一
|
||||
# 经 adventure.pick_bonus/apply_node_bonus/resolve_bonus_replace(core 侧单口,本类零直写)。
|
||||
# 经 adventure.pick_curio/apply_node_curio/resolve_curio_replace(core 侧单口,本类零直写)。
|
||||
extends RefCounted
|
||||
|
||||
const Narrator := preload("res://presentation/battle_narrator.gd")
|
||||
|
||||
var db
|
||||
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证)
|
||||
var bonus_overlay: BonusPickOverlay
|
||||
var curio_overlay: CurioPickOverlay
|
||||
var end_overlay: BattleEndOverlay
|
||||
var board_view: BoardView
|
||||
var toast: ToastStack
|
||||
@@ -27,20 +27,20 @@ var _refresh_presentations: Callable
|
||||
var _fatal_screen: Callable
|
||||
var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根)
|
||||
|
||||
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
||||
var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
||||
var _pending_curio := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
||||
var _curio_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
||||
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(treasure_replace_presented 载荷)
|
||||
var _last_replace_incoming := {} # 同上——incoming 记录
|
||||
|
||||
|
||||
func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_overlay_ref: BattleEndOverlay,
|
||||
func _init(db_ref, meta_get: Callable, curio_overlay_ref: CurioPickOverlay, end_overlay_ref: BattleEndOverlay,
|
||||
board_view_ref: BoardView, toast_ref: ToastStack, save_get: Callable,
|
||||
adventure_get: Callable, runner_get: Callable, adventure_mode_get: Callable,
|
||||
open_modal: Callable, open_modal_after_playback: Callable, close_modal: Callable,
|
||||
refresh_presentations: Callable, fatal_screen: Callable, gp_summary: Callable) -> void:
|
||||
db = db_ref
|
||||
_meta_get = meta_get
|
||||
bonus_overlay = bonus_overlay_ref
|
||||
curio_overlay = curio_overlay_ref
|
||||
end_overlay = end_overlay_ref
|
||||
board_view = board_view_ref
|
||||
toast = toast_ref
|
||||
@@ -58,12 +58,12 @@ func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_
|
||||
|
||||
# 停点在途判据(组合根 _on_battle_end_continue 分派面——战斗总结确认后开三选一)
|
||||
func has_pending() -> bool:
|
||||
return not _pending_bonus.is_empty()
|
||||
return not _pending_curio.is_empty()
|
||||
|
||||
|
||||
# 续玩停点恢复入口(true=已接手呈现,调用方中止后续分派)
|
||||
func resume_if_pending() -> bool:
|
||||
if _pending_bonus.is_empty():
|
||||
if _pending_curio.is_empty():
|
||||
return false
|
||||
show_pick()
|
||||
return true
|
||||
@@ -74,8 +74,8 @@ func resume_if_pending() -> bool:
|
||||
func on_presented(payload: Dictionary) -> void:
|
||||
if int(payload.get("aux_cleared", 0)) > 0:
|
||||
toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
||||
_pending_bonus = payload.duplicate(true)
|
||||
_bonus_context = "level_clear"
|
||||
_pending_curio = payload.duplicate(true)
|
||||
_curio_context = "level_clear"
|
||||
if bool(payload.get("replace_pending", false)):
|
||||
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
|
||||
show_pick()
|
||||
@@ -88,26 +88,26 @@ func on_presented(payload: Dictionary) -> void:
|
||||
|
||||
|
||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
|
||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面,
|
||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面,
|
||||
# 道具件满位替换子交互同构)。原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役。
|
||||
func present_node_bonus() -> void:
|
||||
func present_node_curio() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
_bonus_context = "reward_node"
|
||||
var node_draw: Dictionary = adventure.node_bonus_candidates()
|
||||
_curio_context = "reward_node"
|
||||
var node_draw: Dictionary = adventure.node_curio_candidates()
|
||||
var node_cands: Array = node_draw.get("candidates", []) as Array
|
||||
_open_modal.call(bonus_overlay)
|
||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
||||
adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array)
|
||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||
_open_modal.call(curio_overlay)
|
||||
curio_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
||||
adventure.present_node_curio(node_cands, node_draw.get("aux_detail", []) as Array)
|
||||
curio_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||
|
||||
|
||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
|
||||
func show_pick() -> void:
|
||||
var payload: Dictionary = _pending_bonus
|
||||
_open_modal.call(bonus_overlay)
|
||||
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
||||
var payload: Dictionary = _pending_curio
|
||||
_open_modal.call(curio_overlay)
|
||||
curio_overlay.open_pick(Narrator.curio_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
||||
var adventure = _adventure_get.call()
|
||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||
curio_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||
_refresh_presentations.call()
|
||||
|
||||
|
||||
@@ -157,18 +157,18 @@ func _treasure_card_line(iid: String) -> String:
|
||||
return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)]
|
||||
|
||||
|
||||
# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建,
|
||||
# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 curio_choice_presented 自然重建,
|
||||
# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
||||
func on_refresh() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
if adventure == null:
|
||||
return
|
||||
if not adventure.refresh_bonus():
|
||||
if not adventure.refresh_curio():
|
||||
return
|
||||
if _bonus_context == "reward_node":
|
||||
var st: Dictionary = adventure.node_bonus_pending_state()
|
||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left())
|
||||
if _curio_context == "reward_node":
|
||||
var st: Dictionary = adventure.node_curio_pending_state()
|
||||
curio_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
||||
curio_overlay.set_refresh_info(adventure.attune_refresh_left())
|
||||
|
||||
|
||||
func on_pick_confirmed(item_id: String) -> void:
|
||||
@@ -180,7 +180,7 @@ func on_pick_confirmed(item_id: String) -> void:
|
||||
var kind := parts[0]
|
||||
var tier := parts[1] if parts.size() > 1 else ""
|
||||
var rid := parts[2] if parts.size() > 2 else ""
|
||||
var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid)
|
||||
var ok: bool = adventure.apply_node_curio(kind, tier, rid) if _curio_context == "reward_node" else adventure.pick_curio(kind, tier, rid)
|
||||
if not ok:
|
||||
push_error("[bonus_interact] 三选一兑付被拒——见输出日志")
|
||||
|
||||
@@ -188,8 +188,8 @@ func on_pick_confirmed(item_id: String) -> void:
|
||||
func on_treasure_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.call(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
||||
if not curio_overlay.visible:
|
||||
_open_modal.call(curio_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
||||
_open_replace(items, incoming)
|
||||
|
||||
|
||||
@@ -199,29 +199,29 @@ func _open_replace(items: Array, incoming: Dictionary) -> void:
|
||||
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", "")))),
|
||||
curio_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
||||
named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役)
|
||||
|
||||
|
||||
func on_replace_chosen(discard: int) -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
if adventure == null or not adventure.resolve_bonus_replace(discard):
|
||||
if adventure == null or not adventure.resolve_curio_replace(discard):
|
||||
push_error("[bonus_interact] 同调位替换受理被拒——见输出日志")
|
||||
|
||||
|
||||
# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续)
|
||||
# 兑付反馈(curio_resolved 到达——结果行上屏,唯一出口 = 继续)
|
||||
func on_resolved(result: Dictionary) -> void:
|
||||
_pending_bonus = {}
|
||||
bonus_overlay.show_result(Narrator.bonus_result_text(result))
|
||||
_pending_curio = {}
|
||||
curio_overlay.show_result(Narrator.bonus_result_text(result))
|
||||
_refresh_presentations.call()
|
||||
|
||||
|
||||
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
||||
func on_continue() -> void:
|
||||
_close_modal.call(bonus_overlay)
|
||||
bonus_overlay.close_pick()
|
||||
_close_modal.call(curio_overlay)
|
||||
curio_overlay.close_pick()
|
||||
var adventure = _adventure_get.call()
|
||||
if _bonus_context == "reward_node":
|
||||
if _curio_context == "reward_node":
|
||||
var runner = _runner_get.call()
|
||||
if runner != null:
|
||||
runner.continue_after_node()
|
||||
File renamed without changes.
+19
-19
@@ -38,7 +38,7 @@ const DebugSessionScript := preload("res://presentation/debug_session.gd")
|
||||
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
|
||||
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批)
|
||||
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
|
||||
const BonusInteractController := preload("res://presentation/bonus_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批)
|
||||
const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批)
|
||||
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批)
|
||||
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
|
||||
const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律)
|
||||
@@ -66,7 +66,7 @@ var _settlement_stop_pending: bool: # 挂起态属性化(探针/冒烟直读
|
||||
_settlement_stop_timer.disarm()
|
||||
var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
||||
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
|
||||
var curio_overlay: CurioPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
|
||||
var _start_overlay: Control
|
||||
var _end_overlay: BattleEndOverlay
|
||||
var _inter_overlay: Control
|
||||
@@ -91,7 +91,7 @@ var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道
|
||||
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
|
||||
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
|
||||
var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
|
||||
var _bonus = null # 三选一与替换子交互(bonus_interact_controller.gd——本类只接线与回调注入)
|
||||
var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入)
|
||||
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
|
||||
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
|
||||
|
||||
@@ -261,20 +261,20 @@ func _build_ui() -> void:
|
||||
add_child(settings_overlay)
|
||||
settings_overlay.closed.connect(_on_settings_closed)
|
||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
||||
bonus_overlay = BonusPickOverlay.new()
|
||||
add_child(bonus_overlay)
|
||||
# 提升三选一与替换子交互(T4/D4 拆件批——迁 bonus_interact_controller.gd;本类只接线与回调注入,
|
||||
curio_overlay = CurioPickOverlay.new()
|
||||
add_child(curio_overlay)
|
||||
# 提升三选一与替换子交互(T4/D4 拆件批——迁 curio_interact_controller.gd;本类只接线与回调注入,
|
||||
# ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
|
||||
_bonus = BonusInteractController.new(db, func() -> Variant: return meta, bonus_overlay, _end_overlay, board_view,
|
||||
_bonus = CurioInteractController.new(db, func() -> Variant: return meta, curio_overlay, _end_overlay, board_view,
|
||||
board_zone.toast, func() -> Variant: return save_manager, func() -> Variant: return adventure,
|
||||
func() -> Variant: return runner, func() -> Variant: return _adventure_mode,
|
||||
_open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations,
|
||||
_fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount}))
|
||||
bonus_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed)
|
||||
bonus_overlay.replace_chosen.connect(_bonus.on_replace_chosen)
|
||||
bonus_overlay.continue_pressed.connect(_bonus.on_continue)
|
||||
bonus_overlay.refresh_requested.connect(_bonus.on_refresh)
|
||||
move_child(bonus_overlay, _end_overlay.get_index())
|
||||
curio_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed)
|
||||
curio_overlay.replace_chosen.connect(_bonus.on_replace_chosen)
|
||||
curio_overlay.continue_pressed.connect(_bonus.on_continue)
|
||||
curio_overlay.refresh_requested.connect(_bonus.on_refresh)
|
||||
move_child(curio_overlay, _end_overlay.get_index())
|
||||
|
||||
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
||||
_start_overlay = start.overlay
|
||||
@@ -329,7 +329,7 @@ func _build_ui() -> void:
|
||||
|
||||
# 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位)
|
||||
_modals = ModalStack.new()
|
||||
for m: Control in [legend_overlay, node_overlay, level_screen, bonus_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]:
|
||||
for m: Control in [legend_overlay, node_overlay, level_screen, curio_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]:
|
||||
_register_modal(m)
|
||||
_tune_sim = TuneSimController.new(db, debug_overlay, int(db.hero.max_hp))
|
||||
|
||||
@@ -450,8 +450,8 @@ func _wire_adventure() -> void:
|
||||
adventure.choices_presented.connect(_on_choices_presented)
|
||||
adventure.wallet_changed.connect(_on_wallet_changed)
|
||||
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
|
||||
adventure.bonus_choice_presented.connect(_bonus.on_presented)
|
||||
adventure.bonus_resolved.connect(_bonus.on_resolved)
|
||||
adventure.curio_choice_presented.connect(_bonus.on_presented)
|
||||
adventure.curio_resolved.connect(_bonus.on_resolved)
|
||||
adventure.treasure_replace_presented.connect(_bonus.on_treasure_replace_presented)
|
||||
adventure.adventure_finished.connect(_on_adventure_finished)
|
||||
adventure.level_started.connect(_on_adventure_level_started)
|
||||
@@ -505,9 +505,9 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
||||
_show_board_fx_for_node(t)
|
||||
log_view.append_bb(Narrator.node_banner(runner, node, t))
|
||||
elif t == "reward":
|
||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面)。
|
||||
# T4/D4 拆件:呈现编排归 bonus_interact_controller.present_node_bonus(_bonus_context 随迁)。
|
||||
_bonus.present_node_bonus()
|
||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||
# T4/D4 拆件:呈现编排归 curio_interact_controller.present_node_curio(_curio_context 随迁)。
|
||||
_bonus.present_node_curio()
|
||||
log_view.append_bb(Narrator.reward_line())
|
||||
elif t == "rest":
|
||||
var healed: int = int(info.get("healed", 0))
|
||||
@@ -564,7 +564,7 @@ func _on_battle_end_continue() -> void:
|
||||
_close_modal(_end_overlay)
|
||||
_end_overlay.close()
|
||||
# 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
||||
if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(bonus_interact_controller)
|
||||
if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(curio_interact_controller)
|
||||
return
|
||||
# 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
||||
if _settlement_stop_pending:
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
# 同调位替换子交互。高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发;
|
||||
# 视图零核心引用。交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。
|
||||
# 状态机:pick(三卡点选→确认)→ [replace(满位 N+1 选 N)] → result(兑付反馈→继续)。
|
||||
class_name BonusPickOverlay
|
||||
class_name CurioPickOverlay
|
||||
extends Control
|
||||
|
||||
signal pick_confirmed(item_id: String)
|
||||
@@ -162,7 +162,7 @@ func selected_id() -> String:
|
||||
return _selected
|
||||
|
||||
|
||||
# 兑付反馈态(bonus_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
|
||||
# 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
|
||||
func show_result(text: String) -> void:
|
||||
_label.text = _header + "\n" + text
|
||||
_choice_box.visible = false
|
||||
File renamed without changes.
@@ -19,8 +19,8 @@ var item_slots: HBoxContainer # 秘宝槽(迭代 7 T4 接线—
|
||||
var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
|
||||
var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源)
|
||||
var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新)
|
||||
var _bonus_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
|
||||
var _bonus_list: VBoxContainer
|
||||
var _curio_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
|
||||
var _curio_list: VBoxContainer
|
||||
var _relic_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏)
|
||||
var _relic_list: VBoxContainer
|
||||
var _board_variant_section: VBoxContainer # 地形变异常驻角标(全局作用域变异首发批——空态隐藏)
|
||||
@@ -93,14 +93,14 @@ func _init() -> void:
|
||||
_board_variant_section.add_child(_board_variant_list)
|
||||
# 特性(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入;
|
||||
# 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏
|
||||
_bonus_section = VBoxContainer.new()
|
||||
_bonus_section.add_theme_constant_override("separation", 4)
|
||||
_bonus_section.visible = false
|
||||
col.add_child(_bonus_section)
|
||||
_bonus_section.add_child(UiKit.label("特性", 16, true))
|
||||
_bonus_list = VBoxContainer.new()
|
||||
_bonus_list.add_theme_constant_override("separation", 2)
|
||||
_bonus_section.add_child(_bonus_list)
|
||||
_curio_section = VBoxContainer.new()
|
||||
_curio_section.add_theme_constant_override("separation", 4)
|
||||
_curio_section.visible = false
|
||||
col.add_child(_curio_section)
|
||||
_curio_section.add_child(UiKit.label("特性", 16, true))
|
||||
_curio_list = VBoxContainer.new()
|
||||
_curio_list.add_theme_constant_override("separation", 2)
|
||||
_curio_section.add_child(_curio_list)
|
||||
|
||||
|
||||
func set_attack(atk: int, mod_text: String) -> void:
|
||||
@@ -137,13 +137,13 @@ func set_board_variants(lines: Array) -> void:
|
||||
|
||||
# 特性列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏
|
||||
func set_level_bonuses(lines: Array) -> void:
|
||||
for c in _bonus_list.get_children():
|
||||
_bonus_list.remove_child(c)
|
||||
for c in _curio_list.get_children():
|
||||
_curio_list.remove_child(c)
|
||||
c.queue_free()
|
||||
for line in lines:
|
||||
var l := UiKit.label(String(line), 16, false, COL_INK) # 字号下限(v3.5 批 B3——正文 ≥16)
|
||||
_bonus_list.add_child(l)
|
||||
_bonus_section.visible = not lines.is_empty()
|
||||
_curio_list.add_child(l)
|
||||
_curio_section.visible = not lines.is_empty()
|
||||
|
||||
|
||||
func attack_text() -> String:
|
||||
|
||||
@@ -151,8 +151,8 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
|
||||
adr.wallet_changed.connect(_on_adventure_wallet.bind(st, logger))
|
||||
# 三选一停点驱动(迭代 10 奖励重构批——big_reward_paid 退役:确定性策略候选取首件、
|
||||
# 道具件满位替换放弃新件、兑付后推进下一关——零随机流自开,同种子逐位可复放)
|
||||
adr.bonus_choice_presented.connect(_on_adventure_bonus.bind(st, adr))
|
||||
adr.bonus_resolved.connect(_on_adventure_bonus_resolved.bind(st, adr))
|
||||
adr.curio_choice_presented.connect(_on_adventure_bonus.bind(st, adr))
|
||||
adr.curio_resolved.connect(_on_adventure_curio_resolved.bind(st, adr))
|
||||
adr.treasure_replace_presented.connect(_on_adventure_treasure_replace.bind(st, adr))
|
||||
adr.adventure_finished.connect(_on_adventure_finished.bind(st, logger))
|
||||
adr.set_hero_override(atk_pct, hp_pct)
|
||||
@@ -220,17 +220,17 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr,
|
||||
elif t == "reward":
|
||||
# 奖励节点三选一(迭代 10 追加批——共享池确定性策略;TP0 起 316-f 判读:1 级最稀有、
|
||||
# 同档并列取最左;替换放弃新件;兑付后 continue_after_node 回图选路)
|
||||
var ndraw: Dictionary = adr.node_bonus_candidates()
|
||||
var ndraw: Dictionary = adr.node_curio_candidates()
|
||||
var ncands: Array = ndraw.get("candidates", []) as Array
|
||||
st.reward_events.append({"node_bonus_pick": ncands.duplicate(true)})
|
||||
st.reward_events.append({"node_curio_pick": ncands.duplicate(true)})
|
||||
if ncands.is_empty():
|
||||
r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留)
|
||||
else:
|
||||
adr.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array)
|
||||
adr.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array)
|
||||
var npick: Dictionary = _pick_token(ncands, String(st.get("relic_mode", "off")))
|
||||
if adr.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
|
||||
if not adr._node_bonus_pending.is_empty() and adr._node_bonus_pending.has("incoming"):
|
||||
adr.resolve_bonus_replace(-1) # 满位替换:放弃新件
|
||||
if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
|
||||
if not adr._node_curio_pending.is_empty() and adr._node_curio_pending.has("incoming"):
|
||||
adr.resolve_curio_replace(-1) # 满位替换:放弃新件
|
||||
else:
|
||||
st.result = "advance_failed"
|
||||
elif t == "rest":
|
||||
@@ -260,13 +260,13 @@ static func _on_adventure_wallet(event: String, gp_val: int, extra: Dictionary,
|
||||
# 三选一停点驱动(迭代 10——确定性策略;TP0 起 316-f 判读:1 级最稀有、同档并列取最左;
|
||||
# 奖励事件随 reward_events 留档)
|
||||
static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> void:
|
||||
st.reward_events.append({"bonus_pick": payload.duplicate(true)})
|
||||
st.reward_events.append({"curio_pick": payload.duplicate(true)})
|
||||
var candidates: Array = payload.get("candidates", []) as Array
|
||||
if candidates.is_empty():
|
||||
st.result = "advance_failed"
|
||||
return
|
||||
var pick: Dictionary = _pick_token(candidates, String(st.get("relic_mode", "off")))
|
||||
if not adr.pick_bonus(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))):
|
||||
if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))):
|
||||
st.result = "advance_failed"
|
||||
|
||||
|
||||
@@ -304,13 +304,13 @@ static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictio
|
||||
# 同调位满位替换子交互(确定性策略:放弃新件不获取——分发面按停点上下文裁决节点/通关渠道)
|
||||
static func _on_adventure_treasure_replace(items: Array, incoming: Dictionary, st: Dictionary, adr) -> void:
|
||||
st.reward_events.append({"treasure_replace": incoming.duplicate(true)})
|
||||
if not adr.resolve_bonus_replace(-1):
|
||||
if not adr.resolve_curio_replace(-1):
|
||||
st.result = "advance_failed"
|
||||
|
||||
|
||||
# 三选一兑付完成 → 上下文分派(通关停点=推进下一关 / 奖励节点=回图选路)
|
||||
static func _on_adventure_bonus_resolved(result: Dictionary, st: Dictionary, adr) -> void:
|
||||
st.reward_events.append({"bonus_resolved": result.duplicate(true)})
|
||||
static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr) -> void:
|
||||
st.reward_events.append({"curio_resolved": result.duplicate(true)})
|
||||
if String(result.get("context", "")) == "reward_node":
|
||||
var r = adr.runner()
|
||||
if r == null or not r.continue_after_node():
|
||||
|
||||
+53
-53
@@ -327,12 +327,12 @@ func _smoke_adventure() -> void:
|
||||
var resolves: Array = []
|
||||
var node_picks := 0
|
||||
for ev in (st["reward_events"] as Array):
|
||||
if (ev as Dictionary).has("bonus_pick"):
|
||||
picks.append((ev as Dictionary)["bonus_pick"])
|
||||
elif (ev as Dictionary).has("node_bonus_pick"):
|
||||
if (ev as Dictionary).has("curio_pick"):
|
||||
picks.append((ev as Dictionary)["curio_pick"])
|
||||
elif (ev as Dictionary).has("node_curio_pick"):
|
||||
node_picks += 1 # 奖励节点三选一(追加批——每关奖励节点各 1 次)
|
||||
elif (ev as Dictionary).has("bonus_resolved"):
|
||||
resolves.append((ev as Dictionary)["bonus_resolved"])
|
||||
elif (ev as Dictionary).has("curio_resolved"):
|
||||
resolves.append((ev as Dictionary)["curio_resolved"])
|
||||
check(picks.size() == 2 and resolves.size() == 2 + node_picks,
|
||||
"通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)")
|
||||
check(node_picks >= 1, "奖励节点三选一 ≥1 次(L1 链必经 1 个;L2/L3 图可选路经若干——实际经停 %d)" % node_picks)
|
||||
@@ -467,7 +467,7 @@ func _smoke_adventure() -> void:
|
||||
for ev in wallet:
|
||||
if String(ev["event"]) == "clear":
|
||||
clears += 1
|
||||
elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "bonus_pick":
|
||||
elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "curio_pick":
|
||||
gains += 1 # 战斗完成入账计数(三选一 GP 袋件入账另有 source 标记——不计战斗口径)
|
||||
check(gains == int(st["battles"]), "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" % [gains, int(st["battles"])])
|
||||
check(clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear",
|
||||
@@ -505,13 +505,13 @@ func _smoke_adventure() -> void:
|
||||
check(acc_shop >= 3, "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop)
|
||||
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
|
||||
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关)
|
||||
check(not a8._bonus_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)")
|
||||
check(not a8._curio_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)")
|
||||
check(not a8.continue_to_next_level(), "未兑付推进拒绝(选择停点零自动推进契约)")
|
||||
var l2_cands: Array = a8._bonus_pending["candidates"]
|
||||
var l2_cands: Array = a8._curio_pending["candidates"]
|
||||
var l2_pick: Dictionary = l2_cands[0]
|
||||
check(a8.pick_bonus(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)")
|
||||
var picked2: String = String(a8._bonus_result.get("picked", ""))
|
||||
var expect_acc: int = acc_shop + (int(a8._bonus_result.get("value", 0)) if picked2 == "max_hp" else 0)
|
||||
check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)")
|
||||
var picked2: String = String(a8._curio_result.get("picked", ""))
|
||||
var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0)
|
||||
check(int(a8.bonus.get("max_hp", -1)) == expect_acc,
|
||||
"三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.bonus.get("max_hp", -1)), acc_shop, picked2])
|
||||
var max_eff8: int = a8.effective_max_hp() # T17 口径修复随批:账本生效上限含调试浮窗 pct(a8 已 set 400)
|
||||
@@ -545,11 +545,11 @@ func _smoke_adventure() -> void:
|
||||
var a11 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(11))
|
||||
check(a11.start_adventure(11, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
|
||||
"剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 blast_kit)")
|
||||
for di in range(a11._bonus_deck.size() - 1, -1, -1):
|
||||
var dk := String((a11._bonus_deck[di] as Dictionary).get("kind", ""))
|
||||
for di in range(a11._curio_deck.size() - 1, -1, -1):
|
||||
var dk := String((a11._curio_deck[di] as Dictionary).get("kind", ""))
|
||||
if dk == "attack" or dk == "ap":
|
||||
a11._bonus_deck.remove_at(di)
|
||||
var draw: Array = a11._draw_bonus_candidates()
|
||||
a11._curio_deck.remove_at(di)
|
||||
var draw: Array = a11._draw_curio_candidates()
|
||||
var draw_kinds := {}
|
||||
for t in draw:
|
||||
draw_kinds[String((t as Dictionary).get("kind", ""))] = true
|
||||
@@ -560,7 +560,7 @@ func _smoke_adventure() -> void:
|
||||
# white-box 测试随停点渠道退役拆除——机械惰性承载,零投放零行为)
|
||||
var treasure_seen := false
|
||||
for i in 40:
|
||||
for t in a11._draw_bonus_candidates():
|
||||
for t in a11._draw_curio_candidates():
|
||||
if String((t as Dictionary).get("kind", "")) == "treasure_item":
|
||||
treasure_seen = true
|
||||
break
|
||||
@@ -569,7 +569,7 @@ func _smoke_adventure() -> void:
|
||||
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||
a12.start_adventure(12, {"first_script": true})
|
||||
var cap := {"payload": {}}
|
||||
a12.bonus_choice_presented.connect(func(p): cap["payload"] = p)
|
||||
a12.curio_choice_presented.connect(func(p): cap["payload"] = p)
|
||||
var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0))
|
||||
a12.ledger["hero_hp"] = max_full
|
||||
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
@@ -579,10 +579,10 @@ func _smoke_adventure() -> void:
|
||||
var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13))
|
||||
check(a13.start_adventure(13, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
|
||||
"节点共享池场景:冒险启动(seed 13)")
|
||||
for di13 in range(a13._bonus_deck.size() - 1, -1, -1):
|
||||
if String((a13._bonus_deck[di13] as Dictionary).get("kind", "")) == "attack":
|
||||
a13._bonus_deck.remove_at(di13)
|
||||
var ndraw: Dictionary = a13.node_bonus_candidates()
|
||||
for di13 in range(a13._curio_deck.size() - 1, -1, -1):
|
||||
if String((a13._curio_deck[di13] as Dictionary).get("kind", "")) == "attack":
|
||||
a13._curio_deck.remove_at(di13)
|
||||
var ndraw: Dictionary = a13.node_curio_candidates()
|
||||
var ncands13: Array = ndraw.get("candidates", []) as Array
|
||||
var has_attack13 := false
|
||||
for t in ncands13:
|
||||
@@ -591,12 +591,12 @@ func _smoke_adventure() -> void:
|
||||
break
|
||||
check(not has_attack13,
|
||||
"奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13))
|
||||
a13.present_node_bonus(ncands13, ndraw.get("aux_detail", []) as Array)
|
||||
a13.present_node_bonus([{"kind": "gp", "tier": "common"}])
|
||||
a13.present_node_curio(ncands13, ndraw.get("aux_detail", []) as Array)
|
||||
a13.present_node_curio([{"kind": "gp", "tier": "common"}])
|
||||
var gp13 := int(a13.ledger.get("gp", 0))
|
||||
check(a13.apply_node_bonus("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")),
|
||||
check(a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")),
|
||||
"奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")))
|
||||
check(a13._node_bonus_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
|
||||
check(a13._node_curio_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
|
||||
var s10: Dictionary = a10.summary
|
||||
check(String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)),
|
||||
"放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)")
|
||||
@@ -612,12 +612,12 @@ func _smoke_relics() -> void:
|
||||
check(a_r0.start_adventure(71, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 71)")
|
||||
var relic_tokens := 0
|
||||
var rarest_tokens := 0
|
||||
for t in a_r0._bonus_deck:
|
||||
for t in a_r0._curio_deck:
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
relic_tokens += 1
|
||||
if String((t as Dictionary).get("tier", "")) == "rarest":
|
||||
rarest_tokens += 1
|
||||
check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._bonus_deck.size() == 125,
|
||||
check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 125,
|
||||
"规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125")
|
||||
check(_db.relics.validate().is_empty(), "relic_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)")
|
||||
# ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点)
|
||||
@@ -646,23 +646,23 @@ func _smoke_relics() -> void:
|
||||
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
|
||||
check(a_r.start_adventure(72, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 72——注入 5 行临时表)")
|
||||
var deck_relic := 0
|
||||
for t in a_r._bonus_deck:
|
||||
for t in a_r._curio_deck:
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
deck_relic += 1
|
||||
check(deck_relic == 5 and a_r._bonus_deck.size() == 103,
|
||||
check(deck_relic == 5 and a_r._curio_deck.size() == 103,
|
||||
"规则改写型特性带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103")
|
||||
var relic_seen: Dictionary = {}
|
||||
for i in 60:
|
||||
for t in a_r._draw_bonus_candidates():
|
||||
for t in a_r._draw_curio_candidates():
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
relic_seen[String(t.get("relic_id", ""))] = true
|
||||
check(relic_seen.size() >= 2, "抽签出 relic 候选(60 次重抽 ≥2 种不同 relic——身份键 relic_id 同档可多件)")
|
||||
a_r._bonus_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||
a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||
var deck_m3 := 0
|
||||
for t in a_r._bonus_deck:
|
||||
for t in a_r._curio_deck:
|
||||
if String((t as Dictionary).get("relic_id", "")) == "m3":
|
||||
deck_m3 += 1
|
||||
check(a_r.pick_bonus("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1,
|
||||
check(a_r.pick_curio("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1,
|
||||
"relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1])
|
||||
# 断点往返(持有集+余库 relic_id 随行)
|
||||
var sm_r = load("res://core/save_manager.gd").new("user://_smoke_relics_bp.json")
|
||||
@@ -771,10 +771,10 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
||||
return false
|
||||
if a._pending_next_level:
|
||||
# 三选一停点确认(迭代 10——确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
||||
if not a._bonus_pending.is_empty():
|
||||
var bcands: Array = a._bonus_pending["candidates"]
|
||||
if not a._curio_pending.is_empty():
|
||||
var bcands: Array = a._curio_pending["candidates"]
|
||||
var bpick: Dictionary = autoplay._pick_token(bcands)
|
||||
a.pick_bonus(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", "")))
|
||||
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", "")))
|
||||
a.continue_to_next_level()
|
||||
continue
|
||||
var r = a.runner()
|
||||
@@ -788,14 +788,14 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
||||
var t := String(r.current_node().get("type", ""))
|
||||
if t == "reward":
|
||||
# 奖励节点三选一(迭代 10 追加批——确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化)
|
||||
var ndraw: Dictionary = a.node_bonus_candidates()
|
||||
var ndraw: Dictionary = a.node_curio_candidates()
|
||||
var ncands: Array = ndraw.get("candidates", []) as Array
|
||||
if not ncands.is_empty():
|
||||
a.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array)
|
||||
a.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array)
|
||||
var npick: Dictionary = autoplay._pick_token(ncands)
|
||||
if a.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
|
||||
if not a._node_bonus_pending.is_empty() and a._node_bonus_pending.has("incoming"):
|
||||
a.resolve_bonus_replace(-1)
|
||||
if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
|
||||
if not a._node_curio_pending.is_empty() and a._node_curio_pending.has("incoming"):
|
||||
a.resolve_curio_replace(-1)
|
||||
r.continue_after_node()
|
||||
elif t == "rest":
|
||||
r.continue_after_node()
|
||||
@@ -3871,13 +3871,13 @@ func _smoke_shop_pool() -> void:
|
||||
var tier := String(buy_c.get("tier", ""))
|
||||
var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"}
|
||||
var gp0: int = int(r8.gp)
|
||||
var taken0: int = int(a8._bonus_taken.get(kind, 0))
|
||||
var taken0: int = int(a8._curio_taken.get(kind, 0))
|
||||
var val0: int = int(a8.bonus.get(String(val_map[kind]), -999))
|
||||
check(r8.buy_pool_item(kind), "购买受理(pool 口——%s|%s)" % [kind, tier])
|
||||
check(int(r8.gp) == gp0 - int(buy_c.get("price", 0)), "钱包扣现价(%d → %d)" % [gp0, int(r8.gp)])
|
||||
check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)),
|
||||
"加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))])
|
||||
check(int(a8._bonus_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._bonus_taken.get(kind, 0))])
|
||||
check(int(a8._curio_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))])
|
||||
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
|
||||
var second: Dictionary = {}
|
||||
for c in r8.shop_shelf():
|
||||
@@ -3899,9 +3899,9 @@ func _smoke_shop_pool() -> void:
|
||||
cut_seen = true
|
||||
check(not cut_seen, "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)")
|
||||
# ⑤ 池空硬闸(库存尽剔出牌库——TP0:white-box 耗尽 attack 令牌后货架零 attack + 消费口拒绝)
|
||||
for di8 in range(a8._bonus_deck.size() - 1, -1, -1):
|
||||
if String((a8._bonus_deck[di8] as Dictionary).get("kind", "")) == "attack":
|
||||
a8._bonus_deck.remove_at(di8)
|
||||
for di8 in range(a8._curio_deck.size() - 1, -1, -1):
|
||||
if String((a8._curio_deck[di8] as Dictionary).get("kind", "")) == "attack":
|
||||
a8._curio_deck.remove_at(di8)
|
||||
var attack_seen := false
|
||||
for i in 40:
|
||||
r8._shop_draw_shelf()
|
||||
@@ -3913,10 +3913,10 @@ func _smoke_shop_pool() -> void:
|
||||
check(not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before,
|
||||
"池空消费拒绝(handler 零副作用——买光即枯竭硬闸)")
|
||||
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
|
||||
a8._bonus_deck.append({"kind": "attack", "tier": "common"})
|
||||
check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_bonus_pick 同口——真单点库)")
|
||||
a8._curio_deck.append({"kind": "attack", "tier": "common"})
|
||||
check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_curio_pick 同口——真单点库)")
|
||||
var reward_attack_seen := false
|
||||
for t6 in a8._draw_bonus_candidates():
|
||||
for t6 in a8._draw_curio_candidates():
|
||||
if String((t6 as Dictionary).get("kind", "")) == "attack":
|
||||
reward_attack_seen = true
|
||||
break
|
||||
@@ -4265,11 +4265,11 @@ func _meta_settle_and_workshop() -> void:
|
||||
a12._attunement = ["pick_refresh"]
|
||||
a12._attune_refresh_x = 1
|
||||
a12._attune_refresh_y = 2
|
||||
a12._bonus_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
|
||||
check(a12.refresh_bonus() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
|
||||
a12._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
|
||||
check(a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
|
||||
"刷新双计数器——X 优先消耗(x1y2 → x0y2)")
|
||||
check(a12.refresh_bonus() and a12.refresh_bonus() and a12._attune_refresh_y == 0
|
||||
and a12.refresh_bonus() == false,
|
||||
check(a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0
|
||||
and a12.refresh_curio() == false,
|
||||
"刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)")
|
||||
a12._reset_refresh_x()
|
||||
check(a12.attune_refresh_left() == 1,
|
||||
|
||||
@@ -192,22 +192,22 @@ func _ready() -> void:
|
||||
main.node_overlay.press_continue()
|
||||
_flags_placed.clear()
|
||||
continue
|
||||
if main.bonus_overlay.visible:
|
||||
# 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配——
|
||||
if main.curio_overlay.visible:
|
||||
# 迭代 10 三选一停点(CurioPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配——
|
||||
# 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/
|
||||
# result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。
|
||||
# T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。
|
||||
if not main._input_locked():
|
||||
_problems.append("三选一停点打开时输入未锁定")
|
||||
if main.bonus_overlay._replace_box.visible:
|
||||
main.bonus_overlay.replace_chosen.emit(-1)
|
||||
elif main.bonus_overlay._continue_btn.visible:
|
||||
main.bonus_overlay.press_continue()
|
||||
elif main.adventure != null and not main.adventure._bonus_pending.is_empty():
|
||||
main.bonus_overlay.press_card(0)
|
||||
main.bonus_overlay.press_confirm()
|
||||
if main.curio_overlay._replace_box.visible:
|
||||
main.curio_overlay.replace_chosen.emit(-1)
|
||||
elif main.curio_overlay._continue_btn.visible:
|
||||
main.curio_overlay.press_continue()
|
||||
elif main.adventure != null and not main.adventure._curio_pending.is_empty():
|
||||
main.curio_overlay.press_card(0)
|
||||
main.curio_overlay.press_confirm()
|
||||
else:
|
||||
main.bonus_overlay.press_continue() # 停点已消费的残面——继续关屏
|
||||
main.curio_overlay.press_continue() # 停点已消费的残面——继续关屏
|
||||
_flags_placed.clear()
|
||||
continue
|
||||
if main.shop_overlay.visible:
|
||||
@@ -324,16 +324,16 @@ func _ready() -> void:
|
||||
main._end_overlay._btn.pressed.emit()
|
||||
else:
|
||||
break
|
||||
elif main.bonus_overlay.visible:
|
||||
if main.bonus_overlay._replace_box.visible:
|
||||
main.bonus_overlay.replace_chosen.emit(-1)
|
||||
elif main.bonus_overlay._continue_btn.visible:
|
||||
main.bonus_overlay.press_continue()
|
||||
elif main.adventure != null and not main.adventure._bonus_pending.is_empty():
|
||||
main.bonus_overlay.press_card(0)
|
||||
main.bonus_overlay.press_confirm()
|
||||
elif main.curio_overlay.visible:
|
||||
if main.curio_overlay._replace_box.visible:
|
||||
main.curio_overlay.replace_chosen.emit(-1)
|
||||
elif main.curio_overlay._continue_btn.visible:
|
||||
main.curio_overlay.press_continue()
|
||||
elif main.adventure != null and not main.adventure._curio_pending.is_empty():
|
||||
main.curio_overlay.press_card(0)
|
||||
main.curio_overlay.press_confirm()
|
||||
else:
|
||||
main.bonus_overlay.press_continue()
|
||||
main.curio_overlay.press_continue()
|
||||
elif main.node_overlay.visible:
|
||||
main.node_overlay.press_continue()
|
||||
elif main.shop_overlay.visible:
|
||||
@@ -609,7 +609,7 @@ func _locktest_tick() -> void:
|
||||
return
|
||||
_locktest_state = 1
|
||||
elif _locktest_state == 1:
|
||||
if main.bonus_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \
|
||||
if main.curio_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \
|
||||
or String(settlement.battle_result) != "":
|
||||
_locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测
|
||||
return
|
||||
|
||||
@@ -80,14 +80,14 @@ func _ready() -> void:
|
||||
main._end_overlay.close()
|
||||
_settle()
|
||||
|
||||
main.bonus_overlay.open_pick("结构探针·三选一", [
|
||||
main.curio_overlay.open_pick("结构探针·三选一", [
|
||||
{"id": "probe_heal", "text": "回血\n恢复 3 点"},
|
||||
{"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"},
|
||||
{"id": "probe_pierce", "text": "穿甲\n无视护甲"},
|
||||
])
|
||||
_settle()
|
||||
await _audit("bonus_pick", [main.bonus_overlay])
|
||||
main.bonus_overlay.visible = false
|
||||
await _audit("curio_pick", [main.curio_overlay])
|
||||
main.curio_overlay.visible = false
|
||||
_settle()
|
||||
|
||||
main.shop_overlay.open_shop([
|
||||
|
||||
@@ -93,9 +93,9 @@ func _init() -> void:
|
||||
adr_e.set_save_manager(sm_e)
|
||||
var params_e := {"hero_override": {}, "treasure_unlocked": {"blast_kit": true}}
|
||||
Kit.check_quiet(adr_e.start_adventure(4321, params_e), "P5 冒险启动(seed 4321)")
|
||||
adr_e._bonus_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
|
||||
adr_e._curio_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
|
||||
adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
var cands_e: Array = (adr_e._bonus_pending.get("candidates", []) as Array).duplicate()
|
||||
var cands_e: Array = (adr_e._curio_pending.get("candidates", []) as Array).duplicate()
|
||||
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关通关三选一停点在案(直调 victory——L1 非末关)")
|
||||
var sm_f = SaveManagerScript.new(_bp_path)
|
||||
Kit.check_quiet(sm_f.load_from_disk() and sm_f.has_breakpoint(), "P5 选择停点断点在档(选择停点可中断契约)")
|
||||
@@ -106,24 +106,24 @@ func _init() -> void:
|
||||
var got_cands := {"cands": [] as Array}
|
||||
var adr_f = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
||||
adr_f.set_save_manager(sm_f) # 兑付后停点快照落盘承载(与真机组合根同构)
|
||||
adr_f.bonus_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate())
|
||||
adr_f.curio_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate())
|
||||
Kit.check_quiet(adr_f.resume_adventure(bp_f, params_e), "P5 续玩受理(选择态分支)")
|
||||
Kit.check_quiet(adr_f.runner() == null, "P5 续玩不重跑关卡(三选一停点直回,_start_current_level 未走)")
|
||||
Kit.check_quiet(got_cands["cands"] == cands_e, "P5 续玩候选不重抽(逐位一致)")
|
||||
Kit.check_quiet(int(adr_f._bonus_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)")
|
||||
Kit.check_quiet(int(adr_f._curio_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)")
|
||||
Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)")
|
||||
var pick_e: Dictionary = cands_e[0]
|
||||
Kit.check_quiet(adr_f.pick_bonus(String(pick_e.get("kind", "")), String(pick_e.get("tier", ""))), "P5 点选兑付受理")
|
||||
var picked_e: String = String(adr_f._bonus_result.get("picked", ""))
|
||||
if adr_f._bonus_taken.has(picked_e):
|
||||
Kit.check_quiet(int(adr_f._bonus_taken[picked_e]) == (2 if picked_e == "attack" else 1),
|
||||
Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", ""))), "P5 点选兑付受理")
|
||||
var picked_e: String = String(adr_f._curio_result.get("picked", ""))
|
||||
if adr_f._curio_taken.has(picked_e):
|
||||
Kit.check_quiet(int(adr_f._curio_taken[picked_e]) == (2 if picked_e == "attack" else 1),
|
||||
"P5 兑付计数一致(%s——预置 1 + 本次兑付)" % picked_e)
|
||||
Kit.check_quiet(sm_f.has_breakpoint(), "P5 兑付后停点快照在档(已兑付待推进态可中断)")
|
||||
var bp_g: Dictionary = sm_f.breakpoint_snapshot()
|
||||
var adr_g = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
||||
adr_g.set_save_manager(sm_f) # 已兑付态续玩的后续快照同承载
|
||||
var got_result := {"r": {}}
|
||||
adr_g.bonus_resolved.connect(func(r): got_result["r"] = r.duplicate())
|
||||
adr_g.curio_resolved.connect(func(r): got_result["r"] = r.duplicate())
|
||||
Kit.check_quiet(adr_g.resume_adventure(bp_g, params_e), "P5 已兑付态续玩受理")
|
||||
Kit.check_quiet(String(got_result["r"].get("picked", "")) == picked_e, "P5 已兑付不重付(resolved 重发同件)")
|
||||
Kit.check_quiet(adr_g.continue_to_next_level(), "P5 已兑付续玩后推进受理(下一关开始)")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extends SceneTree
|
||||
# 三选一覆盖层 UI 直验套件(迭代 7 T5-hotfix 转正;迭代 10 追加批随面迁移改写)——
|
||||
# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 BonusPickOverlay 后,
|
||||
# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 CurioPickOverlay 后,
|
||||
# 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/替换子面板两路/结果态。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
|
||||
# T6 收口批:断言走 test_kit 计数(Kit.check)+ 失败即停早退协议保留(顺序状态机——坏态下后继断言无效)。
|
||||
@@ -18,7 +18,7 @@ func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
var ov = load("res://presentation/views/bonus_pick_overlay.gd").new()
|
||||
var ov = load("res://presentation/views/curio_pick_overlay.gd").new()
|
||||
root.add_child(ov)
|
||||
var picked := []
|
||||
var replaced := []
|
||||
@@ -32,10 +32,9 @@ func _run() -> void:
|
||||
{"id": "gp", "text": "金币袋\n金币 +20"},
|
||||
{"id": "ap", "text": "行动点提升\n每战首轮 AP +1"},
|
||||
])
|
||||
var btns := 0
|
||||
for c in ov._choice_box.get_children():
|
||||
if c is Button:
|
||||
btns += 1
|
||||
# M7 bcard 换装适配(2026-10-05 奇物批 T2 连带):三选一卡自 M7 起为 BCard(PanelContainer) 非 Button——
|
||||
# 原 `c is Button` 计数自 M7 后恒 0(基线红成因,主区 3c4291d 起红在案)。改数 _choice_box 子节点数,断言语义不变(三卡呈现)。
|
||||
var btns: int = ov._choice_box.get_child_count()
|
||||
if not _req("选择态应三卡呈现且确认/继续隐藏(btns=%d)" % btns,
|
||||
ov.visible and btns == 3 and not ov._confirm_btn.visible and not ov._continue_btn.visible):
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user