Files
gplan-2/game/core/hero_stats.gd
T
DKChan fde14f1c1c 拆件批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〕
2026-10-27 08:10:08 +08:00

119 lines
5.8 KiB
GDScript

## 主角数值账:冒险生命周期主角数值域唯一所有者(生命账与特性持有集、锻造强化快照与调试浮窗
## 百分比通道、攻击/甲类型轴快照、生效上限/攻击/护甲公式与回血封顶公式单点、续玩生命钳制收口)。
## 生效公式消费加算日记域汇总(注入 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))