424 lines
22 KiB
GDScript
424 lines
22 KiB
GDScript
## 三选一停点件:停点编排唯一所有者(架构原则 9 本域承载)。
|
||
## 停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途);刷新账三源(X 刷新件每碰三选一
|
||
## 重给/优先消耗不结转、Y 营地升级行冒险周期池/用完即无、刷新券存量账/冒险级跨停点持久——账面归
|
||
## RefreshBank 件、编排留本件);兑付方法族(pick/refresh/redeem/skip);凭证出箱(RAP/时沙待注入
|
||
## ——战首取走清零);三选一信号面自带。
|
||
## 零自动推进停点语义承载:兑付只经 pick/apply 显式入口,停点在途时推进被拒(宿主关序受理读放行谓词)。
|
||
## 生命周期=每冒险一次(AdventureRunner 冒险开始建;关卡换实例不换件——每关注入时点经 rebind_level_runner)。
|
||
## 随机只经注入 CurioDeck 的 reward_pick 封闭流;兑付落账经注入主角账件(HeroStats/GainJournal)
|
||
## 单写原语;落盘经注入方法 Callable(core↔core 接线禁 lambda 捕获 self)。Core 层零 UI。
|
||
class_name CurioStop
|
||
extends RefCounted
|
||
|
||
signal curio_choice_presented(payload: Dictionary) ## 专长三选一停点(非末关的关卡完成恰一次;通道白名单后候选恒专长+回血在载荷)——{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}
|
||
|
||
var db: ConfigDb
|
||
var _deck: CurioDeck # 令牌池件(抽签/摘牌/cap/凭证判定——纯池)
|
||
var _stats: HeroStats # 主角数值账(生命/特性——兑付回血与 relic 入集单写原语)
|
||
var _journal: GainJournal # 加算日记件(兑付落账单写原语——加算区/钥石读面)
|
||
var _snapshot_call := Callable() # 落盘回调(宿主断点快照受理——发射点后触发)
|
||
var _grant_gp := Callable() # 当前关卡对象 grant_curio_gp 方法 Callable(GP 件入账维持现路径与 wallet_changed 发射序——每关注入时点更新,防调死对象)
|
||
# ---- 停点三态 ----
|
||
var _pending := {} # 关卡完成三选一在途:{"candidates": Array, "payload": Dictionary}——空 = 无停点
|
||
var _resolved := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付)
|
||
var _node_pending := {} # 奖励节点三选一停点态(共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
||
# ---- 刷新账(三源账与付费计数归 RefreshBank 件——编排留本件:查停点态→bank 支出→重抽重发)----
|
||
var _bank: RefreshBank # 刷新账件(构造注入——零停点态依赖)
|
||
# ---- 凭证出箱(奇物.md 凭证件表——节点级/回合级一次性,战首取走清零)----
|
||
var _pending_rap := 0 # 储备金袋待注入(下一战 RAP +N,战首注入后清零)
|
||
var _time_sand_queue := 0 # 时沙待注入(下一战首轮 AP +N,战首入队后清零)
|
||
var _level_index := 0 # 兑付载荷关卡序号(宿主每关注入——与 AdventureRunner.level_index 同源单写)
|
||
|
||
|
||
func _init(db_ref: ConfigDb, deps: Dictionary, snapshot_call: Callable) -> void:
|
||
db = db_ref
|
||
_deck = deps.get("deck", null) as CurioDeck
|
||
_stats = deps.get("stats", null) as HeroStats
|
||
_journal = deps.get("journal", null) as GainJournal
|
||
_bank = deps.get("bank", null) as RefreshBank
|
||
_snapshot_call = snapshot_call
|
||
|
||
|
||
# 每关注入时点(AdventureRunner._start_current_level 建件接线处调用):关卡序号快照+
|
||
# 当前关卡对象 GP 入账方法 Callable(第二关起绑新 runner——防跨关调死对象)
|
||
func rebind_level_runner(runner: LevelRunner, level_index: int) -> void:
|
||
_level_index = level_index
|
||
if runner != null:
|
||
_grant_gp = runner.grant_curio_gp
|
||
|
||
|
||
# ---- 五组入口之一:关卡完成开停点(宿主结算受理调用——回血先行、抽签、发信号、落盘)----
|
||
|
||
# 关卡完成兑付(非末关恰一次;三层奖励框架):部分回血先行 → 加权洗牌掷 3 → 三选一停点
|
||
#(顺序即契约——回血按当刻账本与上限结算,选择停点零自动推进)。调用方(宿主关序)须先置
|
||
# 「已兑付待推进」关序位(信号同步派发——处理器内立即确认时推进位必须已就位)。
|
||
func open_level_clear(payload_base: Dictionary) -> void:
|
||
var hp_before := _stats.hero_hp
|
||
var max_eff := _stats.effective_max_hp() # 回血封顶口径含调试浮窗 pct(同战斗内上限)
|
||
var heal := mini(int(floor(float(max_eff) * float(int(db.adventure.clear_heal_pct)) / 100.0)), max_eff - hp_before)
|
||
_stats.hero_hp = hp_before + heal
|
||
_bank.reset_x() # 碰三选一重置 X 余量(不结转)
|
||
_bank.reset_paid() # 付费刷计数随停点清零(对齐商店进店清零)
|
||
var pick_payload: Dictionary[Variant, Variant] = {
|
||
"level_index": _level_index,
|
||
"candidates": _deck.draw_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=关卡完成渠道只出专长
|
||
"heal_gained": heal,
|
||
"hp_before": hp_before,
|
||
"hp_after": _stats.hero_hp,
|
||
"keystone_gained": int(_journal.keystone_drop_log().back().get("amount", 0)) if not _journal.keystone_drop_log().is_empty() else 0,
|
||
"keystone_total": _journal.keystone_gained(),
|
||
"gp": int(payload_base.get("gp", 0)), # 钱包读数由宿主随载荷_base 注入(停点件构造参只收 stats+journal——零钱包依赖)
|
||
"aux_cleared": int(payload_base.get("aux_cleared", 0)), # additive 扩签(关卡末清空反馈取数)
|
||
}
|
||
_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
|
||
"payload": pick_payload.duplicate(true)}
|
||
curio_choice_presented.emit(pick_payload)
|
||
if _snapshot_call.is_valid():
|
||
_snapshot_call.call() # 选择停点可中断(契约 5——续玩不重抽不静默丢失)
|
||
|
||
|
||
# ---- 五组入口之二:奖励节点三选一 ----
|
||
|
||
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
||
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
||
func node_curio_candidates() -> Dictionary:
|
||
var cands := _deck.draw_candidates("reward_node")
|
||
return {"candidates": cands}
|
||
|
||
|
||
func present_node_curio(candidates: Array) -> void:
|
||
_bank.reset_x() # 碰三选一重置 X 余量(不结转)
|
||
_bank.reset_paid() # 付费刷计数随停点清零(对齐商店进店清零)
|
||
_node_pending = {"candidates": candidates.duplicate(true)}
|
||
|
||
|
||
# ---- 五组入口之三:兑付族(选择停点零自动推进、重复兑付被拒;兑付恰一件、未选项零生效)----
|
||
|
||
# 玩家点选兑付(呈现层/冒烟/自动模拟确认入口):令牌 ∈ 停点候选(身份键精确匹配——relic 件
|
||
# 另须 feat_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、
|
||
# relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束。
|
||
# 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。
|
||
func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||
if _pending.is_empty():
|
||
push_error("[停点件] pick_curio 拒绝——无待选停点")
|
||
return false
|
||
var candidates: Array[Variant] = _pending.get("candidates", []) as Array
|
||
if _deck.find_token(candidates, kind, tier, feat_id).is_empty():
|
||
push_error("[停点件] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
|
||
return false
|
||
if kind == "feat_ticket":
|
||
return _redeem_feat_ticket() # T5 拿到即开——停点候选替换为专长三选一(零摘牌/不进持有集)
|
||
var value: int = int(db.adventure.curio_value(kind, tier))
|
||
var hp_before := _stats.hero_hp
|
||
if not _apply_curio_pick(kind, tier, value, feat_id):
|
||
push_error("[停点件] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id])
|
||
return false
|
||
_finish_curio_pick(kind, value, hp_before, tier, feat_id)
|
||
return true
|
||
|
||
|
||
# 兑付载荷 relic 伴随键(反馈行取数面;非 relic 件零新增键)
|
||
func _feat_payload_keys(feat_id: String) -> Dictionary:
|
||
if feat_id == "":
|
||
return {}
|
||
var row: Dictionary[Variant, Variant] = db.feat_row(feat_id)
|
||
return {"feat_id": feat_id, "feat_name": String(row.get("display_name", "")), "feat_archetype": String(row.get("archetype", ""))}
|
||
|
||
|
||
# 三选一件兑付共享体(TP0——令牌消费、摘牌单点,含 relic 分支;领域拆分批拆三私有段:
|
||
# 资源件段/路由加算段/副作用通道段,兑付语义与顺序逐字不变):
|
||
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
|
||
# 与账本置满/关卡完成回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
||
# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从牌库摘除该令牌(选中踢池;
|
||
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
|
||
func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool:
|
||
if kind == "relic" and db.feat_row(feat_id).is_empty():
|
||
push_error("[停点件] relic 兑付失败——表无此件: %s" % feat_id)
|
||
return false # 先校验后摘牌(令牌零丢失)
|
||
if not _deck.consume_deck_token(kind, tier, feat_id):
|
||
push_error("[停点件] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, feat_id])
|
||
return false
|
||
if _apply_resource_pick(kind, value, feat_id): # 资源件段(refresh/rap/time_sand/relic)
|
||
return true
|
||
if _apply_route_pick(kind, value): # 路由加算段(CURIO_APPLY_ROUTES 声明路由)
|
||
return true
|
||
return _apply_channel_pick(kind, value) # 副作用通道段(gp/heal 显式分支——未知件 false)
|
||
|
||
|
||
# 资源件段(refresh/rap/time_sand/relic——冒险级资源与凭证账面;非资源件 false 交回主路由)
|
||
func _apply_resource_pick(kind: String, value: int, feat_id: String) -> bool:
|
||
if kind == "refresh":
|
||
_bank.add_refresh_stock(value) # T4 F-1——刷新券入刷新存量账(冒险级;**不写 X 临时账**:X 每停点 reset_x 重置会吞存量;摘牌先于落账=选中即消尽)
|
||
_bump_taken("refresh")
|
||
return true
|
||
if kind == "rap":
|
||
_pending_rap += value # T4——储备金袋:下一战 RAP +N(节点级,战首注入后清零)
|
||
_bump_taken("rap")
|
||
return true
|
||
if kind == "time_sand":
|
||
_time_sand_queue += value # T4——时沙:下一战首轮 AP +N(回合级池增量,不改上限)
|
||
_bump_taken("time_sand")
|
||
return true
|
||
if kind == "relic":
|
||
if not _stats.feats.has(feat_id):
|
||
_stats.feats.append(feat_id)
|
||
_bump_taken("relic")
|
||
_bump_taken("relic:" + feat_id) # 身份维已拿计数(cap 消费面)
|
||
return true
|
||
return false
|
||
|
||
|
||
# 路由加算段(match 收敛进效果类型:属性值提升四类走 adventure_table.CURIO_APPLY_ROUTES 声明
|
||
# 路由(bonus 落点键),加新加算类令牌零代码)
|
||
func _apply_route_pick(kind: String, value: int) -> bool:
|
||
var sink := db.adventure.curio_apply_route(kind)
|
||
if sink == "":
|
||
return false
|
||
_journal.gain_bonus(sink, value, "curio_pick", kind) # 加算区单写原语(获得日记明细+汇总同拍)
|
||
_bump_taken(kind)
|
||
return true
|
||
|
||
|
||
# 副作用通道段(副作用形态——显式分支:GP 件走关卡对象入账回调、恢复件当即回血)
|
||
func _apply_channel_pick(kind: String, value: int) -> bool:
|
||
match kind:
|
||
"gp":
|
||
if _grant_gp.is_valid():
|
||
_grant_gp.call(value)
|
||
_bump_taken("gp")
|
||
"heal":
|
||
_stats.hero_hp = mini(_stats.hero_hp + value, _stats.effective_max_hp())
|
||
_bump_taken("heal")
|
||
_:
|
||
return false
|
||
return true
|
||
|
||
|
||
# 件获取次数计数递增(kind 维度派生账本——池件侧承载)
|
||
func _bump_taken(kind: String) -> void:
|
||
_deck.taken[kind] = int(_deck.taken.get(kind, 0)) + 1
|
||
|
||
|
||
# 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。
|
||
# picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(反馈行取数)
|
||
func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", feat_id: String = "") -> void:
|
||
_pending = {}
|
||
_resolved = {
|
||
"picked": picked,
|
||
"tier": tier,
|
||
"value": value,
|
||
"hp_before": hp_before,
|
||
"hp_after": _stats.hero_hp,
|
||
"level_index": _level_index,
|
||
}
|
||
_resolved.merge(_feat_payload_keys(feat_id))
|
||
curio_resolved.emit(_resolved.duplicate(true))
|
||
if _snapshot_call.is_valid():
|
||
_snapshot_call.call()
|
||
|
||
|
||
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与关卡完成三选一同库;relic 同权入持有集)
|
||
func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||
if kind == "feat_ticket":
|
||
return _redeem_feat_ticket() # T5 拿到即开——节点停点候选替换为专长三选一(呈现层本地重绘)
|
||
if _node_pending.is_empty():
|
||
push_error("[停点件] apply_node_curio 拒绝——无待选停点")
|
||
return false
|
||
var candidates: Array[Variant] = _node_pending.get("candidates", []) as Array
|
||
if _deck.find_token(candidates, kind, tier, feat_id).is_empty():
|
||
push_error("[停点件] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
|
||
return false
|
||
var value: int = int(db.adventure.curio_value(kind, tier))
|
||
if not _apply_curio_pick(kind, tier, value, feat_id):
|
||
push_error("[停点件] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id])
|
||
return false
|
||
_finish_node_bonus(kind, value, tier, feat_id)
|
||
return true
|
||
|
||
|
||
# 停点空候选消化(池空=三选一环节整体跳过:候选 0 件时停点视同已处理,
|
||
# 呈现层跳开选择面直接走后续推进。仅空候选合法;非空停点拒绝——零自动推进契约不破,正常
|
||
# 消化仍只走 pick_curio/apply_node_curio 兑付。触发源=渠道白名单过滤后池空(关卡 Boss 击破=关卡完成
|
||
# 渠道只出专长,专长零配置/旧档 deck 无 relic 余库即空集)。)
|
||
func skip_empty_curio() -> bool:
|
||
if _pending.is_empty():
|
||
return false
|
||
if not (_pending.get("candidates", []) as Array).is_empty():
|
||
return false
|
||
_pending = {}
|
||
return true
|
||
|
||
|
||
# 专长凭证兑付(拿到即开——停点候选替换为专长三选一;不进持有集/零摘牌;限购计数 +1):
|
||
# 节点停点=本地替换(呈现层读在途态重绘);店内无停点=临时停点 + payload.context 标记(呈现层
|
||
# 重 present 无胜利总结面,确认后只关面回店)。
|
||
func _redeem_feat_ticket() -> bool:
|
||
_deck.ticket_sold += 1 # 限购计数(本体在池件——原宿主转发属性迁点)
|
||
_deck.ticket_pity = 0
|
||
var cands := _deck.draw_candidates("level_clear") # 专长池(与关卡完成渠道同白名单)
|
||
if cands.is_empty():
|
||
push_error("[停点件] 专长凭证兑付失败——专长池空集")
|
||
_deck.ticket_sold -= 1
|
||
return false
|
||
if not _node_pending.is_empty():
|
||
_node_pending["candidates"] = cands.duplicate(true)
|
||
return true
|
||
var payload: Dictionary[Variant, Variant] = {"context": "feat_ticket", "candidates": cands.duplicate(true)}
|
||
_pending = {"candidates": cands.duplicate(true), "payload": payload.duplicate(true)}
|
||
curio_choice_presented.emit(payload)
|
||
if _snapshot_call.is_valid():
|
||
_snapshot_call.call()
|
||
return true
|
||
|
||
|
||
# ---- 五组入口之四:刷新编排(三源账与报价归 RefreshBank 件——本件只做停点态校验、
|
||
# 支出分派与重抽重发;重抽消费 reward_pick 既有洗牌流,池规则全保留——不放回/
|
||
# 达上限剔牌/掷定前置直显;停点可中断)----
|
||
|
||
# 节点停点在途态读回(组合根重 present 用——candidates 值拷贝)
|
||
func node_curio_pending_state() -> Dictionary:
|
||
return {
|
||
"candidates": (_node_pending.get("candidates", []) as Array).duplicate(true),
|
||
}
|
||
|
||
|
||
# 三选一刷新(免费位 X/Y——组合根唯一调用口;无停点/无余量 false 零副作用)。关卡完成停点重抽后重发
|
||
# curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。
|
||
func refresh_curio() -> bool:
|
||
return _refresh_curio_common(false)
|
||
|
||
|
||
# 三选一 GP 付费刷新(对齐商店 MC-1——免费位 X/Y 用尽后转付费,同旋钮同曲线同钱包;
|
||
# 组合根先经 curio_stop_pending+钱包支出再调,本口只做计数与重抽)。池规则全保留——不放回/达上限剔牌/掷定前置直显。
|
||
func refresh_curio_paid() -> bool:
|
||
return _refresh_curio_common(true)
|
||
|
||
|
||
# 三选一停点在途谓词(付费路径扣费前置校验——无停点禁扣钱,防空转烧钱包)
|
||
func curio_stop_pending() -> bool:
|
||
return not _pending.is_empty() or not _node_pending.is_empty()
|
||
|
||
|
||
# 放行谓词(宿主关序受理——已兑付待推进且无在途停点)
|
||
func stop_resolved() -> bool:
|
||
return not _resolved.is_empty() and _pending.is_empty()
|
||
|
||
|
||
func _refresh_curio_common(paid: bool) -> bool:
|
||
if not curio_stop_pending():
|
||
return false
|
||
if paid:
|
||
_bank.charge_paid() # 付费刷计数递增(报价曲线输入——账面归刷新账件)
|
||
else:
|
||
if not _bank.spend_free(): # 免费位消耗序 X→Y→存量(三源全空 false 零副作用——序与语义逐字归账件)
|
||
return false
|
||
if not _pending.is_empty():
|
||
var cands := _deck.draw_candidates("level_clear")
|
||
_pending["candidates"] = cands.duplicate(true)
|
||
var payload: Dictionary[Variant, Variant] = (_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||
payload["candidates"] = cands
|
||
payload["refreshed"] = true # 重发载荷带标——呈现层直落选择面,不再重开胜利总结面
|
||
curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||
if _snapshot_call.is_valid():
|
||
_snapshot_call.call()
|
||
return true
|
||
if not _node_pending.is_empty():
|
||
var cands2 := _deck.draw_candidates("reward_node")
|
||
_node_pending["candidates"] = cands2.duplicate(true)
|
||
return true
|
||
return false
|
||
|
||
|
||
func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id: String = "") -> void:
|
||
_node_pending = {}
|
||
var payload := {
|
||
"context": "reward_node",
|
||
"picked": picked,
|
||
"tier": tier,
|
||
"value": value,
|
||
"level_index": _level_index,
|
||
}
|
||
payload.merge(_feat_payload_keys(feat_id))
|
||
curio_resolved.emit(payload)
|
||
|
||
|
||
# 商店共享池消费单点(兑付单点——ShopSession.buy_pool_item 经注入 Callable 调用,回 _apply_curio_pick
|
||
# 同口;专长凭证买下即开零摘牌)。未知 kind 拒绝(货架词表封闭的前置防御)。
|
||
func consume_pool_purchase(kind: String, tier: String) -> bool:
|
||
if kind == "feat_ticket":
|
||
return _redeem_feat_ticket()
|
||
if not db.adventure.CURIO_KINDS.has(kind):
|
||
push_error("[停点件] 商店购买消费拒绝——件不在池词表: %s" % kind)
|
||
return false
|
||
return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
|
||
|
||
|
||
# ---- 冒险级复位(宿主 _reset_curio_state 调用——停点半:池归 deck.reset、停点三态与刷新账清零)----
|
||
func reset_adventure_state() -> void:
|
||
_pending = {}
|
||
_resolved = {}
|
||
_node_pending = {}
|
||
_bank.reset_all() # 刷新三源与付费计数清零(账面归刷新账件)
|
||
_pending_rap = 0
|
||
_time_sand_queue = 0
|
||
|
||
|
||
# 续玩选择停点回填(token_migration 类:旧版断点候选为 kind 字符串,按普通档令牌防御归一;
|
||
# 发射回宿主信号面由调用方承接——本件只载态)
|
||
func restore_pending(bp_pending: Dictionary) -> void:
|
||
var migrated: Array[Variant] = []
|
||
for c in (bp_pending.get("candidates", []) as Array):
|
||
if c is Dictionary:
|
||
# T4——feat_id 随迁移(关卡完成渠道候选=纯专长令牌,身份键 relic|tier|feat_id 三段全要)
|
||
var mig := {"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))}
|
||
if String(mig["kind"]) == "relic":
|
||
mig["feat_id"] = String((c as Dictionary).get("feat_id", ""))
|
||
migrated.append(mig)
|
||
else:
|
||
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
|
||
_pending = {"candidates": migrated}
|
||
var payload: Dictionary[Variant, Variant] = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||
curio_choice_presented.emit(payload)
|
||
|
||
|
||
# ---- 凭证出箱(战首取走清零——LevelRunner.set_voucher_grants 注入面同语义)----
|
||
func voucher_outbox() -> Dictionary:
|
||
var out := {"rap": _pending_rap, "time_sand": _time_sand_queue}
|
||
_pending_rap = 0
|
||
_time_sand_queue = 0
|
||
return out
|
||
|
||
|
||
# 已兑付态清除(宿主 continue_to_next_level 推进受理时调用——重兑付防御复位面)
|
||
func clear_resolved() -> void:
|
||
_resolved = {}
|
||
|
||
|
||
# 已兑付态注入+重发(宿主 resume 已兑付分支调用——置态后重发载荷,信号面归停点件:
|
||
# 续玩不重兑付只重发;原 restore_resolved/reemit_resolved 两步收编一步,时序逐位同)
|
||
func restore_resolved(payload: Dictionary) -> void:
|
||
_resolved = payload.duplicate(true)
|
||
curio_resolved.emit(_resolved.duplicate(true))
|
||
|
||
|
||
# ---- 断点自交片段(应用层拼盘口收件)----
|
||
# 片段键:bonus_pending/bonus_result/feat_ticket_pity/feat_ticket_sold
|
||
#(feat_ticket_* 现役为宿主字段——随停点件迁移同片段承载;refresh_stock/attune_refresh_y 两键
|
||
# 归 RefreshBank 片段,宿主拼盘同一并集)
|
||
|
||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||
return {
|
||
"bonus_pending": _pending.duplicate(true),
|
||
"bonus_result": _resolved.duplicate(true),
|
||
"feat_ticket_pity": _deck.ticket_pity,
|
||
"feat_ticket_sold": _deck.ticket_sold,
|
||
}
|
||
|
||
|
||
# 片段回填(凭证计数;停点在途态与已兑付态归宿主主分支
|
||
# restore_pending/restore_resolved 承接——token_migration 与信号发射时序在彼处)
|
||
func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||
_deck.ticket_pity = int(frag.get("feat_ticket_pity", 0)) # T5 扩列——旧档缺键缺省 0(存档兼容)
|
||
_deck.ticket_sold = int(frag.get("feat_ticket_sold", 0))
|