## 弟子数据模型(纯数据,RefCounted,无业务逻辑)。 ## ## 设计依据:doc/弟子属性.md(属性权威表)、doc/弟子管理.md(模块结构)。 ## - 持有弟子全部信息,运行时可变,不依赖场景树,可脱离场景单测 ## - 状态迁移(突破/升级/经验增减)属领域操作,归 DiscipleManager ## - 属性写入一律经 set_attr()/create():RANGES 夹取在此收口,禁止外部裸写字段 ## ("修改只走管理器"纪律,见 doc/弟子管理.md §5) class_name Disciple extends RefCounted ## 大境界枚举(值即 realm_level 的存储值 0~5,与 doc/弟子属性.md §2 对应)。 ## 注意:值为存档数据,严禁改变成员顺序或插入成员;只可在末尾追加。 enum Realm { MORTAL, # 0 凡人 QI_REFINING, # 1 练气 FOUNDATION, # 2 筑基 GOLDEN_CORE, # 3 金丹 NASCENT_SOUL, # 4 元婴 SPIRIT_TRANSFORMATION # 5 化神 } ## 每属性合法范围(唯一夹取权威,create/set_attr 共用)。 ## 与 doc/弟子属性.md §1 权威属性表一一对应;加新属性=加一行。 ## 注意:name 为字符串不在此表(直接字段读写);id 由 create() 分配(只读语义,见 §3)。 const RANGES := { # ---- 五行属性 ---- "metal": [0, 100], # 金 "wood": [0, 100], # 木 "water": [0, 100], # 水 "fire": [0, 100], # 火 "earth": [0, 100], # 土 # ---- 修炼属性 ---- "efficiency": [1, 100], # 灵气吸收效率(隐藏) "realm_level": [Realm.MORTAL, Realm.SPIRIT_TRANSFORMATION], # 大境界(枚举 Realm) "sub_realm_level": [1, 13], # 小境界:凡人1 / 练气1~13 / 筑基+每境1~4 "cultivation_exp": [0, 2147483647], # 修炼经验 # ---- 技能属性(每技能 = 等级 + 经验两字段,词干区分领域)---- "farming_level": [0, 10], # 耕作等级(凡粮·外田) "farming_exp": [0, 2147483647], # 耕作经验 "herb_level": [0, 10], # 药草种植等级(灵药·内田) "herb_exp": [0, 2147483647], # 药草种植经验 "alchemy_level": [0, 10], # 炼丹等级 "alchemy_exp": [0, 2147483647], # 炼丹经验 # ---- 其他属性 ---- "loyalty": [0, 100], # 忠诚度 } # 五行属性字段(数据字典缺哪个就填 0) const ELEMENT_FIELDS: Array[String] = ["metal", "wood", "water", "fire", "earth"] ## 各境界的小境界上限(索引 = realm_level):凡人1 / 练气13 / 筑基~化神 各4 const SUB_MAX_BY_REALM := [1, 13, 4, 4, 4, 4] ## 大境界显示名(索引 = realm_level) const REALM_NAMES := ["凡人", "练气", "筑基", "金丹", "元婴", "化神"] ## 小境界显示名(索引 = sub_realm_level,0 占位不用):筑基起的 前/中/后/大圆满 const SUB_NAMES := ["", "前期", "中期", "后期", "大圆满"] ## 灵根阈值(占位,待调参):任一五行 ≥ 此值即具灵根,可修炼破境 const SPIRITUAL_ROOT_THRESHOLD := 20 ## 修炼经验存储上限(属性表定义),满级(化神大圆满)时的经验哨兵 const EXP_CAP := 2147483647 ## 修炼经验曲线(占位,待调参):键 "{realm}_{sub}" → 该小境界经验上限。 ## 共 29 条:凡人 1 + 练气 13 + 筑基/金丹/元婴/化神 各 4。 ## 化神大圆满(5_4)无上限不入表,exp_cap() 满级时返回 EXP_CAP。 const CULTIVATION_EXP_CAPS := { # 凡人期:有灵根凡人在此积累经验,满则破境入练气 "0_1": 100, # 练气 1~13 层 "1_1": 100, "1_2": 120, "1_3": 140, "1_4": 160, "1_5": 180, "1_6": 200, "1_7": 220, "1_8": 240, "1_9": 260, "1_10": 280, "1_11": 300, "1_12": 320, "1_13": 340, # 筑基 前期/中期/后期/大圆满 "2_1": 400, "2_2": 460, "2_3": 530, "2_4": 610, # 金丹 "3_1": 700, "3_2": 810, "3_3": 930, "3_4": 1070, # 元婴 "4_1": 1250, "4_2": 1450, "4_3": 1680, "4_4": 1950, # 化神(大圆满 5_4 封顶,不入表) "5_1": 2300, "5_2": 2700, "5_3": 3150, } ## 技能经验曲线(占位,待调参):索引 = 当前等级 N(0~9)→ 升到 N+1 所需经验。 ## 耕作/药草种植/炼丹三套技能共用一张表,将来可拆分(doc/弟子属性.md §5.2)。 const SKILL_EXP_CAPS := [100, 150, 220, 320, 460, 660, 940, 1350, 1900, 2700] ## ---------- 字段 ---------- ## 各字段默认值即"无数据时的出生值";_init/create 只覆盖 data 中出现的键。 ## 直接读写仅限 id/name(id 应经 create 分配);数值属性读写必须走 get_attr/set_attr。 var id: int = 0 # 唯一标识,由 create() 从 data 分配(0 = 未分配) var name: String = "" # 姓名 ## 五行:出生后可变,一律经 create()/DiscipleManager.set_attr 写入 var metal: int = 0 # 金 var wood: int = 0 # 木 var water: int = 0 # 水 var fire: int = 0 # 火 var earth: int = 0 # 土 ## 灵气吸收效率 1~100,决定修炼速度(隐藏属性) var efficiency: int = 1 ## 大境界(枚举 Realm:MORTAL=凡人 QI_REFINING=练气 FOUNDATION=筑基 GOLDEN_CORE=金丹 NASCENT_SOUL=元婴 SPIRIT_TRANSFORMATION=化神) var realm_level: int = Realm.MORTAL ## 小境界:凡人固定1,练气1~13,筑基起1~4 var sub_realm_level: int = 1 ## 修炼经验(每小境界上限查曲线,跨小境界溢出继承,跨大境界清零) var cultivation_exp: int = 0 ## 耕作等级 0~10(凡粮·外田) var farming_level: int = 0 ## 耕作经验 var farming_exp: int = 0 ## 药草种植等级 0~10(灵药·内田) var herb_level: int = 0 ## 药草种植经验 var herb_exp: int = 0 ## 炼丹等级 0~10 var alchemy_level: int = 0 ## 炼丹经验 var alchemy_exp: int = 0 ## 忠诚度 0~100 var loyalty: int = 50 ## 属性总量(只读,实时由五行之和计算) var total_amount: int: get: return metal + wood + water + fire + earth ## ---------- 构造与工厂 ---------- ## 构造:按 data 覆盖属性,数值属性经 RANGES 夹取;缺键保留字段默认值。 ## @param data: 键名与属性名一致,如 {"id": 1, "name": "玄真", "metal": 60} func _init(data: Dictionary[String, Variant] = {}) -> void: if data.has("id"): id = int(data["id"]) if data.has("name"): name = str(data["name"]) for key in RANGES: if data.has(key): set_attr(key, int(data[key])) ## 工厂:唯一创建入口。id 必填,缺失则报错返回 null(防止未分配 id 的孤儿对象)。 ## @param data: 构造字典(须含 "id") ## @return: 新弟子;id 缺失返回 null static func create(data: Dictionary) -> Disciple: if not data.has("id"): push_error("Disciple.create: 缺少 id,放弃生成 Disciple") return null return Disciple.new(data) ## ---------- 读写接口 ---------- ## 数值属性是否存在(RANGES 登记表中的键)。 ## @param key: 属性名 ## @return: true=是数值属性 func has_attr(key: String) -> bool: return RANGES.has(key) ## 读数值属性。name/id 请直接字段访问(id 不经此函数,name 为字符串)。 ## 未知键名 push_error 并返回 0(防止拼错键名静默出错)。 ## @param key: RANGES 登记表中的属性名 ## @return: 当前值 func get_attr(key: String) -> int: if not RANGES.has(key): push_error("Disciple.get_attr: 未知数值属性 \"%s\"" % key) return 0 return int(get(key)) ## 写数值属性(唯一数值写入入口):RANGES 夹取 + 境界/小境界联动约束。 ## 未知键名报错返回 false;不直接改 name/id。 ## @param key: RANGES 登记表中的属性名 ## @param value: 新值(越界自动夹取到合法范围) ## @return: true=写入成功 func set_attr(key: String, value: int) -> bool: if not RANGES.has(key): push_error("Disciple.set_attr: 未知数值属性 \"%s\"" % key) return false var range_: Array = RANGES[key] var v := clampi(value, int(range_[0]), int(range_[1])) if key == "realm_level": # 大境界变化后,小境界不得超过新境界的上限(如落入凡人则强制 1) set("realm_level", v) var max_sub := SUB_MAX_BY_REALM[v] if int(get("sub_realm_level")) > max_sub: set("sub_realm_level", max_sub) elif key == "sub_realm_level": # 小境界写入时按当前大境界上限夹取(凡人上限 1 / 练气 13 / 其余 4) var max_sub := SUB_MAX_BY_REALM[int(get("realm_level"))] set("sub_realm_level", clampi(v, 1, max_sub)) else: set(key, v) return true ## ---------- 派生只读(不存储,由属性实时计算) ---------- ## 当前大境界允许的小境界上限(凡人1 / 练气13 / 筑基起4)。 func sub_realm_max() -> int: return SUB_MAX_BY_REALM[realm_level] ## 是否具灵根:任一五行 ≥ SPIRITUAL_ROOT_THRESHOLD。 ## 有灵根是修炼的前提(凡人无灵根则终生无法破境)。 func has_spiritual_root() -> bool: for f in ELEMENT_FIELDS: if int(get(f)) >= SPIRITUAL_ROOT_THRESHOLD: return true return false ## 是否满级(化神大圆满):到达后不再获得修炼经验。 func is_max_realm() -> bool: return realm_level == Realm.SPIRIT_TRANSFORMATION \ and sub_realm_level == SUB_MAX_BY_REALM[Realm.SPIRIT_TRANSFORMATION] ## 能否修炼:有灵根 且 未满级。 func can_cultivate() -> bool: return has_spiritual_root() and not is_max_realm() ## 是否凡俗:大境界为凡人且无灵根。 func is_mortal() -> bool: return realm_level == Realm.MORTAL and not has_spiritual_root() ## 当前 (realm, sub) 的修炼经验上限;满级返回 EXP_CAP(不再积累)。 func exp_cap() -> int: if is_max_realm(): return EXP_CAP var key := "%d_%d" % [realm_level, sub_realm_level] if not CULTIVATION_EXP_CAPS.has(key): push_error("Disciple.exp_cap: 曲线表缺少条目 %s" % key) return EXP_CAP return int(CULTIVATION_EXP_CAPS[key]) ## 完整境界显示名:凡人 / 练气5层 / 筑基后期 / 化神大圆满。 func realm_name() -> String: if realm_level == Realm.MORTAL: return REALM_NAMES[Realm.MORTAL] if realm_level == Realm.QI_REFINING: return "练气%d层" % sub_realm_level return "%s%s" % [REALM_NAMES[realm_level], SUB_NAMES[sub_realm_level]] ## ---------- 序列化(存档网关) ---------- ## 输出全属性字典(键名与构造一致),供管理器 to_dict 汇总存档。 func to_dict() -> Dictionary: var result := {"id": id, "name": name} for key in RANGES: result[key] = get_attr(key) return result