拆件批v1.3-B1(主角账领域拆分): hero_ledger.gd 整件退役→三新件按领域各归各账——hero_stats.gd(生命/特性/类型轴/生效公式与回血封顶单点/续玩钳制收口 clamp_hp_to_resume_cap/断点片段 hero_hp+relics 两键,构造注入 GainJournal 只读引用消费加算汇总)、gp_wallet.gd(gp 单写原语+片段 gp 键)、gain_journal.gd(获得记录三层+三通道 gain_bonus 单写原语+购买档数/钥石/真视+reset_adventure_accounts+片段六键,record_gain 转私有,_record_gain 词表不变);断点拼盘改三片段并集(BREAKPOINT_FIELDS 28 键序不变,_resume_ledger 三还原各取各键,日记缺键汇总直载/在档推导比对拒载逐字承现);消费方改址——adventure_runner(建件三引用+件出口 hero()→stats()/wallet()/journal()/deck(),start/restart 三件先日记后钱包再置满同序,_resume_params 钳制改 clamp_hp_to_resume_cap,载荷_base 随附钱包读数——停点件零钱包依赖)、level_runner(ctx hero→stats/wallet/journal 三键,私有三件同源成对自建,gp/hero_hp 属性改指三件,effective_ap_bonus→journal,hero()→stats()+journal() 出口,_bind_shop refs 换代)、shop_session(refs wallet+journal,BONUS_AXES 归 GainJournal)、battle_orchestrator(encounter "hero"→"journal" 键+_hero 类型 GainJournal)、curio_stop(构造参 stats+journal,兑付落账 split 指针,头注换代清 HeroLedger 表述)、呈现/探针/模拟六文件 12 处读面改址、测试六文件改址(hero_ledger_test 重写为三件测试——文件名与 manifest 行不变,新增 hs1 零引用源码断言)。验收:全量套件除 round_flow EV 锚 goblin 已知预期红外全绿;bp 对拍 292dumps×28 键 mismatch=0;冒烟 17 项 0 失败〔领域拆分批 V1〕
This commit is contained in:
1 parent
e40aa6f8c1
commit
fde14f1c1c
28 files changed
+679
-532
No files matched your search
@@ -17,7 +17,9 @@ var _state: State = State.IDLE
|
||||
var state: String: # 外部契约面(只读——外部零写点实证;词表 "idle"/"running"/"settling")
|
||||
get:
|
||||
return State.keys()[_state].to_lower()
|
||||
var _hero: HeroLedger # 主角实体账(生命/钱包/加算区/类型轴/特性集/钥石/真视——拆件批 T1 建件)
|
||||
var _stats: HeroStats # 主角数值账(生命/类型轴/特性集——公式单点,拆件批 T1 建件、领域拆分批三件化)
|
||||
var _wallet: GpWallet # GP 钱包件(单写原语——LevelRunner 与 ShopSession 读写单点)
|
||||
var _journal: GainJournal # 加算日记件(获得记录三层/钥石/真视/已购档单写原语)
|
||||
var adventure_counters: Dictionary[Variant, Variant] = {} # 冒险桶(效果引擎 adv_ 计数器宿主字典——跨战持久)
|
||||
|
||||
var level_index := 0 # 当前关卡序号(1 起)
|
||||
@@ -45,16 +47,30 @@ var _shop_mode := "legacy" # 商店模式("legacy"=固定三件
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
db = db_ref
|
||||
rng = rng_ref
|
||||
_hero = HeroLedger.new(db, {})
|
||||
_journal = GainJournal.new()
|
||||
_wallet = GpWallet.new()
|
||||
_stats = HeroStats.new(db, {}, _journal) # 数值账消费日记汇总(同源单件注入)
|
||||
_curio = CurioDeck.new(db, rng)
|
||||
_stop = CurioStop.new(db, _curio, _hero, _snapshot_breakpoint)
|
||||
_stop = CurioStop.new(db, _curio, _stats, _journal, _snapshot_breakpoint)
|
||||
_loadout = AttuneLoadout.new(db, _attunement)
|
||||
adventure_inventory = TreasureItems.new(db)
|
||||
|
||||
|
||||
# 件出口(组件交出非委托壳——呈现/测量面取主角账单点;拆件批接口块)
|
||||
func hero() -> HeroLedger:
|
||||
return _hero
|
||||
# 件出口(组件交出非委托壳——呈现/测量面取主角账三件与令牌池单点;领域拆分批接口块)
|
||||
func stats() -> HeroStats:
|
||||
return _stats
|
||||
|
||||
|
||||
func wallet() -> GpWallet:
|
||||
return _wallet
|
||||
|
||||
|
||||
func journal() -> GainJournal:
|
||||
return _journal
|
||||
|
||||
|
||||
func deck() -> CurioDeck:
|
||||
return _curio
|
||||
|
||||
|
||||
# 件出口(停点件——呈现/测量面取停点编排单点;拆件批接口块)
|
||||
@@ -74,10 +90,12 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
return false
|
||||
master_seed = seed_val
|
||||
rng.new_adventure(seed_val)
|
||||
_hero.rebase_override(params.get("hero_override", {})) # 永久强化 flat 注入(规范化归主角账)
|
||||
_hero.reset_adventure_accounts() # 账面重置(钱包/加算区/已购档/钥石/日记/特性清零+生命置满基础上限)
|
||||
_hero.attack_type = String(params.get("attack_type", "")) # 两根类型轴快照(契约 8 锁死:开局后不随存档选中变化)
|
||||
_hero.armor_type = String(params.get("armor_type", ""))
|
||||
_stats.rebase_override(params.get("hero_override", {})) # 永久强化 flat 注入(规范化归数值账)
|
||||
_journal.reset_adventure_accounts() # 日记域账面清零(汇总/明细/已购档/钥石/真视——先于置满)
|
||||
_wallet.clear_wallet() # 钱包清零
|
||||
_stats.reset_adventure_accounts() # 生命置满基础上限+特性持有集清零(口径不含加算区——日记先清)
|
||||
_stats.attack_type = String(params.get("attack_type", "")) # 两根类型轴快照(契约 8 锁死:开局后不随存档选中变化)
|
||||
_stats.armor_type = String(params.get("armor_type", ""))
|
||||
summary = {}
|
||||
_reset_curio_state()
|
||||
levels_total = db.level_count() # 表访问收口(关数读 level 表行数)
|
||||
@@ -104,7 +122,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集
|
||||
if not feat_seed.is_empty():
|
||||
for r in feat_seed:
|
||||
_hero.feats.append(String(r))
|
||||
_stats.feats.append(String(r))
|
||||
_state = State.RUNNING
|
||||
return _start_current_level()
|
||||
|
||||
@@ -153,10 +171,12 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
return _start_current_level()
|
||||
|
||||
|
||||
# 续玩账面回填(生命/钱包/加算区/已购档/钥石/逐杀明细/特性/真视——主角账片段单点;
|
||||
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
|
||||
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
|
||||
func _resume_ledger(bp: Dictionary, params: Dictionary) -> void:
|
||||
_hero.restore_fragment(bp, params)
|
||||
_stats.restore_fragment(bp, params)
|
||||
_wallet.restore_fragment(bp, params)
|
||||
_journal.restore_fragment(bp, params)
|
||||
summary = {}
|
||||
levels_total = db.level_count() # 表访问收口(关数读 level 表行数)
|
||||
level_index = int(bp.get("level_index", 1))
|
||||
@@ -183,22 +203,21 @@ func _resume_curio_state(bp: Dictionary) -> void:
|
||||
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
|
||||
restored.append(rtok)
|
||||
_curio.deck = restored
|
||||
# 特性持有集还原(缺键 = 旧版断点防御空集——主角账字段单点)
|
||||
_hero.feats = []
|
||||
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
|
||||
_stats.feats = []
|
||||
for r in (bp.get("relics", []) as Array):
|
||||
_hero.feats.append(String(r))
|
||||
_stats.feats.append(String(r))
|
||||
|
||||
# 续玩参数面回填(同 start_adventure 参数面;生命账本越界钳制在此落——clamp 类)
|
||||
func _resume_params(bp: Dictionary, params: Dictionary) -> void:
|
||||
_hero.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归主角账)
|
||||
_hero.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
|
||||
_hero.armor_type = String(params.get("armor_type", ""))
|
||||
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
|
||||
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
|
||||
_stats.armor_type = String(params.get("armor_type", ""))
|
||||
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
|
||||
# (如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
|
||||
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。上限 = 表值+永久
|
||||
# 强化 flat 后乘调试浮窗 pct(保守口径不含加算区——与原钳制式逐字同,加算区恢复后其余战斗照常)。
|
||||
_hero.hero_hp = mini(_hero.hero_hp, maxi(1,
|
||||
int(round(float(int(db.hero.max_hp) + int(_hero.base_override_flat().get("max_hp", 0))) * (100.0 + float(_hero.debug_hp_pct())) / 100.0))))
|
||||
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
|
||||
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
|
||||
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
|
||||
_stats.clamp_hp_to_resume_cap(int(db.hero.max_hp))
|
||||
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
|
||||
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
||||
@@ -219,20 +238,20 @@ func _reset_curio_state() -> void:
|
||||
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
|
||||
_stop.reset_adventure_state()
|
||||
_loadout_seeded = false
|
||||
_hero.feats = []
|
||||
_stats.feats = []
|
||||
adventure_counters = {}
|
||||
|
||||
|
||||
# 调试浮窗百分比通道(测试工具覆盖——每关新 runner 都要应用;正式侧归元进度。单点归主角账)
|
||||
# 调试浮窗百分比通道(测试工具覆盖——每关新 runner 都要应用;正式侧归元进度。单点归数值账)
|
||||
func set_hero_override(atk_pct: int, hp_pct: int) -> void:
|
||||
_hero.set_debug_override(atk_pct, hp_pct)
|
||||
_stats.set_debug_override(atk_pct, hp_pct)
|
||||
if _runner != null:
|
||||
_runner.set_hero_override(atk_pct, hp_pct)
|
||||
|
||||
|
||||
# 冒险账本侧生效上限(公式单点归主角账——置满/回血封顶与战斗内上限同式同源)
|
||||
# 冒险账本侧生效上限(公式单点归数值账——置满/回血封顶与战斗内上限同式同源)
|
||||
func effective_max_hp() -> int:
|
||||
return _hero.effective_max_hp()
|
||||
return _stats.effective_max_hp()
|
||||
|
||||
|
||||
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
|
||||
@@ -245,9 +264,9 @@ func runner() -> LevelRunner:
|
||||
return _runner
|
||||
|
||||
|
||||
# 接口③治疗封顶回报执行体(恢复点节点兑付——按比例回血并封顶)。算术宿主 = HeroLedger.rest_heal(公式单点)
|
||||
# 接口③治疗封顶回报执行体(恢复点节点兑付——按比例回血并封顶)。算术宿主 = HeroStats.rest_heal(公式单点)
|
||||
func apply_rest(pct: int, hp: int, max_hp: int) -> int:
|
||||
return HeroLedger.rest_heal(pct, hp, max_hp)
|
||||
return HeroStats.rest_heal(pct, hp, max_hp)
|
||||
|
||||
|
||||
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
|
||||
@@ -275,7 +294,7 @@ func _find_token(candidates: Array, kind: String, tier: String, feat_id: String
|
||||
# 玩家点选兑付(state 守卫留应用层——编排归停点件;呈现层直调面 T5 改 stop())
|
||||
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
|
||||
func feats_held() -> Array:
|
||||
return _hero.feats_held()
|
||||
return _stats.feats_held()
|
||||
|
||||
|
||||
# ---- 商店共享池消费面(纯池——库存=牌库剩余令牌)----
|
||||
@@ -346,7 +365,7 @@ func _boss_keystone_drop(li: int) -> void:
|
||||
first = not _save_manager.boss_first_kill_marked(li)
|
||||
var row: Dictionary[Variant, Variant] = db.meta_boss_keystone_row(li)
|
||||
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
|
||||
_hero.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
|
||||
_journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
|
||||
|
||||
|
||||
# 主动放弃(仅非战斗期受理——战斗域单事务在飞;与失败同型结算,已获大奖励不追回。
|
||||
@@ -371,7 +390,9 @@ func _start_current_level() -> bool:
|
||||
var pool_wiring: Dictionary[Variant, Variant] = {"deck": _curio.deck, "consume": _stop.consume_pool_purchase,
|
||||
"ticket": _curio.feat_ticket_consider, "cap": _curio.cap_ok} if _shop_mode == "pool" else {}
|
||||
var ctx: Dictionary[Variant, Variant] = {
|
||||
"hero": _hero,
|
||||
"stats": _stats,
|
||||
"wallet": _wallet,
|
||||
"journal": _journal,
|
||||
"loadout": _loadout,
|
||||
"adv_bucket": adventure_counters,
|
||||
"treasure_inventory": adventure_inventory,
|
||||
@@ -379,8 +400,8 @@ func _start_current_level() -> bool:
|
||||
"tree_rules": _tree_rules,
|
||||
"shop_mode": _shop_mode,
|
||||
"treasure_unlocked": _treasure_unlocked,
|
||||
"hero_attack_type": _hero.attack_type,
|
||||
"hero_armor_type": _hero.armor_type,
|
||||
"hero_attack_type": _stats.attack_type,
|
||||
"hero_armor_type": _stats.armor_type,
|
||||
"voucher_rap": int(outbox.get("rap", 0)),
|
||||
"voucher_time_sand": int(outbox.get("time_sand", 0)),
|
||||
"pool_wiring": pool_wiring,
|
||||
@@ -395,7 +416,7 @@ func _start_current_level() -> bool:
|
||||
resume_state = {"node_id": int(_bp_read("node_id", _resume_bp, -1)),
|
||||
"visited_ids": _bp_read("visited_ids", _resume_bp, []) as Array}
|
||||
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
|
||||
# 主角账与冒险桶注入(set_hero——T1 过渡面,T3 收构造注入)。
|
||||
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
|
||||
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
|
||||
# 该回调重取 runner 实例),后入口候选呈现;顺序反了选路口会被呈现层的 runner 空防御静默丢弃。
|
||||
# 呈现零 RNG 消耗、pick 走同一 _enter_node——测量面逐位零漂移。
|
||||
@@ -439,7 +460,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
_last_level_payload = payload.duplicate(true)
|
||||
var result := String(payload.get("result", ""))
|
||||
if result == "defeat":
|
||||
_hero.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
|
||||
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
|
||||
_settle("failed")
|
||||
return
|
||||
if result != "victory":
|
||||
@@ -452,9 +473,11 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
# 关卡完成兑付(非末关恰一次;三层奖励框架):编排归停点件——推进位先于 emit 置位
|
||||
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
|
||||
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
|
||||
# 选择停点零自动推进)
|
||||
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
|
||||
_pending_next_level = true
|
||||
_stop.open_level_clear(payload)
|
||||
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
|
||||
stop_base["gp"] = _wallet.gp
|
||||
_stop.open_level_clear(stop_base)
|
||||
return
|
||||
# 末关的关卡完成:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
|
||||
_settle("completed")
|
||||
@@ -467,13 +490,13 @@ func _settle(result: String) -> void:
|
||||
"cleared": result == "completed",
|
||||
"levels_cleared": levels_total if result == "completed" else level_index - 1,
|
||||
"stopped_at": 0 if result == "completed" else level_index,
|
||||
"keystone_total": _hero.keystone_gained(),
|
||||
"keystone_total": _journal.keystone_gained(),
|
||||
"master_seed": master_seed,
|
||||
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
|
||||
"keystone_drops": _hero.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
||||
"vision_used_delta": _hero.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
|
||||
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
||||
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
|
||||
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
|
||||
"relics": _hero.feats_held(), # additive 扩签(特性终局持有集:清账前快照,成型判读取数面)
|
||||
"relics": _stats.feats_held(), # additive 扩签(特性终局持有集:清账前快照,成型判读取数面)
|
||||
}
|
||||
# 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源;
|
||||
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
|
||||
@@ -551,7 +574,9 @@ func _snapshot_breakpoint() -> void:
|
||||
|
||||
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
|
||||
func _bp_parts() -> Dictionary[Variant, Variant]:
|
||||
var hero_frag: Dictionary[Variant, Variant] = _hero.breakpoint_fragment()
|
||||
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
|
||||
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
|
||||
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
|
||||
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
|
||||
var level_frag: Dictionary[Variant, Variant] = _runner.breakpoint_fragment() if _runner != null else {
|
||||
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
|
||||
@@ -560,32 +585,32 @@ func _bp_parts() -> Dictionary[Variant, Variant]:
|
||||
var parts: Dictionary[Variant, Variant] = {
|
||||
"level_index": level_index,
|
||||
"node_id": int(level_frag.get("node_id", -1)),
|
||||
"hero_hp": int(hero_frag.get("hero_hp", 0)),
|
||||
"bonus": hero_frag.get("bonus", {}),
|
||||
"gp": int(hero_frag.get("gp", 0)),
|
||||
"shop_purchased": hero_frag.get("shop_purchased", {}),
|
||||
"hero_hp": int(stats_frag.get("hero_hp", 0)),
|
||||
"bonus": journal_frag.get("bonus", {}),
|
||||
"gp": int(wallet_frag.get("gp", 0)),
|
||||
"shop_purchased": journal_frag.get("shop_purchased", {}),
|
||||
"shop_shelf": level_frag.get("shop_shelf", []), # 货架入断点(离开店/非店停点 = 空架合法态)
|
||||
"shop_visit_buys": int(level_frag.get("shop_visit_buys", 0)), # 本次进店已购次数(A 链定价输入)
|
||||
"attunement_charges": (_loadout.breakpoint_fragment(_runner.treasure_inventory) if _runner != null and _runner.treasure_inventory != null else {"attunement_charges": {}}).get("attunement_charges", {}), # 同调主动件充能余量随断点(行派生——save v4 域键名策略不变)
|
||||
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
|
||||
"master_seed": master_seed,
|
||||
"rng_counter": rng.counter(),
|
||||
"keystone": int(hero_frag.get("keystone", 0)),
|
||||
"keystone": int(journal_frag.get("keystone", 0)),
|
||||
"visited_ids": level_frag.get("visited_ids", []),
|
||||
"aux_items": level_frag.get("aux_items", []),
|
||||
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
||||
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
||||
"relics": hero_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
|
||||
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
|
||||
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
||||
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
|
||||
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
|
||||
"adventure_counters": adventure_counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
|
||||
"vision_counter_used": int(hero_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
|
||||
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
|
||||
"refresh_stock": int(stop_frag.get("refresh_stock", 0)), # 刷新存量账随断点
|
||||
"attune_refresh_y": int(stop_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
|
||||
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||
"keystone_drops": hero_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
|
||||
"gain_journal": hero_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为正主)
|
||||
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
|
||||
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
|
||||
}
|
||||
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
|
||||
var stray: Array[Variant] = []
|
||||
|
||||
@@ -76,7 +76,7 @@ var _seal_consumed := {} # 兑付资格以危险格实例为
|
||||
# 同调装配运行态(begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
|
||||
var attunement: Array[Variant] = [] # 装备中同调件 item_id 集
|
||||
var loadout = null # 同调配装件(begin_battle 经 enc["loadout"] 持引用;直构 null=零装配——充能面跳过)
|
||||
var _hero: HeroLedger = null # 主角实体账(begin_battle 经 enc["hero"] 持引用;null=调试/单场直构面——账面消费跳过)
|
||||
var _hero: GainJournal = null # 加算日记(begin_battle 经 enc["journal"] 持引用;null=调试/单场直构面——账面消费跳过)
|
||||
var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(装配集 ap_charge_safe 行扫描;满 charge_n 自动兑付当轮 AP+1,无玩家发起入口)
|
||||
# ---- 效果引擎扩签运行态(新效果类型施加面;空订阅零扰动)----
|
||||
var _fx_hero_ac_base := -1 # 主角 AC 基线快照(enc.hero_armor_class;-1=表值兜底未解析)
|
||||
@@ -377,7 +377,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
_mines_hit_last = 0
|
||||
_mines_hit_round = 0
|
||||
loadout = enc.get("loadout", null)
|
||||
_hero = enc.get("hero", null) if enc.get("hero", null) is HeroLedger else null # null=调试直构——账面消费跳过
|
||||
_hero = enc.get("journal", null) if enc.get("journal", null) is GainJournal else null # null=调试直构——账面消费跳过
|
||||
counters_ctx.adventure_bucket = enc.get("adv_bucket", null) if enc.get("adv_bucket", null) is Dictionary else {} # 冒险桶宿主字典(直构兜底自有空字典)
|
||||
var level_ref: Variant = enc.get("level_counters")
|
||||
if level_ref is Dictionary:
|
||||
|
||||
+25
-22
@@ -1,11 +1,12 @@
|
||||
## 三选一停点件:停点编排唯一所有者(冒险域拆件批自 AdventureRunner 迁入——架构原则 9 本域承载)。
|
||||
## 停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途);刷新三源账(X 刷新件每碰三选一
|
||||
## 重给/优先消耗不结转、Y 营地升级行冒险周期池/用完即无、刷新券存量账/冒险级跨停点持久);兑付方法族
|
||||
## (pick/refresh/redeem/skip);凭证出箱(RAP/时沙待注入——战首取走清零);三选一信号面自带。
|
||||
## 三选一停点件:停点编排唯一所有者(架构原则 9 本域承载)。
|
||||
## 停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途);刷新账三源(X 刷新件每碰三选一
|
||||
## 重给/优先消耗不结转、Y 营地升级行冒险周期池/用完即无、刷新券存量账/冒险级跨停点持久——账面归
|
||||
## RefreshBank 件、编排留本件);兑付方法族(pick/refresh/redeem/skip);凭证出箱(RAP/时沙待注入
|
||||
## ——战首取走清零);三选一信号面自带。
|
||||
## 零自动推进停点语义承载:兑付只经 pick/apply 显式入口,停点在途时推进被拒(宿主关序受理读放行谓词)。
|
||||
## 生命周期=每冒险一次(AdventureRunner 冒险开始建;关卡换实例不换件——每关注入时点经 rebind_level_runner)。
|
||||
## 随机只经注入 CurioDeck 的 reward_pick 封闭流;兑付落账经注入 HeroLedger 单写原语;落盘经注入方法
|
||||
## Callable(core↔core 接线禁 lambda 捕获 self)。Core 层零 UI。
|
||||
## 随机只经注入 CurioDeck 的 reward_pick 封闭流;兑付落账经注入主角账件(HeroStats/GainJournal)
|
||||
## 单写原语;落盘经注入方法 Callable(core↔core 接线禁 lambda 捕获 self)。Core 层零 UI。
|
||||
class_name CurioStop
|
||||
extends RefCounted
|
||||
|
||||
@@ -14,7 +15,8 @@ signal curio_resolved(result: Dictionary) ## 三选一兑付完成(
|
||||
|
||||
var db: ConfigDb
|
||||
var _deck: CurioDeck # 令牌池件(抽签/摘牌/cap/凭证判定——纯池)
|
||||
var _hero: HeroLedger # 主角账(兑付落账单写原语——加算区/生命/特性)
|
||||
var _stats: HeroStats # 主角数值账(生命/特性——兑付回血与 relic 入集单写原语)
|
||||
var _journal: GainJournal # 加算日记件(兑付落账单写原语——加算区/钥石读面)
|
||||
var _snapshot_call := Callable() # 落盘回调(宿主断点快照受理——发射点后触发)
|
||||
var _grant_gp := Callable() # 当前关卡对象 grant_curio_gp 方法 Callable(GP 件入账维持现路径与 wallet_changed 发射序——每关注入时点更新,防调死对象)
|
||||
# ---- 停点三态 ----
|
||||
@@ -32,10 +34,11 @@ var _time_sand_queue := 0 # 时沙待注入(下一战首轮 AP
|
||||
var _level_index := 0 # 兑付载荷关卡序号(宿主每关注入——与 AdventureRunner.level_index 同源单写)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, deck_ref: CurioDeck, hero_ref: HeroLedger, snapshot_call: Callable) -> void:
|
||||
func _init(db_ref: ConfigDb, deck_ref: CurioDeck, stats_ref: HeroStats, journal_ref: GainJournal, snapshot_call: Callable) -> void:
|
||||
db = db_ref
|
||||
_deck = deck_ref
|
||||
_hero = hero_ref
|
||||
_stats = stats_ref
|
||||
_journal = journal_ref
|
||||
_snapshot_call = snapshot_call
|
||||
|
||||
|
||||
@@ -53,10 +56,10 @@ func rebind_level_runner(runner: LevelRunner, level_index: int) -> void:
|
||||
#(顺序即契约——回血按当刻账本与上限结算,选择停点零自动推进)。调用方(宿主关序)须先置
|
||||
# 「已兑付待推进」关序位(信号同步派发——处理器内立即确认时推进位必须已就位)。
|
||||
func open_level_clear(payload_base: Dictionary) -> void:
|
||||
var hp_before := _hero.hero_hp
|
||||
var max_eff := _hero.effective_max_hp() # 回血封顶口径含调试浮窗 pct(同战斗内上限)
|
||||
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)
|
||||
_hero.hero_hp = hp_before + heal
|
||||
_stats.hero_hp = hp_before + heal
|
||||
_reset_refresh_x() # 碰三选一重置 X 余量(不结转)
|
||||
_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零)
|
||||
var pick_payload: Dictionary[Variant, Variant] = {
|
||||
@@ -64,10 +67,10 @@ func open_level_clear(payload_base: Dictionary) -> void:
|
||||
"candidates": draw_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=关卡完成渠道只出专长
|
||||
"heal_gained": heal,
|
||||
"hp_before": hp_before,
|
||||
"hp_after": _hero.hero_hp,
|
||||
"keystone_gained": int(_hero.keystone_drop_log().back().get("amount", 0)) if not _hero.keystone_drop_log().is_empty() else 0,
|
||||
"keystone_total": _hero.keystone_gained(),
|
||||
"gp": _hero.gp,
|
||||
"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),
|
||||
@@ -109,7 +112,7 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||||
if kind == "feat_ticket":
|
||||
return _redeem_feat_ticket() # T5 拿到即开——停点候选替换为专长三选一(零摘牌/不进持有集)
|
||||
var value: int = int(db.adventure.curio_value(kind, tier))
|
||||
var hp_before := _hero.hero_hp
|
||||
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
|
||||
@@ -150,8 +153,8 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String =
|
||||
_bump_taken("time_sand")
|
||||
return true
|
||||
if kind == "relic":
|
||||
if not _hero.feats.has(feat_id):
|
||||
_hero.feats.append(feat_id)
|
||||
if not _stats.feats.has(feat_id):
|
||||
_stats.feats.append(feat_id)
|
||||
_bump_taken("relic")
|
||||
_bump_taken("relic:" + feat_id) # 身份维已拿计数(cap 消费面)
|
||||
return true
|
||||
@@ -159,7 +162,7 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String =
|
||||
# adventure_table.CURIO_APPLY_ROUTES 声明路由(bonus 落点键),加新加算类令牌零代码)
|
||||
var sink := db.adventure.curio_apply_route(kind)
|
||||
if sink != "":
|
||||
_hero.gain_bonus(sink, value, "curio_pick", kind) # 加算区单写原语(获得日记明细+汇总同拍)
|
||||
_journal.gain_bonus(sink, value, "curio_pick", kind) # 加算区单写原语(获得日记明细+汇总同拍)
|
||||
_bump_taken(kind)
|
||||
return true
|
||||
# 通道面(副作用形态——显式分支)
|
||||
@@ -169,7 +172,7 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String =
|
||||
_grant_gp.call(value)
|
||||
_bump_taken("gp")
|
||||
"heal":
|
||||
_hero.hero_hp = mini(_hero.hero_hp + value, _hero.effective_max_hp())
|
||||
_stats.hero_hp = mini(_stats.hero_hp + value, _stats.effective_max_hp())
|
||||
_bump_taken("heal")
|
||||
_:
|
||||
return false
|
||||
@@ -190,7 +193,7 @@ func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String
|
||||
"tier": tier,
|
||||
"value": value,
|
||||
"hp_before": hp_before,
|
||||
"hp_after": _hero.hero_hp,
|
||||
"hp_after": _stats.hero_hp,
|
||||
"level_index": _level_index,
|
||||
}
|
||||
_resolved.merge(_feat_payload_keys(feat_id))
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
## 加算日记件:统一冒险加算区与获得记录唯一所有者。获得记录三层结构=获得记录列表
|
||||
## (_record_gain 追加、返回记录 id)→ 属性增益明细(带来源指回)→ 四轴汇总(由明细同步维护;
|
||||
## summary 与断点读汇总,日记为现行事实源)。三写通道统一走 gain_bonus 单写原语:三选一属性件
|
||||
## (source_class="curio_pick")/商店属性商品("shop_product"、kind=product_id)/战末导出
|
||||
## ("battle_export"、kind=轴名)。本局钥石增量与逐杀明细、真视使用记录、商店已购档数记账同域。
|
||||
## 生命与钱包各归 HeroStats/GpWallet——主角账三件各交各的断点片段,拼盘落盘归冒险应用层。
|
||||
## Core 层零 UI;随机零消费。
|
||||
class_name GainJournal
|
||||
extends RefCounted
|
||||
|
||||
# 加算区四轴(键序即断点 bonus 键序——与拆件前共享账置账形态逐位同形)
|
||||
const BONUS_AXES: Array[String] = ["atk", "max_hp", "armor_class", "ap"]
|
||||
|
||||
var _gain_log: Array[Variant] = [] # 获得记录列表(三选一件/商店购买/战末奖励各一条)
|
||||
var _bonus_detail: Array[Variant] = [] # 属性增益明细(带来源指回 gain_id)
|
||||
var _bonus_total: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
var _keystone := 0 # 钥石入账累计(摘要第三件事的账面)
|
||||
var _keystone_drops: Array[Variant] = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops)
|
||||
var _vision_used := 0 # 真视使用记录(本局计数——冒险收束聚合入档)
|
||||
var _purchased: Dictionary[Variant, Variant] = {} # product_id → 已购档数(递增定价输入;冒险开始清零)
|
||||
|
||||
|
||||
# ---- 获得记录三层 ----
|
||||
|
||||
# 获得记录列表追加(三通道各一条——source_class 词表:curio_pick/shop_product/battle_export;
|
||||
# 返回记录 id 供明细来源指回)
|
||||
func _record_gain(source_class: String, kind: String, detail: Dictionary) -> int:
|
||||
var rec_id := _gain_log.size() + 1 # 记录 id = 列表序派生(1 起——恢复面按 max(id) 校正)
|
||||
var rec: Dictionary[Variant, Variant] = {"id": rec_id, "source_class": source_class, "kind": kind}
|
||||
rec.merge(detail.duplicate(true))
|
||||
_gain_log.append(rec)
|
||||
return rec_id
|
||||
|
||||
|
||||
# 统一冒险加算区单写原语(三通道统一入口。明细+汇总同步维护——写时同拍,无事后对账)
|
||||
func gain_bonus(axis: String, value: int, source_class: String, kind: String) -> void:
|
||||
var gain_id := _record_gain(source_class, kind, {"axis": axis, "value": value})
|
||||
_bonus_detail.append({"gain_id": gain_id, "axis": axis, "value": value,
|
||||
"source_class": source_class, "kind": kind})
|
||||
_bonus_total[axis] = int(_bonus_total.get(axis, 0)) + value
|
||||
|
||||
|
||||
# 汇总读面(生效公式/断点/摘要唯一取数口)
|
||||
func bonus_total(axis: String) -> int:
|
||||
return int(_bonus_total.get(axis, 0))
|
||||
|
||||
|
||||
func bonus_snapshot() -> Dictionary[Variant, Variant]:
|
||||
return _bonus_total.duplicate(true)
|
||||
|
||||
|
||||
# 商店已购档数记账(购买条目——递增定价输入;定价读数走 purchase_tier)
|
||||
func record_purchase(product_id: String) -> void:
|
||||
_purchased[product_id] = purchase_tier(product_id) + 1
|
||||
|
||||
|
||||
func purchase_tier(product_id: String) -> int:
|
||||
return int(_purchased.get(product_id, 0))
|
||||
|
||||
|
||||
# 本局钥石增量(Boss 逐杀掉落渠道——首杀/复杀金额由调用方按存档标记判读后入行)
|
||||
func record_keystone_drop(drop: Dictionary) -> void:
|
||||
_keystone_drops.append(drop.duplicate(true))
|
||||
_keystone += int(drop.get("amount", 0))
|
||||
|
||||
|
||||
func keystone_gained() -> int:
|
||||
return _keystone
|
||||
|
||||
|
||||
func keystone_drop_log() -> Array:
|
||||
return _keystone_drops.duplicate(true)
|
||||
|
||||
|
||||
# 真视使用记录(真视类工具受理成功恰计一次;冒险收束聚合入档)
|
||||
func record_vision_use() -> void:
|
||||
_vision_used += 1
|
||||
|
||||
|
||||
func vision_used() -> int:
|
||||
return _vision_used
|
||||
|
||||
|
||||
# 日记域账面重置(冒险开始/单关重开共用值面——宿主须先调本件再调 HeroStats 置满,生命口径
|
||||
# 才不含加算区)
|
||||
func reset_adventure_accounts() -> void:
|
||||
_bonus_total = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
_gain_log = []
|
||||
_bonus_detail = []
|
||||
_purchased = {}
|
||||
_keystone = 0
|
||||
_keystone_drops = []
|
||||
_vision_used = 0
|
||||
|
||||
|
||||
# ---- 断点自交片段(应用层拼盘口收件——本件只交自己的账)----
|
||||
# 片段键:bonus(汇总快照)/shop_purchased/keystone/keystone_drops/vision_counter_used
|
||||
# +获得日记键(唯一声明的断点扩列;旧档缺日记→汇总直载 clamp、在档→明细推导比对不符
|
||||
# push_error 拒载)
|
||||
|
||||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
return {
|
||||
"bonus": bonus_snapshot(),
|
||||
"shop_purchased": _purchased.duplicate(true),
|
||||
"keystone": _keystone,
|
||||
"keystone_drops": keystone_drop_log(),
|
||||
"vision_counter_used": _vision_used,
|
||||
"gain_journal": {"records": _gain_log.duplicate(true), "details": _bonus_detail.duplicate(true)},
|
||||
}
|
||||
|
||||
|
||||
# 片段回填(日记缺键 = 旧档——汇总直载跳过比对;日记在档 = 由明细推导汇总、与留存汇总不符
|
||||
# 即报错中止——日记为现行事实源,汇总仅作一致性校验读数)
|
||||
func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||||
_purchased = (frag.get("shop_purchased", {}) as Dictionary).duplicate(true)
|
||||
_keystone = int(frag.get("keystone", 0))
|
||||
_keystone_drops = (frag.get("keystone_drops", []) as Array).duplicate(true)
|
||||
_vision_used = maxi(0, int(frag.get("vision_counter_used", 0))) # clamp 类(现役读侧策略同款)
|
||||
var journal: Variant = frag.get("gain_journal", null)
|
||||
if journal is Dictionary and not (journal as Dictionary).is_empty():
|
||||
_restore_from_journal(journal as Dictionary, frag)
|
||||
return
|
||||
# 旧档回退:汇总键直载(clamp 类——四轴子键夹 ≥0)
|
||||
_bonus_total = {}
|
||||
var bp_bonus: Dictionary[Variant, Variant] = frag.get("bonus", {}) as Dictionary
|
||||
for axis in BONUS_AXES:
|
||||
_bonus_total[axis] = maxi(0, int(bp_bonus.get(axis, 0)))
|
||||
|
||||
|
||||
# 日记回填(记录列表与明细全量还原;汇总由明细推导——与留存汇总逐轴比对,不符即致命中止)
|
||||
func _restore_from_journal(journal: Dictionary, frag: Dictionary) -> void:
|
||||
_gain_log = (journal.get("records", []) as Array).duplicate(true)
|
||||
_bonus_detail = (journal.get("details", []) as Array).duplicate(true)
|
||||
var derived: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
for d in _bonus_detail:
|
||||
var axis := String((d as Dictionary).get("axis", ""))
|
||||
if not derived.has(axis):
|
||||
push_error("[加算日记] 断点日记明细含未知加算轴: %s(拒绝载入)" % axis)
|
||||
derived.clear()
|
||||
break
|
||||
derived[axis] = int(derived[axis]) + int((d as Dictionary).get("value", 0))
|
||||
if derived.is_empty():
|
||||
return # 已报错中止(fail-fast——明细非法整面拒绝)
|
||||
var stored: Dictionary[Variant, Variant] = frag.get("bonus", {}) as Dictionary
|
||||
for axis in derived.keys():
|
||||
if int(stored.get(axis, -1)) != int(derived[axis]):
|
||||
push_error("[加算日记] 断点汇总与日记推导不一致(axis=%s 汇总=%d 推导=%d)——拒绝载入"
|
||||
% [axis, int(stored.get(axis, -1)), int(derived[axis])])
|
||||
return
|
||||
_bonus_total = derived
|
||||
@@ -0,0 +1 @@
|
||||
uid://chssg25j1e75a
|
||||
@@ -0,0 +1,36 @@
|
||||
## GP 钱包件:冒险局内货币唯一所有者(gain/spend/clear 单写原语;LevelRunner 与 ShopSession
|
||||
## 读写全指此单点)。生命与加算日记各归 HeroStats/GainJournal——主角账三件各交各的断点片段,
|
||||
## 拼盘落盘归冒险应用层。Core 层零 UI;随机零消费。
|
||||
class_name GpWallet
|
||||
extends RefCounted
|
||||
|
||||
var gp: int = 0 # GP 钱包(冒险开始 = 0、收束清零;不入存档不入摘要)
|
||||
|
||||
|
||||
func gain_gp(amount: int) -> void:
|
||||
gp = maxi(0, gp + amount)
|
||||
|
||||
|
||||
# 余额不足 false 零副作用(出账前置校验由调用方守卫时本方法不重复拒绝)
|
||||
func spend_gp(amount: int) -> bool:
|
||||
if amount <= 0 or gp < amount:
|
||||
return false
|
||||
gp = gp - amount
|
||||
return true
|
||||
|
||||
|
||||
func clear_wallet() -> void:
|
||||
gp = 0
|
||||
|
||||
|
||||
# ---- 断点自交片段(应用层拼盘口收件——本件只交自己的账)----
|
||||
# 片段键:gp
|
||||
|
||||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
return {
|
||||
"gp": gp,
|
||||
}
|
||||
|
||||
|
||||
func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||||
gp = int(frag.get("gp", 0))
|
||||
@@ -0,0 +1 @@
|
||||
uid://byi84vj4wyg1y
|
||||
@@ -1,273 +0,0 @@
|
||||
## 主角实体账:冒险生命周期主角数值与获得记录唯一所有者(冒险域拆件批自 AdventureRunner 迁入)。
|
||||
## 生命与 GP 钱包(gain/spend/clear 单写原语);锻造强化快照(永久强化 flat)与调试浮窗百分比通道;
|
||||
## 攻击/甲类型轴快照;特性持有集(活引用注入——关内兑付当关生效);本局钥石增量与逐杀明细;
|
||||
## 真视使用记录;统一冒险加算区(商店购买/三选一属性件/战末导出三通道唯一落点)。
|
||||
## 获得记录三层结构=获得记录列表(record_gain 追加、返回记录 id)→ 属性增益明细(带来源指回)→
|
||||
## 四轴汇总(由明细同步维护;summary 与断点读汇总,日记为正主)。
|
||||
## 生效上限/攻击/护甲公式与回血封顶公式单点归本件(与战斗内上限同式同源,pct=0 时乘法恒等——MC 锚零漂移)。
|
||||
## Core 层零 UI;随机零消费;断点自交片段(账本键+获得日记键)——拼盘落盘归冒险应用层。
|
||||
class_name HeroLedger
|
||||
extends RefCounted
|
||||
|
||||
# 加算区四轴(键序即断点 bonus 键序——与拆件前共享账置账形态逐位同形)
|
||||
const BONUS_AXES: Array[String] = ["atk", "max_hp", "armor_class", "ap"]
|
||||
|
||||
var db: ConfigDb
|
||||
var hero_hp: int = 0 # 生命账(原冒险账本 ledger["hero_hp"] 单值化)
|
||||
var gp: int = 0 # GP 钱包(原 ledger["gp"]——LevelRunner 与 ShopSession 读写全改指此单点)
|
||||
var feats: Array[String] = [] # 特性持有集(活引用注入——关内兑付 relic 须当关生效;收束清零)
|
||||
var attack_type := "" # 攻击类型轴快照(空串=未注入→锻造配置默认值回退)
|
||||
var armor_type := "" # 甲类型轴快照(同上)
|
||||
var _base_override: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
var _override: Array[int] = [0, 0] # 调试浮窗百分比通道 [atk_pct, hp_pct](每关新 runner 应用面)
|
||||
var _gain_log: Array[Variant] = [] # 获得记录列表(三选一件/商店购买/战末奖励各一条)
|
||||
var _bonus_detail: Array[Variant] = [] # 属性增益明细(带来源指回 gain_id)
|
||||
var _bonus_total: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
var _keystone := 0 # 钥石入账累计(摘要第三件事的账面)
|
||||
var _keystone_drops: Array[Variant] = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops)
|
||||
var _vision_used := 0 # 真视使用记录(本局计数——冒险收束聚合入档)
|
||||
var _purchased: Dictionary[Variant, Variant] = {} # product_id → 已购档数(递增定价输入;冒险开始清零)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, base_override_ref: Dictionary) -> void:
|
||||
db = db_ref
|
||||
_base_override = _normalize_override(base_override_ref)
|
||||
|
||||
|
||||
# 永久强化 flat 重注(单关测试模式 start_level 收 base_override_in 时调用——实例不重建,值重注)
|
||||
func rebase_override(raw: Dictionary) -> void:
|
||||
_base_override = _normalize_override(raw)
|
||||
|
||||
|
||||
# 调试浮窗百分比通道(测试工具覆盖——每关新 runner 都要应用;正式侧归元进度)
|
||||
func set_debug_override(atk_pct: int, hp_pct: int) -> void:
|
||||
_override = [atk_pct, hp_pct]
|
||||
|
||||
|
||||
func debug_atk_pct() -> int:
|
||||
return int(_override[0])
|
||||
|
||||
|
||||
func debug_hp_pct() -> int:
|
||||
return int(_override[1])
|
||||
|
||||
|
||||
# 永久强化 flat 快照读面(续玩生命钳制保守上限用——不含加算区)
|
||||
func base_override_flat() -> Dictionary[Variant, Variant]:
|
||||
return _base_override.duplicate(true)
|
||||
|
||||
|
||||
# 注入参数规范化:三字段缺键/-1 = 表值兜底(→ 0 flat);负值一律 clamp 0(白板 = 全零合法态)
|
||||
func _normalize_override(raw: Dictionary) -> Dictionary[Variant, Variant]:
|
||||
var out: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
for k in out.keys():
|
||||
out[k] = maxi(0, int(raw.get(k, 0)))
|
||||
return out
|
||||
|
||||
|
||||
# ---- 生效公式(单点——叠加次序钉死:表值 → 永久强化 flat → 加算区 flat 先加,
|
||||
# 调试浮窗百分比后乘;pct=0 时乘法恒等——MC 锚零漂移)----
|
||||
|
||||
func effective_max_hp() -> int:
|
||||
var base: int = int(db.hero.max_hp) + int(_base_override.get("max_hp", 0)) + bonus_total("max_hp")
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(_override[1])) / 100.0)))
|
||||
|
||||
|
||||
func effective_atk() -> int:
|
||||
var base: int = int(db.hero.atk) + int(_base_override.get("atk", 0)) + bonus_total("atk")
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(_override[0])) / 100.0)))
|
||||
|
||||
|
||||
# 护甲等级生效值(入战 AC 三层可加:甲型基础 AC + 甲路线等级加值 + 加算区 armor_class 轴;
|
||||
# 无调试浮窗百分比通道,纯 flat)
|
||||
func effective_ac() -> int:
|
||||
return maxi(0, _armor_base_ac() + int(_base_override.get("armor_class", 0)) + bonus_total("armor_class"))
|
||||
|
||||
|
||||
# 甲型基础 AC(三层之第一层——选中甲路线行 base_ac;未注入/缺行=锻造配置默认值路线,
|
||||
# 与 hero 表两轴删除前 armor_class=10 行为连续)
|
||||
func _armor_base_ac() -> int:
|
||||
var at: String = armor_type if armor_type != "" else db.forge_default_armor_type()
|
||||
var row: Dictionary[Variant, Variant] = db.forge_armor_route_by_type(at)
|
||||
if row.is_empty():
|
||||
return int(db.hero.armor_class) # 表缺失防御兜底(现役基准)
|
||||
return int(row.get("base_ac", 0))
|
||||
|
||||
|
||||
# 回血公式单点(恢复点节点兑付/账本置满封顶同式;算术宿主——原 LevelRunner.rest_heal 迁入)
|
||||
static func rest_heal(pct: int, hp: int, max_hp: int) -> int:
|
||||
return mini(int(floor(float(max_hp) * float(pct) / 100.0)), max_hp - hp)
|
||||
|
||||
|
||||
# ---- GP 钱包单写原语 ----
|
||||
|
||||
func gain_gp(amount: int) -> void:
|
||||
gp = maxi(0, gp + amount)
|
||||
|
||||
|
||||
# 余额不足 false 零副作用(出账前置校验由调用方守卫时本方法不重复拒绝)
|
||||
func spend_gp(amount: int) -> bool:
|
||||
if amount <= 0 or gp < amount:
|
||||
return false
|
||||
gp = gp - amount
|
||||
return true
|
||||
|
||||
|
||||
func clear_wallet() -> void:
|
||||
gp = 0
|
||||
|
||||
|
||||
# 单关模式重开重置(值重置非实例重建——shop 绑 hero 对象引用不换代;语义=原单关 start_level 重置面)
|
||||
func reset_for_single_level() -> void:
|
||||
_base_override = {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
reset_adventure_accounts()
|
||||
|
||||
|
||||
# 冒险账面重置(冒险开始/单关重开共用值面——生命置满基础上限、钱包与账目清零)
|
||||
func reset_adventure_accounts() -> void:
|
||||
gp = 0
|
||||
_bonus_total = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
_gain_log = []
|
||||
_bonus_detail = []
|
||||
_purchased = {}
|
||||
_keystone = 0
|
||||
_keystone_drops = []
|
||||
_vision_used = 0
|
||||
feats = []
|
||||
hero_hp = effective_max_hp()
|
||||
|
||||
|
||||
# ---- 获得记录三层 ----
|
||||
|
||||
# 获得记录列表追加(三通道各一条——source_class 词表:curio_pick/shop_product/battle_export;
|
||||
# 返回记录 id 供明细来源指回)
|
||||
func record_gain(source_class: String, kind: String, detail: Dictionary) -> int:
|
||||
var rec_id := _gain_log.size() + 1 # 记录 id = 列表序派生(1 起——恢复面按 max(id) 校正)
|
||||
var rec: Dictionary[Variant, Variant] = {"id": rec_id, "source_class": source_class, "kind": kind}
|
||||
rec.merge(detail.duplicate(true))
|
||||
_gain_log.append(rec)
|
||||
return rec_id
|
||||
|
||||
|
||||
# 统一冒险加算区单写原语(三通道统一入口:三选一属性件 source_class="curio_pick";
|
||||
# 商店属性商品 source_class="shop_product"、kind=product_id;战末导出 source_class="battle_export"、
|
||||
# kind=轴名。明细+汇总同步维护——写时同拍,无事后对账)
|
||||
func gain_bonus(axis: String, value: int, source_class: String, kind: String) -> void:
|
||||
var gain_id := record_gain(source_class, kind, {"axis": axis, "value": value})
|
||||
_bonus_detail.append({"gain_id": gain_id, "axis": axis, "value": value,
|
||||
"source_class": source_class, "kind": kind})
|
||||
_bonus_total[axis] = int(_bonus_total.get(axis, 0)) + value
|
||||
|
||||
|
||||
# 汇总读面(生效公式/断点/摘要唯一取数口)
|
||||
func bonus_total(axis: String) -> int:
|
||||
return int(_bonus_total.get(axis, 0))
|
||||
|
||||
|
||||
func bonus_snapshot() -> Dictionary[Variant, Variant]:
|
||||
return _bonus_total.duplicate(true)
|
||||
|
||||
|
||||
# 商店已购档数记账(购买条目——递增定价输入;定价读数走 purchase_tier)
|
||||
func record_purchase(product_id: String) -> void:
|
||||
_purchased[product_id] = purchase_tier(product_id) + 1
|
||||
|
||||
|
||||
func purchase_tier(product_id: String) -> int:
|
||||
return int(_purchased.get(product_id, 0))
|
||||
|
||||
|
||||
func purchased_snapshot() -> Dictionary[Variant, Variant]:
|
||||
return _purchased.duplicate(true)
|
||||
|
||||
|
||||
# 本局钥石增量(Boss 逐杀掉落渠道——首杀/复杀金额由调用方按存档标记判读后入行)
|
||||
func record_keystone_drop(drop: Dictionary) -> void:
|
||||
_keystone_drops.append(drop.duplicate(true))
|
||||
_keystone += int(drop.get("amount", 0))
|
||||
|
||||
|
||||
func keystone_gained() -> int:
|
||||
return _keystone
|
||||
|
||||
|
||||
func keystone_drop_log() -> Array:
|
||||
return _keystone_drops.duplicate(true)
|
||||
|
||||
|
||||
# 真视使用记录(真视类工具受理成功恰计一次;冒险收束聚合入档)
|
||||
func record_vision_use() -> void:
|
||||
_vision_used += 1
|
||||
|
||||
|
||||
func vision_used() -> int:
|
||||
return _vision_used
|
||||
|
||||
|
||||
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
|
||||
func feats_held() -> Array:
|
||||
return feats.duplicate()
|
||||
|
||||
|
||||
# ---- 断点自交片段(应用层拼盘口收件——本件只交自己的账)----
|
||||
# 片段键:hero_hp/bonus(汇总快照)/gp/shop_purchased/keystone/keystone_drops/relics/vision_counter_used
|
||||
# +获得日记键(唯一声明的断点扩列;旧档缺日记→汇总直载跳过比对;日记在档→由日记推导汇总比对)
|
||||
|
||||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
return {
|
||||
"hero_hp": hero_hp,
|
||||
"bonus": bonus_snapshot(),
|
||||
"gp": gp,
|
||||
"shop_purchased": purchased_snapshot(),
|
||||
"keystone": _keystone,
|
||||
"keystone_drops": keystone_drop_log(),
|
||||
"relics": feats.duplicate(),
|
||||
"vision_counter_used": _vision_used,
|
||||
"gain_journal": {"records": _gain_log.duplicate(true), "details": _bonus_detail.duplicate(true)},
|
||||
}
|
||||
|
||||
|
||||
# 片段回填(日记缺键 = 旧档——汇总直载跳过比对;日记在档 = 由明细推导汇总、与留存汇总不符
|
||||
# 即报错中止——日记为正主,汇总仅作一致性校验读数)
|
||||
func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||||
hero_hp = int(frag.get("hero_hp", 0))
|
||||
gp = int(frag.get("gp", 0))
|
||||
_purchased = (frag.get("shop_purchased", {}) as Dictionary).duplicate(true)
|
||||
_keystone = int(frag.get("keystone", 0))
|
||||
_keystone_drops = (frag.get("keystone_drops", []) as Array).duplicate(true)
|
||||
feats = []
|
||||
for r in (frag.get("relics", []) as Array):
|
||||
feats.append(String(r))
|
||||
_vision_used = maxi(0, int(frag.get("vision_counter_used", 0))) # clamp 类(现役读侧策略同款)
|
||||
var journal: Variant = frag.get("gain_journal", null)
|
||||
if journal is Dictionary and not (journal as Dictionary).is_empty():
|
||||
_restore_from_journal(journal as Dictionary, frag)
|
||||
return
|
||||
# 旧档回退:汇总键直载(clamp 类——四轴子键夹 ≥0)
|
||||
_bonus_total = {}
|
||||
var bp_bonus: Dictionary[Variant, Variant] = frag.get("bonus", {}) as Dictionary
|
||||
for axis in BONUS_AXES:
|
||||
_bonus_total[axis] = maxi(0, int(bp_bonus.get(axis, 0)))
|
||||
|
||||
|
||||
# 日记回填(记录列表与明细全量还原;汇总由明细推导——与留存汇总逐轴比对,不符即致命中止)
|
||||
func _restore_from_journal(journal: Dictionary, frag: Dictionary) -> void:
|
||||
_gain_log = (journal.get("records", []) as Array).duplicate(true)
|
||||
_bonus_detail = (journal.get("details", []) as Array).duplicate(true)
|
||||
var derived: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
for d in _bonus_detail:
|
||||
var axis := String((d as Dictionary).get("axis", ""))
|
||||
if not derived.has(axis):
|
||||
push_error("[主角账] 断点日记明细含未知加算轴: %s(拒绝载入)" % axis)
|
||||
derived.clear()
|
||||
break
|
||||
derived[axis] = int(derived[axis]) + int((d as Dictionary).get("value", 0))
|
||||
if derived.is_empty():
|
||||
return # 已报错中止(fail-fast——明细非法整面拒绝)
|
||||
var stored: Dictionary[Variant, Variant] = frag.get("bonus", {}) as Dictionary
|
||||
for axis in derived.keys():
|
||||
if int(stored.get(axis, -1)) != int(derived[axis]):
|
||||
push_error("[主角账] 断点汇总与日记推导不一致(axis=%s 汇总=%d 推导=%d)——拒绝载入"
|
||||
% [axis, int(stored.get(axis, -1)), int(derived[axis])])
|
||||
return
|
||||
_bonus_total = derived
|
||||
@@ -1 +0,0 @@
|
||||
uid://cweny8ulalase
|
||||
@@ -0,0 +1,118 @@
|
||||
## 主角数值账:冒险生命周期主角数值域唯一所有者(生命账与特性持有集、锻造强化快照与调试浮窗
|
||||
## 百分比通道、攻击/甲类型轴快照、生效上限/攻击/护甲公式与回血封顶公式单点、续玩生命钳制收口)。
|
||||
## 生效公式消费加算日记域汇总(注入 GainJournal 只读引用——无反向引用不成环);GP 钱包归
|
||||
## GpWallet、加算日记归 GainJournal——主角账三件各交各的断点片段,拼盘落盘归冒险应用层。
|
||||
## Core 层零 UI;随机零消费。
|
||||
class_name HeroStats
|
||||
extends RefCounted
|
||||
|
||||
var db: ConfigDb
|
||||
var journal: GainJournal # 加算日记只读引用(生效公式的加算区取数口——构造注入,宿主同源单件)
|
||||
var hero_hp: int = 0 # 生命账
|
||||
var feats: Array[String] = [] # 特性持有集(活引用注入——关内兑付 relic 须当关生效;收束清零)
|
||||
var attack_type := "" # 攻击类型轴快照(空串=未注入→锻造配置默认值回退)
|
||||
var armor_type := "" # 甲类型轴快照(同上)
|
||||
var _base_override: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
var _override: Array[int] = [0, 0] # 调试浮窗百分比通道 [atk_pct, hp_pct](每关新 runner 应用面)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, base_override_ref: Dictionary, journal_ref: GainJournal) -> void:
|
||||
db = db_ref
|
||||
journal = journal_ref
|
||||
_base_override = _normalize_override(base_override_ref)
|
||||
|
||||
|
||||
# 永久强化 flat 重注(单关测试模式 start_level 收 base_override_in 时调用——实例不重建,值重注)
|
||||
func rebase_override(raw: Dictionary) -> void:
|
||||
_base_override = _normalize_override(raw)
|
||||
|
||||
|
||||
# 调试浮窗百分比通道(测试工具覆盖——每关新 runner 都要应用;正式侧归元进度)
|
||||
func set_debug_override(atk_pct: int, hp_pct: int) -> void:
|
||||
_override = [atk_pct, hp_pct]
|
||||
|
||||
|
||||
# 续玩生命钳制(断点可能带出高于生效上限的账本值——恢复后钳到保守上限:表值+永久强化 flat
|
||||
# 后乘调试 pct、下限 1;口径不含加算区,加算区恢复后其余战斗照常。原宿主 _resume_params 的
|
||||
# mini 式逐字收进)
|
||||
func clamp_hp_to_resume_cap(db_max_hp: int) -> void:
|
||||
hero_hp = mini(hero_hp, maxi(1,
|
||||
int(round(float(int(db_max_hp) + int(_base_override.get("max_hp", 0))) * (100.0 + float(_override[1])) / 100.0))))
|
||||
|
||||
|
||||
# 注入参数规范化:三字段缺键/-1 = 表值兜底(→ 0 flat);负值一律 clamp 0(白板 = 全零合法态)
|
||||
func _normalize_override(raw: Dictionary) -> Dictionary[Variant, Variant]:
|
||||
var out: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
for k in out.keys():
|
||||
out[k] = maxi(0, int(raw.get(k, 0)))
|
||||
return out
|
||||
|
||||
|
||||
# ---- 生效公式(单点——叠加次序钉死:表值 → 永久强化 flat → 加算区 flat 先加,
|
||||
# 调试浮窗百分比后乘;pct=0 时乘法恒等——MC 锚零漂移)----
|
||||
|
||||
func effective_max_hp() -> int:
|
||||
var base: int = int(db.hero.max_hp) + int(_base_override.get("max_hp", 0)) + journal.bonus_total("max_hp")
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(_override[1])) / 100.0)))
|
||||
|
||||
|
||||
func effective_atk() -> int:
|
||||
var base: int = int(db.hero.atk) + int(_base_override.get("atk", 0)) + journal.bonus_total("atk")
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(_override[0])) / 100.0)))
|
||||
|
||||
|
||||
# 护甲等级生效值(入战 AC 三层可加:甲型基础 AC + 甲路线等级加值 + 加算区 armor_class 轴;
|
||||
# 无调试浮窗百分比通道,纯 flat)
|
||||
func effective_ac() -> int:
|
||||
return maxi(0, _armor_base_ac() + int(_base_override.get("armor_class", 0)) + journal.bonus_total("armor_class"))
|
||||
|
||||
|
||||
# 甲型基础 AC(三层之第一层——选中甲路线行 base_ac;未注入/缺行=锻造配置默认值路线,
|
||||
# 与 hero 表两轴删除前 armor_class=10 行为连续)
|
||||
func _armor_base_ac() -> int:
|
||||
var at: String = armor_type if armor_type != "" else db.forge_default_armor_type()
|
||||
var row: Dictionary[Variant, Variant] = db.forge_armor_route_by_type(at)
|
||||
if row.is_empty():
|
||||
return int(db.hero.armor_class) # 表缺失防御兜底(现役基准)
|
||||
return int(row.get("base_ac", 0))
|
||||
|
||||
|
||||
# 回血公式单点(恢复点节点兑付/账本置满封顶同式;算术宿主——原 LevelRunner.rest_heal 迁入)
|
||||
static func rest_heal(pct: int, hp: int, max_hp: int) -> int:
|
||||
return mini(int(floor(float(max_hp) * float(pct) / 100.0)), max_hp - hp)
|
||||
|
||||
|
||||
# 单关模式重开重置(值重置非实例重建——shop 绑定引用不换代;语义=原单关 start_level 重置面。
|
||||
# 钱包/日记清零各归各件,由宿主在本调用前完成——日记先清再置满,生命口径才不含加算区)
|
||||
func reset_for_single_level() -> void:
|
||||
_base_override = {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
reset_adventure_accounts()
|
||||
|
||||
|
||||
# 冒险账面重置(冒险开始/单关重开共用值面——生命置满基础上限、特性持有集清零)
|
||||
func reset_adventure_accounts() -> void:
|
||||
feats = []
|
||||
hero_hp = effective_max_hp()
|
||||
|
||||
|
||||
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
|
||||
func feats_held() -> Array:
|
||||
return feats.duplicate()
|
||||
|
||||
|
||||
# ---- 断点自交片段(应用层拼盘口收件——本件只交自己的账)----
|
||||
# 片段键:hero_hp/relics(其余账面键归 GpWallet/GainJournal 各自片段)
|
||||
|
||||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
return {
|
||||
"hero_hp": hero_hp,
|
||||
"relics": feats.duplicate(),
|
||||
}
|
||||
|
||||
|
||||
# 片段回填(生命直载;特性持有集逐项归一 String——旧档防御同款)
|
||||
func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||||
hero_hp = int(frag.get("hero_hp", 0))
|
||||
feats = []
|
||||
for r in (frag.get("relics", []) as Array):
|
||||
feats.append(String(r))
|
||||
@@ -0,0 +1 @@
|
||||
uid://boeklf03gti5b
|
||||
+51
-36
@@ -5,7 +5,7 @@
|
||||
## 调试浮窗百分比通道单点归主角账(set_hero_override 转调——公式消费面同源)。
|
||||
## 信号顺序契约:呈现层须在 level_started 回调重取 runner 实例,choices_presented 才不会被空防御丢弃。
|
||||
## 关卡开始信号前零发射:本件全部信号在 level_started 发射前保持静默(构造与 start 收参零信号——
|
||||
## defer_entry 两步式以 level_started 为界,先广播后呈现入口候选;配断言面见冒烟源码断言〔拆件批 V5〕)。
|
||||
## defer_entry 两步式以 level_started 为界,先广播后呈现入口候选;配断言面见冒烟源码断言)。
|
||||
class_name LevelRunner
|
||||
extends RefCounted
|
||||
|
||||
@@ -27,18 +27,20 @@ var node_index := -1
|
||||
var level_counters: Dictionary[Variant, Variant] = {} # 关卡桶(lvl_ 前缀计数器;每关随 LevelRunner new 天然空=按关清零,替代旧硬编码清除名单)
|
||||
var _rest_handler: Callable # 治疗封顶回报执行体(默认内置 rest_heal_amount;冒险模式 = AdventureRunner.apply_rest——接口③)
|
||||
var _external := false # 冒险模式标记(钱包收束时机与播种语义分叉点)
|
||||
# 主角账引用(拆件批 T1 过渡面:冒险模式经 set_hero 注入共享实体账、单关模式 _init 自建私有账;
|
||||
# 构造注入收口归 T3 ctx 面——后置 setter 随构造注入一并退役)
|
||||
var _hero: HeroLedger = HeroLedger.new(null, {}) # 直构缺省私有账(db 随 _init 补)
|
||||
# 主角账三件引用(领域拆分批三件化:冒险模式经 ctx 注入共享三件、单关模式 _init 自建私有件;
|
||||
# 数值账消费日记汇总——私有默认件同源成对建)
|
||||
var _journal: GainJournal
|
||||
var _wallet: GpWallet
|
||||
var _stats: HeroStats
|
||||
var loadout = null # 同调配装件(构造注入;直构=单关零件)
|
||||
var _adv_bucket: Dictionary[Variant, Variant] = {} # 冒险桶(效果引擎 adv_ 计数器宿主字典——冒险模式随 hero 注入;单关=本实例自有)
|
||||
# hero_hp / gp 属性化(既有读写点零改动——值类型经属性落主角账)
|
||||
var _adv_bucket: Dictionary[Variant, Variant] = {} # 冒险桶(效果引擎 adv_ 计数器宿主字典——冒险模式随 ctx 注入;单关=本实例自有)
|
||||
# hero_hp / gp 属性化(既有读写点零改动——值类型经属性落主角账三件)
|
||||
var hero_hp: int:
|
||||
get: return _hero.hero_hp
|
||||
set(v): _hero.hero_hp = v
|
||||
get: return _stats.hero_hp
|
||||
set(v): _stats.hero_hp = v
|
||||
var gp: int: # GP 冒险钱包(局内货币——冒险开始 = 0、收束清零;不入存档不入摘要)
|
||||
get: return _hero.gp
|
||||
set(v): _hero.gp = v
|
||||
get: return _wallet.gp
|
||||
set(v): _wallet.gp = v
|
||||
var _shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
|
||||
# 协作对象私有(E1/E3 封装收口):settlement 读取经 current_battle() 成文出口
|
||||
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
|
||||
@@ -60,10 +62,10 @@ var visited: Array[Dictionary] = [] # 已兑付/到达节点
|
||||
var _generator # 参数化长图生成器(惰性建——NodeMapGenerator 持注入的 RngService)
|
||||
|
||||
|
||||
# 构造注入一次给齐(拆件批 T3——后置 setter 群与商店重绑机制退役;_bind_shop 全仓仅本处构造调用)。
|
||||
# 构造注入一次给齐(后置 setter 群与商店重绑机制退役;_bind_shop 全仓仅本处构造调用)。
|
||||
# ctx 键(冒险模式由 AdventureRunner._start_current_level 组包;缺省空字典 = 单关测试模式:
|
||||
# 私有主角账自建〔空 override〕、充能账瘦壳自建、冒险桶本实例自有、pool 模式零注入):
|
||||
# hero:HeroLedger(共享主角账——活引用)/loadout:AttuneLoadout(同调配装件)/adv_bucket:Dictionary(冒险桶宿主字典)
|
||||
# 私有主角账三件自建〔空 override、数值账同源消费私有日记〕、冒险桶本实例自有、pool 模式零注入):
|
||||
# stats:HeroStats/wallet:GpWallet/journal:GainJournal(共享主角账三件——活引用)/loadout:AttuneLoadout(同调配装件)/adv_bucket:Dictionary(冒险桶宿主字典)
|
||||
# attunement:Array(同调装配集——活引用)/tree_rules:Array(树 rule 键集——活引用)/shop_mode:String("pool" 才注入池面)
|
||||
# treasure_unlocked:Dictionary/hero_attack_type:String/hero_armor_type:String
|
||||
# voucher_rap:int/voucher_time_sand:int(凭证出箱注入面)
|
||||
@@ -71,11 +73,16 @@ var _generator # 参数化长图生成器(惰性建——NodeMapGenerator
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> void:
|
||||
db = db_ref
|
||||
rng = rng_ref
|
||||
if ctx.get("hero", null) is HeroLedger:
|
||||
_hero = ctx["hero"]
|
||||
_feats = _hero.feats # 特性集活引用注入(关内兑付 relic 当关生效——契约 6)
|
||||
if ctx.get("stats", null) is HeroStats:
|
||||
_stats = ctx["stats"]
|
||||
_wallet = ctx["wallet"] if ctx.get("wallet", null) is GpWallet else GpWallet.new()
|
||||
_journal = ctx["journal"] if ctx.get("journal", null) is GainJournal else GainJournal.new()
|
||||
_feats = _stats.feats # 特性集活引用注入(关内兑付 relic 当关生效——契约 6)
|
||||
else:
|
||||
_hero.db = db # 直构私有账补表引用(单关模式——实例不重建,值重置走 start_level)
|
||||
_journal = GainJournal.new() # 直构私有三件(单关模式——实例不重建,值重置走 start_level)
|
||||
_wallet = GpWallet.new()
|
||||
_stats = HeroStats.new(null, {}, _journal)
|
||||
_stats.db = db # 直构私有账补表引用(单关模式——实例不重建,值重置走 start_level)
|
||||
var loadout_ref: Variant = ctx.get("loadout", null)
|
||||
loadout = loadout_ref if loadout_ref is AttuneLoadout else null # 直构=单关零 loadout(充能面跳过)
|
||||
if ctx.get("adv_bucket", null) is Dictionary:
|
||||
@@ -105,31 +112,31 @@ func _is_running() -> bool:
|
||||
# ---- 测试面板覆盖(模拟天赋树属性提升——仅测试用,正式侧归 元进度;经 encounter 传入战斗)----
|
||||
|
||||
func set_hero_override(atk_pct: int, hp_pct: int) -> void:
|
||||
_hero.set_debug_override(atk_pct, hp_pct)
|
||||
_stats.set_debug_override(atk_pct, hp_pct)
|
||||
|
||||
|
||||
# 生效公式单点归主角账(拆件批 T1——本类删公式本体,读面转调;叠加次序注释随公式迁主角账)
|
||||
# 生效公式单点归数值账(本类删公式本体,读面转调;叠加次序注释随公式迁数值账)
|
||||
func effective_atk() -> int:
|
||||
return _hero.effective_atk()
|
||||
return _stats.effective_atk()
|
||||
|
||||
|
||||
func effective_max_hp() -> int:
|
||||
return _hero.effective_max_hp()
|
||||
return _stats.effective_max_hp()
|
||||
|
||||
|
||||
func effective_ac() -> int:
|
||||
return _hero.effective_ac()
|
||||
return _stats.effective_ac()
|
||||
|
||||
|
||||
# 行动点加成生效值(统一冒险加算区第四轴 ap;经战斗开始入参 hero_ap_bonus
|
||||
# 行动点加成生效值(统一冒险加算区第四轴;经战斗开始入参 hero_ap_bonus
|
||||
# 传入战斗域,兑现 = 每场战斗开始首轮行动点基线 +加成值〔行动经济 GDD 跨冒险行动点加成轴节〕)
|
||||
func effective_ap_bonus() -> int:
|
||||
return maxi(0, _hero.bonus_total("ap"))
|
||||
return maxi(0, _journal.bonus_total("ap"))
|
||||
|
||||
|
||||
# 覆盖复位(关卡结束归零——表值不可变,仅清百分比;调用方兼容保留)
|
||||
func restore_hero_base() -> void:
|
||||
_hero.set_debug_override(0, 0)
|
||||
_stats.set_debug_override(0, 0)
|
||||
|
||||
|
||||
# ---- 关卡生命周期 ----
|
||||
@@ -152,8 +159,10 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
|
||||
_phase = Phase.IDLE
|
||||
_candidates = []
|
||||
visited = []
|
||||
_hero.rebase_override(base_override_in) # 永久强化 flat 重注(规范化归主角账——契约同冒险开始参数面)
|
||||
_hero.reset_for_single_level() # 账面值重置(钱包/加算区/已购档/钥石/日记清零+生命置满——实例不重建,shop 绑定不换代)
|
||||
_stats.rebase_override(base_override_in) # 永久强化 flat 重注(规范化归数值账——契约同冒险开始参数面)
|
||||
_journal.reset_adventure_accounts() # 日记域账面清零(先于置满——生命口径不含加算区)
|
||||
_wallet.clear_wallet() # 钱包清零
|
||||
_stats.reset_for_single_level() # 数值账面重置(override 清零+生命置满+特性清零——实例不重建,shop 绑定不换代)
|
||||
rng.new_adventure(seed_val) # 关卡起点 = 冒险起点(切片口径;跨关接续归 冒险结构)
|
||||
if not _assign_generated_graph(row, seed_val):
|
||||
_phase = Phase.ENDED
|
||||
@@ -171,7 +180,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
|
||||
# 种子域归冒险结构:本路径不重播种;账本归冒险结构:本路径不重置 gp/加算区/已购档,
|
||||
# 也不重置 hero_hp——账本当前值即开局生命,AdventureRunner 已在主角账写好)。
|
||||
# 流程与 start_level 同款(_find_level_row → 生成图装配 → 状态初始化 → _enter_node(入口)),
|
||||
# 差异仅在恢复执行体的注入与零重置(主角账/冒险桶经 set_hero 注入——T1 过渡面,T3 收构造)。
|
||||
# 差异仅在恢复执行体的注入与零重置(主角账三件/冒险桶经 ctx 构造注入)。
|
||||
# defer_entry 尾参(缺省 false = 直接进节点)——true 时不自动
|
||||
# _enter_node(entry),改置选路态(awaiting_choice + 单候选 = 入口节点),由 AdventureRunner 在
|
||||
# level_started 之后调 present_entry_choice() 呈现、玩家 pick_next_node 显式进首战。拆两步的
|
||||
@@ -302,7 +311,7 @@ func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
# "visited_ids": Array[int], "aux_items": Array};空字典 = 既有路径逐位不变)。
|
||||
# 返回 resume_state 形态供 start_level_external 分派(_restore_to_stop / restore_treasure_items)。
|
||||
func restore_fragment(resume_state: Dictionary) -> Dictionary:
|
||||
var aux: Array = resume_state.get("aux_items", []) as Array
|
||||
var aux: Array[Variant] = resume_state.get("aux_items", []) as Array
|
||||
if not aux.is_empty():
|
||||
restore_treasure_items(aux)
|
||||
return resume_state
|
||||
@@ -526,7 +535,7 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
"attack_type": _hero_attack_type, "armor_type": _hero_armor_type, # 两根类型轴(spawn 英雄快照;空串=锻造配置默认值回退)
|
||||
"attunement": _attunement,
|
||||
"loadout": loadout, # 同调配装件下传(充能单源化——reset_cycle/consume_charge 消费面;直构 null=零装配)
|
||||
"hero": _hero, # 主角实体账下传(战末 bonus 汇入/真视计数单写原语消费面)
|
||||
"journal": _journal, # 加算日记下传(战末 bonus 汇入/真视计数单写原语消费面)
|
||||
"adv_bucket": _adv_bucket, # 冒险桶下传(效果引擎 adv_ 计数器宿主字典——跨战持久同一引用)
|
||||
"level_counters": level_counters, # 关卡桶下传(同关跨战共享引用、随本类 new 天然空)
|
||||
"feats": _feats,
|
||||
@@ -661,9 +670,14 @@ func feats_held() -> Array:
|
||||
return _feats.duplicate()
|
||||
|
||||
|
||||
# 主角账只读出口(加算区生产读面承接—— AdventureRunner.hero() 同款组件交出)
|
||||
func hero() -> HeroLedger:
|
||||
return _hero
|
||||
# 主角数值账只读出口(组件交出—— AdventureRunner.stats() 同款;v1.3 hero() 换代)
|
||||
func stats() -> HeroStats:
|
||||
return _stats
|
||||
|
||||
|
||||
# 加算日记只读出口(bonus 生产读面承接——呈现层选路屏/结算行经此读加算区)
|
||||
func journal() -> GainJournal:
|
||||
return _journal
|
||||
|
||||
|
||||
# 凭证出箱注入面(储备金袋 RAP/时沙 首轮 AP;节点级一次性,battle begin 消费后归零;缺省 0=零扰动)
|
||||
@@ -710,10 +724,11 @@ func close_wallet() -> void:
|
||||
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);本类保留同签名委托与件注入/
|
||||
# 白盒入口——外部调用面与断点取数面零变化。
|
||||
|
||||
# 件注入(建件与宿主引用换代时调用——钱包/主角账/图行/装配集为共享引用,出账直接落同一引用)
|
||||
# 件注入(建件与宿主引用换代时调用——钱包/主角账三件/图行/装配集为共享引用,出账直接落同一引用)
|
||||
func _bind_shop() -> void:
|
||||
_shop.bind({
|
||||
"hero": _hero,
|
||||
"wallet": _wallet,
|
||||
"journal": _journal,
|
||||
"level_row": level_row,
|
||||
"tree_rules": _tree_rules,
|
||||
"attunement": _attunement,
|
||||
@@ -730,7 +745,7 @@ func _emit_wallet_event(event: String, gp_now: int, extra: Dictionary) -> void:
|
||||
|
||||
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/关卡完成回血同式)
|
||||
func _gain_max_hp_flat(flat: int) -> void:
|
||||
hero_hp = mini(hero_hp + flat, effective_max_hp())
|
||||
_stats.hero_hp = mini(_stats.hero_hp + flat, _stats.effective_max_hp())
|
||||
|
||||
|
||||
func shop_products() -> Array:
|
||||
|
||||
+14
-10
@@ -1,6 +1,6 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 商店会话件:关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主。
|
||||
## 钱包/主角账/装配集归宿主(HeroLedger 共享引用经 bind 注入——GP 读写与购买加算走主角账单点);
|
||||
## 钱包/主角账/装配集归宿主(GpWallet/GainJournal 共享引用经 bind 注入——GP 读写走钱包、购买加算走日记单点);
|
||||
## Core 层零 UI 依赖;公开面由 LevelRunner 同签名委托承载(外部调用面零变化);
|
||||
## legacy 模式零 RNG 零池消费(off 对照承载)。
|
||||
class_name ShopSession
|
||||
@@ -63,7 +63,7 @@ func _spend_discounted(price: int) -> int:
|
||||
|
||||
|
||||
func purchased_tier(product_id: String) -> int:
|
||||
return _hero().purchase_tier(product_id)
|
||||
return _journal().purchase_tier(product_id)
|
||||
|
||||
|
||||
# 购买(未知商品/属性键出域/余额不足拒绝——返回 false 零副作用):
|
||||
@@ -74,7 +74,7 @@ func buy_product(product_id: String) -> bool:
|
||||
push_error("[关卡节点地图] 商店房未知商品: %s" % product_id)
|
||||
return false
|
||||
var attr := String(prow.get("attr", ""))
|
||||
if not HeroLedger.BONUS_AXES.has(attr):
|
||||
if not GainJournal.BONUS_AXES.has(attr):
|
||||
push_error("[关卡节点地图] 商品属性键出域: %s(attr=%s)" % [product_id, attr])
|
||||
return false
|
||||
var price := current_price(prow)
|
||||
@@ -83,10 +83,10 @@ func buy_product(product_id: String) -> bool:
|
||||
return false # 余额不足(覆盖层按钮禁用为常态防线——此处兜底拒绝+静默留痕)
|
||||
_set_gp(_gp() - price)
|
||||
var flat := int(prow.get("flat_value", 0))
|
||||
_hero().gain_bonus(attr, flat, "shop_product", product_id) # 加算区单写原语(明细+汇总同拍——wr1-s 同值承载)
|
||||
_journal().gain_bonus(attr, flat, "shop_product", product_id) # 加算区单写原语(明细+汇总同拍——wr1-s 同值承载)
|
||||
if attr == "max_hp":
|
||||
(_hooks["max_hp_gain"] as Callable).call(flat) # 购血:上限与当前生命同步 +Δ(封顶新上限)
|
||||
_hero().record_purchase(product_id)
|
||||
_journal().record_purchase(product_id)
|
||||
_emit("buy", {"product": product_id, "price": price})
|
||||
return true
|
||||
|
||||
@@ -307,18 +307,22 @@ func shop_bp_visit_buys() -> int:
|
||||
return visit_buys
|
||||
|
||||
|
||||
# ---- 宿主引用读取(共享引用绑定面——钱包/账类/图行/装配集)----
|
||||
# ---- 宿主引用读取(共享引用绑定面——钱包/加算日记/图行/装配集)----
|
||||
|
||||
func _gp() -> int:
|
||||
return _hero().gp
|
||||
return _wallet().gp
|
||||
|
||||
|
||||
func _set_gp(v: int) -> void:
|
||||
_hero().gp = v
|
||||
_wallet().gp = v
|
||||
|
||||
|
||||
func _hero() -> HeroLedger:
|
||||
return _refs["hero"] as HeroLedger
|
||||
func _wallet() -> GpWallet:
|
||||
return _refs["wallet"] as GpWallet
|
||||
|
||||
|
||||
func _journal() -> GainJournal:
|
||||
return _refs["journal"] as GainJournal
|
||||
|
||||
|
||||
func _level_index() -> int:
|
||||
|
||||
Reference in new issue
Block a user