Files

185 lines
9.9 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## VERTICAL SLICE - NOT FOR PRODUCTION
## 锻造工坊表 schema:两根类型轴默认值 + 六条锻造路线行族的类型化读取(数值 provisional)。
## 所有权切分(红线):攻击唯一入口=武器路线、AC 唯一入口=甲路线——树零攻防来源,本表是攻防数值成长唯一数据面。
## 表运行期只读(铁律):永久强化只写存档 forge_routes 域,本表零写面。
class_name ForgeTable
extends Resource
# 路线 id 词表(甲路线 id=甲型值本身——ward=魔法护甲,统一命名)
const WEAPON_IDS := ["sword", "spear", "staff"]
const ARMOR_IDS := ["light", "heavy", "ward"]
const ROUTE_IDS := ["sword", "spear", "staff", "light", "heavy", "ward"]
# 两根类型轴枚举域(hero.tres 静态表两轴随本批删除——词表归本表常驻持有)
const ATTACK_TYPES := ["normal", "pierce", "magic"]
const ARMOR_TYPES := ["light", "ward", "heavy"]
# 行字段闭集(V11:满级特效本期不做——行族无特效参数位;键集出域即拒)
const ROW_KEYS := ["route_id", "display_name", "kind", "type_axis", "cap", "per_level_gain",
"base_ac", "base_price", "price_ratio_pct", "initial_owned", "prereq_route", "unlock_price"]
@export var default_attack_type: String = "normal" # 攻击类型轴默认值(常驻持有——与柜台解锁状态解耦:未点亮=无交互入口但默认值照常注入)
@export var default_armor_type: String = "light" # 甲型轴默认值(常驻持有)
@export var routes: Array[Dictionary] = [] # 六路线行族(键 route_id)
# 各级价格(几何递增:基础价 × 150%^已购级数——锚表 20/30/45/67/101;1.5^n 二进制精确,无浮点尘)
static func level_price(row: Dictionary, owned_levels: int) -> int:
var base := int(row.get("base_price", 0))
var ratio := float(int(row.get("price_ratio_pct", 100))) / 100.0
return int(floor(float(base) * pow(ratio, maxi(0, owned_levels))))
func validate() -> Array:
var errs: Array[Dictionary] = []
# 默认两轴枚举域
if not ATTACK_TYPES.has(default_attack_type):
errs.append({"category": "enum", "message": "default_attack_type 出域 %s: %s" % [ATTACK_TYPES, default_attack_type]})
if not ARMOR_TYPES.has(default_armor_type):
errs.append({"category": "enum", "message": "default_armor_type 出域 %s: %s" % [ARMOR_TYPES, default_armor_type]})
# 行族规模恰六(三武器+三甲——需求卡片 §1①)
if routes.size() != 6:
errs.append({"category": "structural", "message": "锻造路线行族规模 %d ≠ 6(三武器+三甲)" % routes.size()})
var seen := {}
var type_seen := {}
var kind_by_id := {}
var initial_ids := {}
for row in routes: # 预扫:route_id→kind 全集(前置反查需前向引用——轻甲行在长枪行之后)
kind_by_id[String(row.get("route_id", ""))] = String(row.get("kind", ""))
for row in routes:
var head_out := _validate_route_head(row, seen, type_seen)
errs.append_array(head_out["errs"])
if head_out["skip"]:
continue
var rid := String(row.get("route_id", ""))
errs.append_array(_validate_route_values(row, rid))
errs.append_array(_validate_route_unlock(row, rid, kind_by_id, initial_ids))
errs.append_array(_validate_initial_coverage(initial_ids))
return errs
## 行头域:route_id 词表与查重/kind 出域(skip=跳过行剩余判据)/类型轴域与每型恰一路线
func _validate_route_head(row: Dictionary, seen: Dictionary, type_seen: Dictionary) -> Dictionary:
var errs: Array[Dictionary] = []
var skip := false
var rid := String(row.get("route_id", ""))
if not ROUTE_IDS.has(rid):
errs.append({"category": "enum", "message": "锻造路线 route_id 出域 %s: %s" % [ROUTE_IDS, rid]})
if seen.has(rid):
errs.append({"category": "dup_key", "message": "锻造路线 route_id 重复: %s" % rid})
seen[rid] = true
var kind := String(row.get("kind", ""))
if not ["weapon", "armor"].has(kind):
errs.append({"category": "enum", "message": "%s kind 出域 {weapon, armor}: %s" % [rid, kind]})
skip = true
return {"errs": errs, "skip": skip}
# 类型轴:武器=attack_type 域、甲=armor_type 域;每型恰一路线(三武器三甲各一)
var axis := String(row.get("type_axis", ""))
var domain: Array[Variant] = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES
if not domain.has(axis):
errs.append({"category": "enum", "message": "%s type_axis 出域 %s: %s" % [rid, domain, axis]})
var type_key := "%s|%s" % [kind, axis]
if type_seen.has(type_key):
errs.append({"category": "dup_key", "message": "%s 类型轴重复(每型恰一路线): %s %s" % [kind, axis, rid]})
type_seen[type_key] = true
return {"errs": errs, "skip": skip}
## 数值域:等级/增量/价格/基础 AC 值域(甲型基础 AC 只属护甲路线)
func _validate_route_values(row: Dictionary, rid: String) -> Array[Dictionary]:
var errs: Array[Dictionary] = []
var kind := String(row.get("kind", ""))
var cap_v: int = int(row.get("cap", -1))
if cap_v < 1 or cap_v > 9:
errs.append({"category": "domain", "message": "%s cap 越域 [1,9]: %d" % [rid, cap_v]})
var gain: int = int(row.get("per_level_gain", -1))
if gain < 0 or gain > 9:
errs.append({"category": "domain", "message": "%s per_level_gain 越域 [0,9]: %d" % [rid, gain]})
var base_ac: int = int(row.get("base_ac", -1))
if kind == "armor":
if base_ac < 0 or base_ac > 999:
errs.append({"category": "domain", "message": "%s base_ac 越域 [0,999]: %d" % [rid, base_ac]})
elif base_ac != 0:
errs.append({"category": "cross_field", "message": "%s 武器行 base_ac 须为 0(甲型基础 AC 只属护甲路线): %d" % [rid, base_ac]})
var base_price: int = int(row.get("base_price", -1))
if base_price < 1 or base_price > 999:
errs.append({"category": "domain", "message": "%s base_price 越域 [1,999]: %d" % [rid, base_price]})
var ratio: int = int(row.get("price_ratio_pct", -1))
if ratio < 100 or ratio > 500:
errs.append({"category": "domain", "message": "%s price_ratio_pct 越域 [100,500]: %d" % [rid, ratio]})
var unlock_price: int = int(row.get("unlock_price", -1))
if unlock_price < 0 or unlock_price > 999:
errs.append({"category": "domain", "message": "%s unlock_price 越域 [0,999]: %d" % [rid, unlock_price]})
return errs
## 解锁通道域(并联双通道):初始持有行无前置无解锁价;待解锁行须有一次性解锁价+合法前置(武器←武器/甲←甲)
func _validate_route_unlock(row: Dictionary, rid: String, kind_by_id: Dictionary, initial_ids: Dictionary) -> Array[Dictionary]:
var errs: Array[Dictionary] = []
var kind := String(row.get("kind", ""))
var unlock_price: int = int(row.get("unlock_price", -1))
var initial: bool = bool(row.get("initial_owned", false))
var prereq := String(row.get("prereq_route", ""))
if initial:
if prereq != "":
errs.append({"category": "cross_field", "message": "%s 初始持有行不得带前置: %s" % [rid, prereq]})
if unlock_price != 0:
errs.append({"category": "cross_field", "message": "%s 初始持有行解锁价须为 0: %d" % [rid, unlock_price]})
initial_ids[rid] = kind
else:
if unlock_price < 1:
errs.append({"category": "cross_field", "message": "%s 待解锁行须有一次性解锁价(并联双通道②): %d" % [rid, unlock_price]})
if not ROUTE_IDS.has(prereq):
errs.append({"category": "enum", "message": "%s prereq_route 出域 %s: %s" % [rid, ROUTE_IDS, prereq]})
elif prereq == rid:
errs.append({"category": "cross_field", "message": "%s prereq_route 不得自指" % rid})
elif kind_by_id.has(prereq) and String(kind_by_id[prereq]) != kind:
errs.append({"category": "cross_field", "message": "%s 前置 %s 类型不符(武器←武器/甲←甲): %s vs %s" % [rid, prereq, kind, String(kind_by_id[prereq])]})
return errs
## 初始持有覆盖域(两尾段):初始持有恰两行且一武器一甲(长剑/轻甲)+
## 默认轴=初始持有路线类型轴(Q1 裸体态语义:未锻造=0 加值且类型=普通/轻甲)
func _validate_initial_coverage(initial_ids: Dictionary) -> Array[Dictionary]:
var errs: Array[Dictionary] = []
var init_weapons := 0
var init_armors := 0
for k in initial_ids.keys():
if String(initial_ids[k]) == "weapon":
init_weapons += 1
elif String(initial_ids[k]) == "armor":
init_armors += 1
if init_weapons != 1 or init_armors != 1:
errs.append({"category": "coverage", "message": "初始持有须恰一武器一甲(现 sword/light): 武器 %d / 甲 %d" % [init_weapons, init_armors]})
if init_weapons == 1 and init_armors == 1:
for row in routes:
if not bool(row.get("initial_owned", false)):
continue
if String(row.get("kind", "")) == "weapon" and String(row.get("type_axis", "")) != default_attack_type:
errs.append({"category": "cross_field", "message": "default_attack_type 须=初始武器路线类型轴(裸体态普通): %s vs %s" % [default_attack_type, String(row.get("type_axis", ""))]})
if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) != default_armor_type:
errs.append({"category": "cross_field", "message": "default_armor_type 须=初始甲路线类型轴(裸体态轻甲): %s vs %s" % [default_armor_type, String(row.get("type_axis", ""))]})
return errs
# ---- 行族访问器(自 ConfigDB 归位;消费面 db.crossrun.forge.<m> 直连)——
# routes 属性在册故行族读取命名 route_rows(避开成员名冲突)----
## 六路线行族(副本——调用方禁改)
func route_rows() -> Array:
return routes.duplicate()
## 路线行单点(键 route_id;缺行=空字典)
func route(route_id: String) -> Dictionary:
for row in routes:
if String(row.get("route_id", "")) == route_id:
return row
return {}
## 按类型轴反查护甲路线(armor_type → 路线行——入战 AC 三层之「甲型基础 AC」取数口)
func armor_route_by_type(armor_type: String) -> Dictionary:
for row in routes:
if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) == armor_type:
return row
return {}