254 lines
10 KiB
GDScript
254 lines
10 KiB
GDScript
## 弟子数据模型(纯数据,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
|