175 lines
7.9 KiB
GDScript
175 lines
7.9 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与门面(垂直切片验证:改表即改玩法,数值零硬编码)。
|
||
## 玩法代码禁止绕过本单点自建 load();行查询归各表类(表管自己的行——2026-10-27 全清设计定稿),
|
||
## 表持有按域收进三容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域),
|
||
## 消费面经 db.<容器>.<表> 直连。「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。
|
||
## headless --script 模式无 autoload——冒烟脚本显式实例化本类;加载失败 is_ready=false 加 fatal 封印,调用方须查。
|
||
class_name ConfigDb
|
||
extends Node
|
||
|
||
const SCHEMA_VERSION := 1
|
||
|
||
const TABLE_MANIFEST := {
|
||
"action_economy": "action_economy.tres",
|
||
"variation": "variation.tres",
|
||
"status_effects": "status_effects.tres",
|
||
"board_gen": "board_gen.tres",
|
||
"enemy": "enemy.tres",
|
||
"skill": "skills.tres",
|
||
"hero": "hero.tres",
|
||
"reward": "reward.tres",
|
||
"settlement": "settlement.tres",
|
||
"level": "level.tres",
|
||
"difficulty": "difficulty.tres",
|
||
"level_gen_params": "level_gen_params.tres", # LevelGenParams 参数档三行(节点图生成器核心批 卡①——五旋钮 provisional)
|
||
"shop": "shop.tres", # ShopTable(表与 schema 已入库归数据面——本处只接线加载与消费)
|
||
"meta": "meta.tres", # MetaTable 四行族(schema 与表同源已入库)
|
||
"meta_tree": "meta_tree.tres", # MetaTreeTable 12 节点(定义表 Rule 4 豁免——天赋树.md v0.8 结构事实源)
|
||
"treasure_items": "treasure_items.tres", # TreasureItemTable 六行(定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
|
||
"adventure": "adventure.tres", # AdventureTable(大奖励与钥石行值单点)
|
||
"feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
|
||
"forge": "forge.tres", # ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面
|
||
}
|
||
|
||
var combat: ConfigDomainCombat # 战斗编排域七表(board_gen/variation/status_effects/enemy/skill/hero/settlement)
|
||
var run: ConfigDomainRun # 冒险 runner 域七表(level/difficulty/level_gen_params/reward/adventure/action_economy/shop)
|
||
var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge)
|
||
|
||
var is_ready := false
|
||
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
||
var _resolver: ConfigDbResolver # R1 难度行解析协作类(门面公有签名不变;load_tables 装配段注入三表)
|
||
var _validator := ConfigDbCrossValidator.new() # 跨表校验协作类(无状态——声明期即建)
|
||
var _reload_gate := false # 数据配置 EC8——战斗结算 唯一调用方
|
||
|
||
|
||
func _init() -> void:
|
||
combat = ConfigDomainCombat.new()
|
||
run = ConfigDomainRun.new()
|
||
crossrun = ConfigDomainCrossrun.new()
|
||
_resolver = ConfigDbResolver.new(push_diag,
|
||
func() -> Variant: return combat.board_gen.rows, func() -> Variant: return combat.enemy.rows, func() -> Variant: return run.difficulty) # 协作类持门面引用(诊断/致命通道回呼)
|
||
|
||
|
||
func push_diag(severity: String, category: String, message: String) -> void:
|
||
_diagnostics.append({"severity": severity, "category": category, "message": message})
|
||
if severity == "warning":
|
||
push_warning("[ConfigDB/%s] %s" % [category, message])
|
||
else:
|
||
push_error("[ConfigDB/%s] %s" % [category, message])
|
||
|
||
|
||
# 运行期数据致命(fail-fast;退出执行归根:核心层不自携
|
||
# quit/crash,越域记 Fatal 级日志后置 _fatal_flag,进程退出决策归组合根(main._ready 表加载
|
||
# 失败已有同通道 fatal 屏+fail-fast 边界)——查询期消费方按返回值安全面走既有默认值回退)
|
||
func _fatal(category: String, message: String) -> void:
|
||
push_diag("fatal", category, message)
|
||
push_error("[ConfigDB/%s] FATAL: %s —— 数据配置致命(退出决策归组合根)" % [category, message])
|
||
_fatal_flag = true
|
||
|
||
|
||
var _fatal_flag := false # 运行期致命封印(退出归根承载;组合根/闸读面)
|
||
|
||
|
||
func is_fatal() -> bool:
|
||
return _fatal_flag
|
||
|
||
|
||
func reset_diagnostics() -> void:
|
||
_diagnostics.clear()
|
||
_resolver.reset_warn_dedup() # R1 告警去重注册表(协作类持钥——清空时点与拆分前一致)
|
||
|
||
|
||
func diagnostics() -> Array:
|
||
return _diagnostics.duplicate()
|
||
|
||
|
||
func diagnostics_count(severity: String) -> int:
|
||
var n := 0
|
||
for d in _diagnostics:
|
||
if d.severity == severity:
|
||
n += 1
|
||
return n
|
||
|
||
|
||
func _ready() -> void:
|
||
load_tables("res://data/tables", false)
|
||
|
||
|
||
func load_tables(root: String, collect_only: bool) -> bool:
|
||
is_ready = false
|
||
_diagnostics.clear()
|
||
_resolver.reset_warn_dedup()
|
||
var loaded := {}
|
||
for key in TABLE_MANIFEST.keys():
|
||
var path := root.path_join(TABLE_MANIFEST[key])
|
||
if not ResourceLoader.exists(path):
|
||
push_diag("error", "missing_file", "表文件缺失: %s" % path)
|
||
continue
|
||
var res: Resource = load(path)
|
||
if res == null:
|
||
push_diag("error", "load_failed", "表加载失败: %s" % path)
|
||
continue
|
||
loaded[key] = res
|
||
for e in res.validate():
|
||
push_diag("error", e.category, "%s → %s" % [key, e.message])
|
||
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)
|
||
_cross_validate(loaded)
|
||
var has_errors := diagnostics_count("error") > 0
|
||
if has_errors and not collect_only:
|
||
# 游戏通道:致命(Rule 6)——is_ready 恒 false,调用方(主场景)中止构建
|
||
return false
|
||
# 容器装配(19 表成员按域收进三容器——2026-10-27 全清设计定稿)
|
||
combat.board_gen = loaded.get("board_gen")
|
||
combat.variation = loaded.get("variation")
|
||
combat.status_effects = loaded.get("status_effects")
|
||
combat.enemy = loaded.get("enemy")
|
||
combat.skill = loaded.get("skill")
|
||
combat.hero = loaded.get("hero")
|
||
combat.settlement = loaded.get("settlement")
|
||
run.level = loaded.get("level")
|
||
run.difficulty = loaded.get("difficulty")
|
||
run.level_gen_params = loaded.get("level_gen_params")
|
||
run.reward = loaded.get("reward")
|
||
run.adventure = loaded.get("adventure")
|
||
run.action_economy = loaded.get("action_economy")
|
||
run.shop = loaded.get("shop")
|
||
crossrun.meta = loaded.get("meta")
|
||
crossrun.meta_tree = loaded.get("meta_tree")
|
||
crossrun.treasure_items = loaded.get("treasure_items")
|
||
crossrun.feats = loaded.get("feats")
|
||
crossrun.forge = loaded.get("forge")
|
||
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
|
||
combat.board_gen._resolver = _resolver
|
||
combat.enemy._resolver = _resolver
|
||
run.difficulty._resolver = _resolver
|
||
is_ready = not has_errors
|
||
return not has_errors
|
||
|
||
|
||
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
|
||
# ConfigDbCrossValidator(core/config_db_cross_validator.gd)承载,门面公有签名不变。
|
||
func _cross_validate(loaded: Dictionary) -> void:
|
||
_validator.cross_validate(loaded, push_diag)
|
||
|
||
|
||
# 池模板 × 技能行跨表校验(冒烟直验独立入口——签名与消费面零变化,实现归协作类;门面私有——
|
||
# 消费面经门面者改 _ 前缀直调,cross_validator 直验入口公开面不变)
|
||
func _validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable, status_table: StatusEffectTable = null) -> Array:
|
||
return _validator.validate_skill_pool_links(skill_table, enemy_table, status_table)
|
||
|
||
|
||
# ---- 数据配置 EC8:debug 热重载门(战斗结算 唯一调用方;切片实现 = 门标志 + 拒绝语义)----
|
||
|
||
func set_reload_gate(gated: bool) -> void:
|
||
_reload_gate = gated
|
||
|
||
|
||
func reload() -> bool:
|
||
if _reload_gate:
|
||
push_diag("warning", "reload_gated", "战斗中热重载被拒绝(数据配置 EC8)")
|
||
return false
|
||
var ok := load_tables("res://data/tables", false)
|
||
if ok:
|
||
push_diag("debug", "config_reloaded", "表热重载完成")
|
||
return ok
|