Files

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.combat.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.combat.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.crossrun.forge.default_armor_type
var row: Dictionary[Variant, Variant] = db.crossrun.forge.armor_route_by_type(at)
if row.is_empty():
return int(db.combat.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))