Files
ImmortalSect/src/Data/Models/Disciple.gd
T

254 lines
10 KiB
GDScript
Raw 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.
## 弟子数据模型(纯数据,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_level0 占位不用):筑基起的 前/中/后/大圆满
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/nameid 应经 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
## 大境界(枚举 RealmMORTAL=凡人 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