feat:完善加点代码导入
This commit is contained in:
@@ -244,6 +244,32 @@ if len(data) % 2:
|
||||
|
||||
其中最后的 `00` 是当前解析实现补入的对齐字节,不是解压后原始数据的一部分。该行为为兼容现有返回格式而保留。
|
||||
|
||||
### 5.1 普通职业技能连招
|
||||
|
||||
版本 5 的非黑暗武士编码新增 `skill_combo`,由 `_parse_skill_combo()` 读取:
|
||||
|
||||
```python
|
||||
'skill_combo': {
|
||||
'skills': [309, 305, 312, 311],
|
||||
'interval_ms': 5000,
|
||||
}
|
||||
```
|
||||
|
||||
- `remaining[92:100]`:固定四个技能索引,每项两字节;沿用快捷栏的高位标志规则。
|
||||
- 技能为 OpenAPI 数字 ID,空槽返回 `None`,保留四个槽位及顺序。
|
||||
- `remaining[100:102]`:小端 uint16 初始化间隔,单位毫秒。
|
||||
- `remaining[102]`:尾部 `F3`。长度不足或标记不匹配时 `skill_combo` 返回 `None`。
|
||||
- `remaining[91]` 尚未确认,不解释为开关或数量。
|
||||
|
||||
女蓝拳样本的四个技能为拳震八方、巨力炮拳、致命冲拳、怒气贯地击,
|
||||
顺序已由用户确认。仅把间隔从 0 秒改为 5 秒后,唯一变化是
|
||||
`remaining[100:102]` 从 `00 00` 变为 `88 13`(小端整数 5000)。
|
||||
剑豪样本也能对应三个技能和一个空槽。
|
||||
|
||||
该字段当前仅在 `decode_skill_hash()` 输出;计算器接口及黑暗武士的
|
||||
`arrangements` / `combos` 不变。黑暗武士和其他编码版本不套用此偏移。
|
||||
原始 `section2_remaining` 始终完整保留。
|
||||
|
||||
## 6. 黑暗武士连续技排列
|
||||
|
||||
只有满足以下条件时,顶层结果才增加 `arrangements`:
|
||||
@@ -330,6 +356,7 @@ index = index_byte + (0x100 if flag else 0)
|
||||
{
|
||||
'header': dict,
|
||||
'skills': list,
|
||||
'auto_skills': list,
|
||||
'evolutions': list,
|
||||
'enhancements': list,
|
||||
'keybinds': list,
|
||||
@@ -350,21 +377,32 @@ index = index_byte + (0x100 if flag else 0)
|
||||
|
||||
## 8. 技能索引到名称的解析
|
||||
|
||||
`decode_skill_hash()` 只负责二进制结构和技能索引,不直接写入名称。
|
||||
`decode_skill_hash()` 保留代码中的原始 `skills`,并根据 Header 职业读取本地
|
||||
`backend/openapi/static/data/cn/{job_name}/{job_grow_name}/skill_tree.json`,新增:
|
||||
|
||||
`get_skill_tree_info()` 根据职业加载:
|
||||
|
||||
```text
|
||||
backend/openapi/static/data/cn/{job_name}/{job_grow_name}/skill_tree.json
|
||||
```python
|
||||
'auto_skills': [
|
||||
{'skill_index': 174, 'level': 115},
|
||||
]
|
||||
```
|
||||
|
||||
并通过 `load_skill_tree_name_map()` 建立:
|
||||
仅补充具有 `autoLevel` 且未出现在原始 `skills` 中的技能,等级直接使用
|
||||
`autoLevel`,不覆盖代码中的显式等级。没有补充项时返回空列表。
|
||||
这些条目来自技能树,没有二进制 `skill_flag` / `level_flag`。
|
||||
解码不创建角色;通过 `_get_skill_tree_context()` 优先读取 Redis,缓存键为
|
||||
`openapi:skillTreeMap:v1:{job_name}:{job_grow_name}:map`,与旧名称映射隔离。
|
||||
未命中或 Redis 读写失败时,按现有 `get_redis_info()` 规则回退本地加载;
|
||||
DEBUG 模式跳过 Redis。文件加载另有进程内缓存,更新数据需清理对应 Redis
|
||||
缓存并重启进程(或升级缓存版本)。
|
||||
|
||||
```text
|
||||
OpenAPI 技能索引 -> 技能名称
|
||||
```
|
||||
解析和计算器适配分为三步:
|
||||
|
||||
随后再按技能名称连接到 `character.skills` 中的计算对象。
|
||||
1. `decode_skill_hash(encoded)`:二进制解析及 `auto_skills` 补充。
|
||||
2. `resolve_skill_tree(decoded, skill_tree)`:合并两类技能,映射名称、进化、强化、快捷栏和排列。
|
||||
`get_openapi_skill_tree_info(encoded)` 封装前两步,以 OpenAPI ID 返回结果,不调用 `createCharacter`。
|
||||
3. `get_skill_tree_info(encoded)`:调用上述入口,再按名称匹配计算器技能,保持现有接口字段和计算器 ID。
|
||||
|
||||
尚未新增 HTTP 路由;后续对外接口可直接调用 `get_openapi_skill_tree_info()`。
|
||||
|
||||
需要区分:
|
||||
|
||||
@@ -391,7 +429,7 @@ OpenAPI 技能索引 -> 技能名称
|
||||
- Header 中未命名的常量或状态字节;
|
||||
- Section2 首字节 marker 的业务含义;
|
||||
- 黑暗武士排列前缀之后的 remaining 数据;
|
||||
- 普通职业 remaining 中非零字段的业务含义;
|
||||
- 普通职业 remaining 中除上述技能连招以外的字段含义;
|
||||
- 尾部 `0xF3` 之前其他状态值的业务含义。
|
||||
|
||||
在获得“只修改一个游戏设置后重新导出的对照代码”之前,不应为这些字节添加业务字段。
|
||||
|
||||
+198
-129
@@ -3,8 +3,7 @@ import zlib
|
||||
import struct
|
||||
|
||||
from api.core.Redis import get_redis_info
|
||||
from api.utils.skill_tree import SKILL_TREE_NAME_MAP_VERSION, load_skill_tree_name_map
|
||||
from core.basic.character import createCharacter
|
||||
from api.utils.skill_tree import SKILL_TREE_MAP_VERSION, load_skill_tree_map
|
||||
from core.character.adventure import get_adv_info
|
||||
from database.connect import get_redis_client
|
||||
|
||||
@@ -50,17 +49,47 @@ role_map = {
|
||||
|
||||
def decode_skill_hash(encoded: str) -> dict:
|
||||
"""
|
||||
解码 DNF 技能加点编码。
|
||||
解码 DNF 技能加点编码,并根据职业技能树补充自动学习技能。
|
||||
|
||||
编码流程: base64 -> zlib decompress -> 二进制
|
||||
输入 encoded 为游戏导出的 Base64 加点字符串。
|
||||
解码流程: base64 -> zlib decompress -> 二进制
|
||||
二进制结构:
|
||||
[0xF3] [Header 14B] [UTF-16LE 角色名]
|
||||
[Header 14B,首字节 0xF3] [UTF-16LE 角色名]
|
||||
[Section1: N个(技能索引,等级)对]
|
||||
[Section2: 进化 | 强化 | 键位 | 其他]
|
||||
[零填充] [0xF3]
|
||||
[Section2: 进化 | 强化 | 键位 | 职业排列及其他尾部数据]
|
||||
|
||||
Section1 中每个 uint16 的高字节=数据值,低字节=标志位。
|
||||
Section2 按字节读取,包含三部分定长子结构。
|
||||
技能索引在 level_flag 非零时加 0x100。
|
||||
Section2 按字节读取,各子结构由数量字段和对应数量的定长条目组成;
|
||||
其中技能索引为低字节加 0x100(第二字节标志非零时)。
|
||||
|
||||
所有职业均返回:
|
||||
header: 角色名、等级、职业字节、编码版本等头信息。
|
||||
skills: 原始加点列表,每项包含 skill_index、level、skill_flag、
|
||||
level_flag;不将自动学习技能混入此列表。
|
||||
auto_skills: 技能树中具有 autoLevel 且未出现在 skills 中的技能,
|
||||
每项为 {skill_index, level},等级直接取 autoLevel。
|
||||
不覆盖显式加点,无补充项时为 []。
|
||||
evolutions: 进化列表,每项为 {skill_index, flag, type},type 从 1 开始。
|
||||
enhancements: 强化列表,字段与 evolutions 相同。
|
||||
keybinds: 快捷栏列表,每项为 {slot, skill_index, flag};
|
||||
slot 从 0 开始,空槽的 skill_index 为 None。
|
||||
section2_remaining: 快捷栏之后的完整原始字节,不因扩展解析而截断。
|
||||
若角色名之后的数据长度为奇数,末尾含对齐补入的 0x00。
|
||||
raw_size: zlib 解压后的原始字节数,不包含解析器补入的对齐字节。
|
||||
|
||||
按职业和版本额外返回:
|
||||
arrangements: 仅黑暗武士返回,格式为 [{slot_index, skills}],
|
||||
skills 保留该排列内技能索引的顺序。
|
||||
skill_combo: 仅版本 5 的非黑暗武士返回,格式为
|
||||
{skills: [309, 305, 312, 311], interval_ms: 5000}。
|
||||
skills 为四个有序槽位,空槽为 None;interval_ms 为
|
||||
初始化间隔(毫秒)。读取 remaining[92:100] 的技能索引
|
||||
和 remaining[100:102] 的小端 uint16 间隔;长度不足或
|
||||
remaining[102] 不是 0xF3 时返回 None。
|
||||
remaining[91] 含义未确认,不解释为数量或开关。
|
||||
|
||||
未确认的尾部字段仅保留原始字节;黑暗武士不套用普通职业连招偏移。
|
||||
"""
|
||||
raw = zlib.decompress(base64.b64decode(encoded))
|
||||
header = _parse_header(raw)
|
||||
@@ -103,6 +132,16 @@ def decode_skill_hash(encoded: str) -> dict:
|
||||
# 黑暗武士才有技能排列;其他职业保持原有返回结构不变。
|
||||
if header['job_bytes'][0] == 9:
|
||||
result['arrangements'] = _parse_dark_knight_arrangements(remaining)
|
||||
elif header['version'] == 5:
|
||||
# 普通职业的技能连招与黑暗武士排列是两种结构,不能共用偏移。
|
||||
result['skill_combo'] = _parse_skill_combo(remaining)
|
||||
_, skill_tree = _get_skill_tree_context(header)
|
||||
encoded_ids = {str(skill['skill_index']) for skill in skills}
|
||||
result['auto_skills'] = [
|
||||
{'skill_index': int(skill_id), 'level': metadata['autoLevel']}
|
||||
for skill_id, metadata in skill_tree.items()
|
||||
if 'autoLevel' in metadata and skill_id not in encoded_ids
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
@@ -129,7 +168,21 @@ def _parse_section2(sec2: bytes):
|
||||
byte[2+3E+1..2+3E+3H]: H 个强化条目,每条 3 字节
|
||||
byte[2+3E+3H+1]: 键位数量 K (固定 0x0E=14)
|
||||
byte[..+2K]: K 个键位条目,每条 2 字节 (技能索引, 标志)
|
||||
剩余: 其他数据
|
||||
剩余: remaining = sec2[off:],包含职业排列、技能连招及未解析数据。
|
||||
|
||||
技能连招位于快捷栏之后的 remaining 中,不属于上面的 K 个键位条目。
|
||||
版本 5 普通职业(不含黑暗武士)的已确认布局如下,偏移从 remaining
|
||||
开头计算,不是从 sec2 或整个解压数据开头计算:
|
||||
remaining[91]: 含义未确认,不解释为数量或开关。
|
||||
remaining[92:100]: 四个有序连招技能,每项 2 字节;索引为低字节
|
||||
加 0x100(第二字节非零时),0 表示空槽。
|
||||
remaining[100:102]: 初始化间隔,小端 uint16,单位毫秒。
|
||||
remaining[102]: 尾部标记 0xF3,其后可能有解析器补入的对齐 0x00。
|
||||
|
||||
本函数仅返回完整 remaining,不直接解析连招;decode_skill_hash()
|
||||
根据职业和版本调用 _parse_skill_combo(),输出 skill_combo。
|
||||
黑暗武士的 remaining 前缀由 _parse_dark_knight_arrangements() 解析,
|
||||
不套用上述普通职业偏移。任何扩展解析均不截断原始 remaining。
|
||||
"""
|
||||
if len(sec2) < 2:
|
||||
return [], [], [], sec2
|
||||
@@ -194,6 +247,30 @@ def _parse_section2(sec2: bytes):
|
||||
return evolutions, enhancements, keybinds, remaining
|
||||
|
||||
|
||||
def _parse_skill_combo(remaining: bytes) -> dict | None:
|
||||
"""读取版本 5 普通职业尾部的技能连招,不修改 remaining。
|
||||
|
||||
已通过女蓝拳顺序确认及仅将间隔从 0 秒改为 5 秒的代码验证:
|
||||
remaining[92:100]:四个技能索引,每个 2 字节,0 表示空槽;
|
||||
remaining[100:102]:小端 uint16 初始化间隔,单位毫秒;
|
||||
remaining[102]:尾部 0xF3,解析器可能在它后面补齐一个 0x00。
|
||||
remaining[91] 的含义尚未确认,不将其解释为数量或开关。
|
||||
数据不足或尾部标记不匹配时返回 None,避免把未知布局当成空连招。
|
||||
"""
|
||||
if len(remaining) < 103 or remaining[102] != 0xF3:
|
||||
return None
|
||||
|
||||
skills = []
|
||||
for offset in range(92, 100, 2):
|
||||
# 与快捷栏一致:低字节为索引,第二字节非零表示加 0x100。
|
||||
index = remaining[offset] + (0x100 if remaining[offset + 1] else 0)
|
||||
skills.append(index if index else None)
|
||||
return {
|
||||
'skills': skills,
|
||||
'interval_ms': struct.unpack_from('<H', remaining, 100)[0],
|
||||
}
|
||||
|
||||
|
||||
def _parse_dark_knight_arrangements(remaining: bytes) -> list[dict]:
|
||||
"""从原始 remaining 前缀中读取黑暗武士技能排列。
|
||||
|
||||
@@ -236,24 +313,52 @@ def _parse_dark_knight_arrangements(remaining: bytes) -> list[dict]:
|
||||
return arrangements
|
||||
|
||||
|
||||
def _resolve_openapi_skill_id(skill_index: int, skill_name_map: dict[str, str]) -> int | None:
|
||||
if str(skill_index) in skill_name_map:
|
||||
return skill_index
|
||||
return None
|
||||
def resolve_skill_tree(decoded: dict, skill_tree: dict[str, dict]) -> dict:
|
||||
"""将二进制结果映射到 OpenAPI 技能 ID;不依赖角色计算对象。
|
||||
|
||||
autoLevel 仅补充代码中缺失的技能,不覆盖显式等级。
|
||||
"""
|
||||
skills = {}
|
||||
for item in decoded['skills'] + decoded['auto_skills']:
|
||||
skill_id = str(item['skill_index'])
|
||||
metadata = skill_tree.get(skill_id)
|
||||
if metadata is None:
|
||||
continue
|
||||
if skill_id not in skills or item['level'] > skills[skill_id]['lv']:
|
||||
skills[skill_id] = {
|
||||
'lv': item['level'], 'up': 0, 'vp': 0, 'name': metadata['name'],
|
||||
}
|
||||
for section, field in [('evolutions', 'vp'), ('enhancements', 'up')]:
|
||||
for item in decoded[section]:
|
||||
skill = skills.get(str(item['skill_index']))
|
||||
if skill is not None:
|
||||
skill[field] = item['type']
|
||||
|
||||
def reference(skill_index):
|
||||
metadata = skill_tree.get(str(skill_index))
|
||||
return {
|
||||
'skill_id': skill_index if metadata is not None else None,
|
||||
'skill_name': metadata['name'] if metadata is not None else None,
|
||||
}
|
||||
|
||||
return {
|
||||
'skills': skills,
|
||||
'shortcuts': [
|
||||
{'slot': item['slot'], **reference(item['skill_index'])}
|
||||
for item in decoded['keybinds']
|
||||
],
|
||||
'combos': [
|
||||
{
|
||||
'slot_index': item['slot_index'],
|
||||
'skills': [reference(index) for index in item['skills']],
|
||||
}
|
||||
for item in decoded.get('arrangements', [])
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# def _resolve_section_skill_id(skill_index: int, skill_name_map: dict[str, str]) -> int | None:
|
||||
# high_skill_id = skill_index + 0x100
|
||||
# if str(high_skill_id) in skill_name_map:
|
||||
# return high_skill_id
|
||||
# if str(skill_index) in skill_name_map:
|
||||
# return skill_index
|
||||
# return None
|
||||
|
||||
|
||||
def get_skill_tree_info(encoded: str) -> dict:
|
||||
r = decode_skill_hash(encoded)
|
||||
header = r['header']
|
||||
def _get_skill_tree_context(header: dict) -> tuple[dict, dict[str, dict]]:
|
||||
"""根据编码职业读取 Redis 技能树缓存,未命中时回退本地文件。"""
|
||||
role = role_map.get(str(header['job_bytes'][0]), None)
|
||||
job_id = int(header['job_bytes'][1]) - 1
|
||||
# 黑暗武士
|
||||
@@ -269,119 +374,83 @@ def get_skill_tree_info(encoded: str) -> dict:
|
||||
job_name = class_parts[1]
|
||||
job_grow_name = class_parts[2].replace('_carry', '')
|
||||
|
||||
def get_skill_name_map():
|
||||
return load_skill_tree_name_map(job_name, job_grow_name)
|
||||
|
||||
with get_redis_client() as redis:
|
||||
skill_name_map = get_redis_info(
|
||||
skill_tree = get_redis_info(
|
||||
redis,
|
||||
f'openapi:{SKILL_TREE_NAME_MAP_VERSION}:{job_name}:{job_grow_name}:nameMap',
|
||||
get_skill_name_map,
|
||||
f'openapi:{SKILL_TREE_MAP_VERSION}:{job_name}:{job_grow_name}:map',
|
||||
lambda: load_skill_tree_map(job_name, job_grow_name),
|
||||
)
|
||||
return advInfo, skill_tree
|
||||
|
||||
character = createCharacter(className)
|
||||
skillMap = {}
|
||||
for index, i in enumerate(r['skills']):
|
||||
skillIndex = i['skill_index']
|
||||
actualSkillId = _resolve_openapi_skill_id(skillIndex, skill_name_map)
|
||||
skillName = skill_name_map.get(str(actualSkillId)) if actualSkillId is not None else None
|
||||
skills = [skill for skill in character.skills if skill.name == skillName] if skillName else []
|
||||
i['actual_skill_id'] = actualSkillId
|
||||
i['skill_name'] = skillName
|
||||
i['candidates'] = skills[0] if skills else None
|
||||
if actualSkillId is not None:
|
||||
skillMap[actualSkillId] = skills
|
||||
if len(skills) == 0:
|
||||
r['skills'][index] = None
|
||||
for k, v in skillMap.items():
|
||||
if len(v) > 1:
|
||||
skillMap[k] = [s for s in v if s.learnLv not in [50, 85]]
|
||||
skillLearn = {}
|
||||
# 处理技能等级
|
||||
for skill in r['skills']:
|
||||
if skill is None:
|
||||
continue
|
||||
candidates = skill.get('candidates', [])
|
||||
skillName = skill.get('skill_name')
|
||||
chosen = None
|
||||
if candidates:
|
||||
chosen = candidates
|
||||
if chosen and skillLearn.get(str(chosen.id), {'lv': 0})['lv'] < skill['level']:
|
||||
skillLearn[str(chosen.id)] = {
|
||||
'lv': skill['level'],
|
||||
'up': 0,
|
||||
'vp': 0,
|
||||
'name': skillName or chosen.name,
|
||||
}
|
||||
# 处理进化
|
||||
for evo in r['evolutions']:
|
||||
actualSkillId = _resolve_openapi_skill_id(evo['skill_index'], skill_name_map)
|
||||
for s in skillMap.get(actualSkillId, []):
|
||||
if getattr(s, 'hasVP', False) and str(s.id) in skillLearn:
|
||||
skillLearn[str(s.id)]['vp'] = evo['type']
|
||||
|
||||
# 处理强化
|
||||
for enh in r['enhancements']:
|
||||
actualSkillId = _resolve_openapi_skill_id(enh['skill_index'], skill_name_map)
|
||||
for s in skillMap.get(actualSkillId, []):
|
||||
if getattr(s, 'hasUP', False) and str(s.id) in skillLearn:
|
||||
skillLearn[str(s.id)]['up'] = enh['type']
|
||||
for i in character.skills:
|
||||
if str(i.id) not in skillLearn:
|
||||
skillLearn[str(i.id)] = {
|
||||
'lv': i.calculate_lv() if (i.learnLv in [50,85] or i.name == '基础精通') and str(i.id) else 0,
|
||||
'up': 0,
|
||||
'vp': 0,
|
||||
'name': i.name,
|
||||
}
|
||||
shortcuts = []
|
||||
for k in r['keybinds']:
|
||||
actualSkillId = _resolve_openapi_skill_id(k['skill_index'], skill_name_map)
|
||||
if actualSkillId is None:
|
||||
shortcuts.append({
|
||||
'slot': k['slot'],
|
||||
'skill_id': None,
|
||||
'skill_name': None,
|
||||
})
|
||||
continue
|
||||
for s in skillMap.get(actualSkillId, []):
|
||||
if str(s.id) in skillLearn:
|
||||
shortcuts.append({
|
||||
'slot': k['slot'],
|
||||
'skill_id': s.id,
|
||||
'skill_name': s.name,
|
||||
})
|
||||
if s is None:
|
||||
shortcuts.append({
|
||||
'slot': k['slot'],
|
||||
'skill_id': None,
|
||||
'skill_name': None,
|
||||
})
|
||||
combos = []
|
||||
for a in r.get('arrangements', []):
|
||||
combo = {
|
||||
'slot_index': a['slot_index'],
|
||||
'skills': [],
|
||||
}
|
||||
for skillIndex in a['skills']:
|
||||
actualSkillId = _resolve_openapi_skill_id(skillIndex, skill_name_map)
|
||||
for s in skillMap.get(actualSkillId, []):
|
||||
if str(s.id) in skillLearn:
|
||||
combo['skills'].append({
|
||||
'skill_id': s.id,
|
||||
'skill_name': s.name,
|
||||
})
|
||||
combos.append(combo)
|
||||
def get_openapi_skill_tree_info(encoded: str) -> dict:
|
||||
"""解析并补全技能树,返回 OpenAPI 技能 ID,不创建计算角色。"""
|
||||
r = decode_skill_hash(encoded)
|
||||
header = r['header']
|
||||
advInfo, skill_tree = _get_skill_tree_context(header)
|
||||
return {
|
||||
"name": header['name'],
|
||||
"level": header['level'],
|
||||
"job":advInfo,
|
||||
"skills": skillLearn,
|
||||
"shortcuts":shortcuts,
|
||||
"combos":combos,
|
||||
'name': header['name'],
|
||||
'level': header['level'],
|
||||
'job': advInfo,
|
||||
**resolve_skill_tree(r, skill_tree),
|
||||
}
|
||||
|
||||
|
||||
def get_skill_tree_info(encoded: str) -> dict:
|
||||
"""将完整 OpenAPI 加点映射为计算器技能 ID,保持现有接口格式。"""
|
||||
from core.basic.character import createCharacter
|
||||
|
||||
info = get_openapi_skill_tree_info(encoded)
|
||||
character = createCharacter(info['job']['class'])
|
||||
skills_by_name = {}
|
||||
for skill in character.skills:
|
||||
skills_by_name.setdefault(skill.name, []).append(skill)
|
||||
|
||||
skill_learn = {}
|
||||
for item in info['skills'].values():
|
||||
candidates = skills_by_name.get(item['name'], [])
|
||||
if not candidates:
|
||||
continue
|
||||
# 保持原有同名技能等级选首项、进化/强化排除觉醒分身的规则。
|
||||
chosen = candidates[0]
|
||||
chosen_id = str(chosen.id)
|
||||
if chosen_id not in skill_learn or skill_learn[chosen_id]['lv'] < item['lv']:
|
||||
skill_learn[chosen_id] = {**item, 'up': 0, 'vp': 0}
|
||||
targets = [s for s in candidates if s.learnLv not in [50, 85]] if len(candidates) > 1 else candidates
|
||||
for skill in targets:
|
||||
learned = skill_learn.get(str(skill.id))
|
||||
if learned is not None:
|
||||
if getattr(skill, 'hasVP', False):
|
||||
learned['vp'] = item['vp']
|
||||
if getattr(skill, 'hasUP', False):
|
||||
learned['up'] = item['up']
|
||||
|
||||
for skill in character.skills:
|
||||
skill_learn.setdefault(str(skill.id), {
|
||||
'lv': skill.calculate_lv() if skill.name == '基础精通' else 0,
|
||||
'up': 0, 'vp': 0, 'name': skill.name,
|
||||
})
|
||||
|
||||
def match_reference(item):
|
||||
candidates = skills_by_name.get(item['skill_name'], [])
|
||||
if len(candidates) > 1:
|
||||
candidates = [s for s in candidates if s.learnLv not in [50, 85]]
|
||||
return [{'skill_id': s.id, 'skill_name': s.name} for s in candidates]
|
||||
|
||||
shortcuts = []
|
||||
for item in info['shortcuts']:
|
||||
matches = match_reference(item) or [{'skill_id': None, 'skill_name': None}]
|
||||
shortcuts.extend({'slot': item['slot'], **match} for match in matches)
|
||||
combos = [
|
||||
{
|
||||
'slot_index': combo['slot_index'],
|
||||
'skills': [match for item in combo['skills'] for match in match_reference(item)],
|
||||
}
|
||||
for combo in info['combos']
|
||||
]
|
||||
return {**info, 'skills': skill_learn, 'shortcuts': shortcuts, 'combos': combos}
|
||||
|
||||
|
||||
# def print_decoded(encoded: str):
|
||||
# """解码并打印技能编码的全部信息。"""
|
||||
# r = decode_skill_hash(encoded)
|
||||
|
||||
Reference in New Issue
Block a user