mirror of
https://github.com/ZZZure/ZZZ-Plugin.git
synced 2025-12-16 05:07:46 +00:00
feature:支持Enka面板更新:%更新展柜面板
This commit is contained in:
parent
0fea67a389
commit
21b8915418
25 changed files with 36048 additions and 382 deletions
64
model/Enka/enkaApi.js
Normal file
64
model/Enka/enkaApi.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { Enka2Mys } from './formater.js'
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
const EnkaApi = 'https://enka.network/api/zzz/uid/'
|
||||
|
||||
export function parsePlayerInfo(SocialDetail = {}) {
|
||||
const ProfileDetail = SocialDetail.ProfileDetail || {}
|
||||
return {
|
||||
game_biz: 'nap_cn',
|
||||
region: 'prod_gf_cn',
|
||||
game_uid: ProfileDetail.Uid || SocialDetail.uid || '114514',
|
||||
nickname: ProfileDetail.Nickname || 'Fairy',
|
||||
level: ProfileDetail.Level || 60,
|
||||
is_chosen: true,
|
||||
region_name: '新艾利都',
|
||||
is_official: true,
|
||||
desc: SocialDetail.Desc || '',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enka更新面板
|
||||
* @param {string|number} uid
|
||||
*/
|
||||
export async function refreshPanelFromEnka(uid) {
|
||||
const res = await fetch(`${EnkaApi}${uid}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'ZZZ-Plugin/UCPr',
|
||||
}
|
||||
})
|
||||
if (!res.ok) {
|
||||
logger.warn(`Enka更新面板失败:${res.status} ${res.statusText}`)
|
||||
return res.status
|
||||
}
|
||||
const data = await res.json()
|
||||
/** @type {import('./interface.ts').Enka.Avatar[]} */
|
||||
const panelList = data?.PlayerInfo?.ShowcaseDetail?.AvatarList
|
||||
if (!panelList || !Array.isArray(panelList)) {
|
||||
logger.warn('Enka更新面板失败:获取面板数据失败')
|
||||
return res.status
|
||||
}
|
||||
if (!panelList.length)
|
||||
console.log('面板列表为空')
|
||||
return {
|
||||
playerInfo: parsePlayerInfo(data.PlayerInfo.SocialDetail),
|
||||
panelList: Enka2Mys(panelList)
|
||||
}
|
||||
}
|
||||
|
||||
// import fs from 'fs'
|
||||
// const uid = 11070609
|
||||
// const res = await fetch(`${EnkaApi}${uid}`, {
|
||||
// method: 'GET',
|
||||
// headers: {
|
||||
// 'User-Agent': 'ZZZ-Plugin/UCPr',
|
||||
// }
|
||||
// })
|
||||
// if (!res.ok) {
|
||||
// console.log(`Enka更新面板失败:${res.status} ${res.statusText}`)
|
||||
// }
|
||||
// const data = await res.json()
|
||||
// console.log(data)
|
||||
// fs.writeFileSync('enkaPanel1.json', JSON.stringify(data, null, 2))
|
||||
524
model/Enka/formater.js
Normal file
524
model/Enka/formater.js
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
import { getMapData } from '../../utils/file.js';
|
||||
var Rarity;
|
||||
(function (Rarity) {
|
||||
Rarity[Rarity["S"] = 4] = "S";
|
||||
Rarity[Rarity["A"] = 3] = "A";
|
||||
Rarity[Rarity["B"] = 2] = "B";
|
||||
})(Rarity || (Rarity = {}));
|
||||
const WeaponId2Data = getMapData('WeaponId2Data');
|
||||
const PartnerId2Data = getMapData('PartnerId2Data');
|
||||
const EquipId2Data = getMapData('EquipId2Data');
|
||||
const SuitData = getMapData('SuitData');
|
||||
const id2zh = {
|
||||
111: '生命值',
|
||||
121: '攻击力',
|
||||
122: '冲击力',
|
||||
131: '防御力',
|
||||
201: '暴击率',
|
||||
211: '暴击伤害',
|
||||
231: '穿透率',
|
||||
232: '穿透值',
|
||||
305: '能量自动回复',
|
||||
312: '异常精通',
|
||||
314: '异常掌控',
|
||||
315: '物理伤害加成',
|
||||
316: '火属性伤害加成',
|
||||
317: '冰属性伤害加成',
|
||||
318: '电属性伤害加成',
|
||||
319: '以太伤害加成'
|
||||
};
|
||||
const zh2id = Object.fromEntries(Object.entries(id2zh).map(([key, value]) => [value, +key]));
|
||||
const id2en = {
|
||||
111: 'HpMax',
|
||||
121: 'Attack',
|
||||
122: 'BreakStun',
|
||||
131: 'Defence',
|
||||
201: 'Crit',
|
||||
211: 'CritDamage',
|
||||
231: 'PenRate',
|
||||
232: 'PenDelta',
|
||||
305: 'SpRecover',
|
||||
312: 'ElementMystery',
|
||||
314: 'ElementAbnormalPower',
|
||||
315: 'PhysDmgBonus',
|
||||
316: 'FireDmgBonus',
|
||||
317: 'IceDmgBonus',
|
||||
318: 'ThunderDmgBonus',
|
||||
319: 'EtherDmgBonus'
|
||||
};
|
||||
const en2id = Object.fromEntries(Object.entries(id2en).map(([key, value]) => [value, +key]));
|
||||
const percentPropId = [11102, 12102, 12202, 13102, 20103, 21103, 23103, 30502, 31402, 31503, 31603, 31703, 31803, 31903];
|
||||
function get_base(propId, value) {
|
||||
if (percentPropId.includes(propId)) {
|
||||
const v = value / 100;
|
||||
return v.toFixed(v % 1 === 0 ? 0 : 1) + '%';
|
||||
}
|
||||
return Math.trunc(value).toString();
|
||||
}
|
||||
export class Equip {
|
||||
enkaEquip;
|
||||
Equipment;
|
||||
id;
|
||||
data;
|
||||
info;
|
||||
equip;
|
||||
constructor(enkaEquip) {
|
||||
this.enkaEquip = enkaEquip;
|
||||
this.Equipment = this.enkaEquip.Equipment;
|
||||
this.id = this.Equipment.Id;
|
||||
this.data = EquipId2Data[`${this.id.toString().slice(0, 3)}00`];
|
||||
if (!this.data) {
|
||||
throw new Error(`驱动盘数据缺失: ${this.id}`);
|
||||
}
|
||||
this.info = this.init();
|
||||
this.equip = this.info;
|
||||
}
|
||||
static main(EquippedList) {
|
||||
const equips = [];
|
||||
for (const equip of EquippedList) {
|
||||
if (equip.Equipment?.Id) {
|
||||
const e = new Equip(equip);
|
||||
equips.push(e.main());
|
||||
}
|
||||
}
|
||||
const cache = {};
|
||||
for (const equip of equips) {
|
||||
const suit_id = equip.equip_suit.suit_id;
|
||||
cache[suit_id] ??= equips.reduce((acc, cur) => {
|
||||
if (cur.equip_suit.suit_id === suit_id) {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
equip.equip_suit.own = cache[suit_id];
|
||||
}
|
||||
return equips;
|
||||
}
|
||||
main() {
|
||||
this.equip.properties = this.properties(this.Equipment.RandomPropertyList, false);
|
||||
this.equip.main_properties = this.properties(this.Equipment.MainPropertyList, true);
|
||||
this.equip.equip_suit = this.equip_suit();
|
||||
return this.equip;
|
||||
}
|
||||
init() {
|
||||
return {
|
||||
id: this.id,
|
||||
level: this.Equipment.Level,
|
||||
name: `${this.data.equip_name}[${this.enkaEquip.Slot}]`,
|
||||
icon: '',
|
||||
rarity: Rarity[+String(this.id)[3]] || 'S',
|
||||
equipment_type: this.enkaEquip.Slot,
|
||||
invalid_property_cnt: 0,
|
||||
all_hit: false
|
||||
};
|
||||
}
|
||||
properties(enkaProperties, isMain) {
|
||||
const properties = [];
|
||||
for (const p of enkaProperties) {
|
||||
const property = {};
|
||||
const propId = p.PropertyId;
|
||||
property.property_name = id2zh[propId.toString().slice(0, 3)];
|
||||
property.property_id = propId;
|
||||
const value = p.PropertyValue * (isMain ? (1 + this.info.level * ({
|
||||
S: 0.2,
|
||||
A: 0.25,
|
||||
B: 0.3
|
||||
})[this.info.rarity]) : p.PropertyLevel);
|
||||
property.base = get_base(propId, value);
|
||||
property.level = p.PropertyLevel;
|
||||
property.valid = false;
|
||||
property.system_id = 0;
|
||||
property.add = p.PropertyLevel - 1;
|
||||
properties.push(property);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
equip_suit() {
|
||||
return {
|
||||
suit_id: +`${this.id.toString().slice(0, 3)}00`,
|
||||
name: this.data.equip_name,
|
||||
own: 0,
|
||||
desc1: this.data.desc1,
|
||||
desc2: this.data.desc2
|
||||
};
|
||||
}
|
||||
}
|
||||
export class Weapon {
|
||||
enkaWeapon;
|
||||
id;
|
||||
data;
|
||||
info;
|
||||
weapon;
|
||||
constructor(enkaWeapon) {
|
||||
this.enkaWeapon = enkaWeapon;
|
||||
this.id = this.enkaWeapon.Id;
|
||||
this.data = WeaponId2Data[this.id];
|
||||
if (!this.data) {
|
||||
throw new Error(`音擎数据缺失: ${this.id}`);
|
||||
}
|
||||
this.info = this.init();
|
||||
this.weapon = this.info;
|
||||
}
|
||||
static main(enkaWeapon) {
|
||||
if (!enkaWeapon)
|
||||
return null;
|
||||
const weapon = new Weapon(enkaWeapon);
|
||||
return weapon.main();
|
||||
}
|
||||
main() {
|
||||
this.weapon.properties = this.properties(false);
|
||||
this.weapon.main_properties = this.properties(true);
|
||||
return this.weapon;
|
||||
}
|
||||
init() {
|
||||
return {
|
||||
id: this.id,
|
||||
level: this.enkaWeapon.Level,
|
||||
name: this.data.Name,
|
||||
star: this.enkaWeapon.UpgradeLevel,
|
||||
icon: '',
|
||||
rarity: this.data.Rarity,
|
||||
talent_title: this.data.Talents[1].Name,
|
||||
talent_content: this.data.Talents[1].Desc,
|
||||
profession: this.data.Profession
|
||||
};
|
||||
}
|
||||
properties(isMain) {
|
||||
const property = {};
|
||||
const p = this.data[isMain ? 'BaseProperty' : 'RandProperty'];
|
||||
const propId = p.Id;
|
||||
property.property_name = id2zh[propId.toString().slice(0, 3)];
|
||||
if (isMain && property.property_name === '攻击力') {
|
||||
property.property_name = '基础攻击力';
|
||||
}
|
||||
property.property_id = propId;
|
||||
let value;
|
||||
const baseValue = p.Value;
|
||||
if (isMain) {
|
||||
value = baseValue * (1 +
|
||||
(this.data.Level[this.info.level].Rate +
|
||||
this.data.Stars[this.enkaWeapon.BreakLevel].StarRate) / 10000);
|
||||
}
|
||||
else {
|
||||
value = baseValue * (1 + this.data.Stars[this.enkaWeapon.BreakLevel].RandRate / 10000);
|
||||
}
|
||||
property.base = get_base(propId, value);
|
||||
property.level = 0;
|
||||
property.valid = false;
|
||||
property.system_id = 0;
|
||||
property.add = 0;
|
||||
return [property];
|
||||
}
|
||||
}
|
||||
let isToFixed = true;
|
||||
export class Property {
|
||||
info;
|
||||
equips;
|
||||
weapon;
|
||||
enkaAvatar;
|
||||
data;
|
||||
keepPercent = [201, 211, 231, 315, 316, 317, 318, 319];
|
||||
constructor(info, equips, weapon, enkaAvatar) {
|
||||
this.info = info;
|
||||
this.equips = equips;
|
||||
this.weapon = weapon;
|
||||
this.enkaAvatar = enkaAvatar;
|
||||
this.data = PartnerId2Data[this.info.id];
|
||||
}
|
||||
static main(info, equips, weapon, enkaAvatar) {
|
||||
return new Property(info, equips, weapon, enkaAvatar).main();
|
||||
}
|
||||
main() {
|
||||
const initial = this.initial();
|
||||
const properties = initial.map(prop => {
|
||||
const propId = prop.property_id;
|
||||
const isPercent = this.keepPercent.includes(propId);
|
||||
if (!isToFixed) {
|
||||
return {
|
||||
property_name: prop.property_name,
|
||||
property_id: prop.property_id,
|
||||
base: isPercent ? `${prop.base}%` : `${prop.base}`,
|
||||
add: isPercent ? `${prop.add}%` : `${prop.add}`,
|
||||
final: isPercent ? `${prop.final}%` : `${prop.final}`,
|
||||
};
|
||||
}
|
||||
const isTofixed2 = propId === 305;
|
||||
return {
|
||||
property_name: prop.property_name,
|
||||
property_id: prop.property_id,
|
||||
base: isPercent ? `${prop.base.toFixed(1)}%` : `${isTofixed2 ? prop.base.toFixed(2) : prop.base}`,
|
||||
add: isPercent ? `${prop.add.toFixed(1)}%` : `${isTofixed2 ? prop.add.toFixed(2) : prop.add}`,
|
||||
final: isPercent ? `${prop.final.toFixed(1)}%` : `${isTofixed2 ? prop.final.toFixed(2) : prop.final}`,
|
||||
};
|
||||
});
|
||||
const elementType2PropId = {
|
||||
200: 315,
|
||||
201: 316,
|
||||
202: 317,
|
||||
203: 318,
|
||||
205: 319
|
||||
};
|
||||
const elementIds = Object.values(elementType2PropId);
|
||||
const propId = elementType2PropId[this.info.element_type];
|
||||
if (propId) {
|
||||
return properties.filter(prop => !elementIds.includes(prop.property_id) || prop.property_id === propId);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
base() {
|
||||
const base = {};
|
||||
const ids = Object.keys(id2en).map(Number);
|
||||
ids.forEach((id) => {
|
||||
const prop = id2en[id];
|
||||
if (Object.prototype.hasOwnProperty.call(this.data, prop)) {
|
||||
base[id] = +this.data[prop] || 0;
|
||||
}
|
||||
else {
|
||||
base[id] = 0;
|
||||
}
|
||||
});
|
||||
[111, 121, 131].forEach((id) => {
|
||||
const prop = id2en[id];
|
||||
if (this.info.level) {
|
||||
const growth = ({
|
||||
111: this.data.HpGrowth,
|
||||
121: this.data.AttackGrowth,
|
||||
131: this.data.DefenceGrowth
|
||||
})[id] || 0;
|
||||
base[id] += (this.info.level - 1) * growth / 10000;
|
||||
}
|
||||
const PromotionLevel = this.enkaAvatar.PromotionLevel || 0;
|
||||
base[id] += this.data.Level[PromotionLevel][prop] || 0;
|
||||
});
|
||||
const CoreSkillEnhancement = this.enkaAvatar.CoreSkillEnhancement || 0;
|
||||
if (CoreSkillEnhancement) {
|
||||
const extra = this.data.ExtraLevel[CoreSkillEnhancement].Extra;
|
||||
const extraIds = Object.keys(extra).map(Number);
|
||||
extraIds.forEach((id) => base[id.toString().slice(0, 3)] += extra[id].Value || 0);
|
||||
}
|
||||
if (this.weapon) {
|
||||
for (const property of this.weapon.main_properties) {
|
||||
base[property.property_id.toString().slice(0, 3)] += +property.base;
|
||||
}
|
||||
}
|
||||
ids.forEach(id => base[id] = Math.trunc(base[id]));
|
||||
[201, 211, 231, 305].forEach(id => base[id] /= 100);
|
||||
return base;
|
||||
}
|
||||
en2id(data) {
|
||||
const idData = {};
|
||||
const ens = Object.keys(data);
|
||||
ens.forEach((en) => {
|
||||
const id = en2id[en];
|
||||
idData[id] = data[en] || 0;
|
||||
});
|
||||
return idData;
|
||||
}
|
||||
en2zh(data) {
|
||||
const idData = this.en2id(data);
|
||||
return this.id2zh(idData);
|
||||
}
|
||||
id2zh(idData) {
|
||||
const zhData = {};
|
||||
const ids = Object.keys(idData).map(Number);
|
||||
ids.forEach(id => {
|
||||
const zh = id2zh[id];
|
||||
if (zhData[zh]) {
|
||||
if (!idData[id])
|
||||
return;
|
||||
zhData[zh] += idData[id];
|
||||
}
|
||||
else {
|
||||
zhData[zh] = idData[id] || 0;
|
||||
}
|
||||
});
|
||||
return zhData;
|
||||
}
|
||||
initial(base = this.id2zh(this.base())) {
|
||||
const properties = Object.entries(base)
|
||||
.reduce((acc, [key, value]) => {
|
||||
const property_id = zh2id[key];
|
||||
acc[property_id] = {
|
||||
property_name: key,
|
||||
property_id,
|
||||
base: value,
|
||||
add: 0,
|
||||
final: 0
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
const all_properties = [];
|
||||
if (this.weapon?.properties.length) {
|
||||
all_properties.push(...this.weapon.properties);
|
||||
}
|
||||
for (const equip of this.equips) {
|
||||
equip.properties.length && all_properties.push(...equip.properties);
|
||||
equip.main_properties.length && all_properties.push(...equip.main_properties);
|
||||
}
|
||||
const suitIds = Array.from(new Set(this.equips.filter(equip => equip.equip_suit.own >= 2).map(v => v.equip_suit.suit_id)));
|
||||
for (const suitId of suitIds) {
|
||||
const suitData = SuitData[suitId];
|
||||
if (!suitData || !suitData.properties) {
|
||||
logger.warn(`驱动盘套装数据缺失: ${suitId},跳过套装效果计算`);
|
||||
continue;
|
||||
}
|
||||
if (!suitData.properties.length)
|
||||
continue;
|
||||
all_properties.push(...suitData.properties);
|
||||
}
|
||||
for (const property of all_properties) {
|
||||
const propId = +property.property_id.toString().slice(0, 3);
|
||||
if (property.base.includes('%')) {
|
||||
if (this.keepPercent.includes(propId)) {
|
||||
properties[propId].add += +property.base.replace('%', '');
|
||||
}
|
||||
else {
|
||||
properties[propId].add += properties[propId].base * +property.base.replace('%', '') / 100;
|
||||
}
|
||||
}
|
||||
else {
|
||||
properties[propId].add += +property.base;
|
||||
}
|
||||
}
|
||||
const sp = special[this.info.id];
|
||||
if (sp && sp.initial_before_format) {
|
||||
sp.initial_before_format(properties, this);
|
||||
}
|
||||
if (isToFixed) {
|
||||
for (const propId in properties) {
|
||||
const property = properties[propId];
|
||||
if (this.keepPercent.includes(+propId)) {
|
||||
property.add = +property.add.toFixed(1);
|
||||
property.final = +(property.base + property.add).toFixed(1);
|
||||
}
|
||||
else if (propId === '305') {
|
||||
property.add = +(Math.trunc(property.add * 100) / 100).toFixed(2);
|
||||
property.final = +(property.base + property.add).toFixed(2);
|
||||
}
|
||||
else if (propId === '111') {
|
||||
property.add = Math.ceil(property.add);
|
||||
property.final = property.base + property.add;
|
||||
}
|
||||
else {
|
||||
property.add = Math.trunc(property.add);
|
||||
property.final = property.base + property.add;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const propId in properties) {
|
||||
const property = properties[propId];
|
||||
property.final = property.base + property.add;
|
||||
}
|
||||
}
|
||||
if (sp && sp.initial_after_format) {
|
||||
sp.initial_after_format(properties, this);
|
||||
}
|
||||
return Object.values(properties);
|
||||
}
|
||||
}
|
||||
export class Skill {
|
||||
static main(skills, rank) {
|
||||
const result = skills.map(skill => ({
|
||||
level: skill.Level,
|
||||
skill_type: skill.Index,
|
||||
items: []
|
||||
}));
|
||||
const rankLevel = rank >= 3 ? (rank >= 5 ? 4 : 2) : 0;
|
||||
if (rankLevel) {
|
||||
result.forEach(skill => {
|
||||
if (skill.skill_type === 5)
|
||||
return;
|
||||
skill.level += rankLevel;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
export function parseInfo(enkaAvatar) {
|
||||
const info = {};
|
||||
info.id = enkaAvatar.Id;
|
||||
const data = PartnerId2Data[info.id];
|
||||
if (!data)
|
||||
return;
|
||||
info.name_mi18n = data.name;
|
||||
info.level = enkaAvatar.Level;
|
||||
info.full_name_mi18n = data.full_name;
|
||||
info.element_type = +data.ElementType;
|
||||
info.camp_name_mi18n = data.Camp;
|
||||
info.avatar_profession = +data.WeaponType;
|
||||
info.rarity = data.Rarity;
|
||||
info.rank = enkaAvatar.TalentLevel;
|
||||
info.us_full_name = data.en_name;
|
||||
info.sub_element_type = ({
|
||||
1091: 1,
|
||||
1371: 2,
|
||||
})[info.id] || 0;
|
||||
info.group_icon_path = '';
|
||||
info.hollow_icon_path = '';
|
||||
info.role_vertical_painting_url = '';
|
||||
info.vertical_painting_color = '';
|
||||
info.role_square_url = '';
|
||||
return info;
|
||||
}
|
||||
export function Enka2Mys(enkaAvatars, __isToFixed__ = true) {
|
||||
isToFixed = __isToFixed__;
|
||||
const avatars = Array.isArray(enkaAvatars) ? enkaAvatars : [enkaAvatars];
|
||||
const results = [];
|
||||
for (const enkaAvatar of avatars) {
|
||||
try {
|
||||
const info = parseInfo(enkaAvatar);
|
||||
if (!info) {
|
||||
throw new Error(`角色数据缺失: ${enkaAvatar.Id}`);
|
||||
}
|
||||
const avatar = info;
|
||||
avatar.ranks = [];
|
||||
avatar.equip = Equip.main(enkaAvatar.EquippedList);
|
||||
avatar.weapon = Weapon.main(enkaAvatar.Weapon);
|
||||
avatar.properties = Property.main(info, avatar.equip, avatar.weapon, enkaAvatar);
|
||||
avatar.skills = Skill.main(enkaAvatar.SkillLevelList, enkaAvatar.TalentLevel);
|
||||
results.push(avatar);
|
||||
}
|
||||
catch (error) {
|
||||
logger.warn(`Enka数据失败 ID: ${enkaAvatar.Id}\n`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Array.isArray(enkaAvatars) ? results : results[0];
|
||||
}
|
||||
const special = {
|
||||
1121: {
|
||||
id: 1121,
|
||||
name: '本',
|
||||
initial_before_format: (properties, { enkaAvatar }) => {
|
||||
const core = [0.4, 0.46, 0.52, 0.6, 0.66, 0.72, 0.8];
|
||||
const { CoreSkillEnhancement } = enkaAvatar;
|
||||
const value = (core[CoreSkillEnhancement] || 0) * Math.trunc(properties[131].base + properties[131].add);
|
||||
properties[121].add += Math.trunc(value);
|
||||
}
|
||||
},
|
||||
1371: {
|
||||
id: 1371,
|
||||
name: '仪玄',
|
||||
initial_after_format: (properties) => {
|
||||
delete properties[231], delete properties[232];
|
||||
delete properties[305];
|
||||
const sheerForce = Math.trunc(0.1 * properties[111].final) +
|
||||
Math.trunc(0.3 * properties[121].final);
|
||||
properties[19] = {
|
||||
property_name: '贯穿力',
|
||||
property_id: 19,
|
||||
base: 0,
|
||||
add: sheerForce,
|
||||
final: sheerForce
|
||||
};
|
||||
properties[20] = {
|
||||
property_name: '闪能自动累积',
|
||||
property_id: 20,
|
||||
base: 2,
|
||||
add: 0,
|
||||
final: 2
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
608
model/Enka/formater.ts
Normal file
608
model/Enka/formater.ts
Normal file
|
|
@ -0,0 +1,608 @@
|
|||
import type { Mys, Enka, Map } from './interface.js'
|
||||
import { getMapData } from '../../utils/file.js'
|
||||
|
||||
type FilterValueType<T, U> = {
|
||||
[K in keyof T as T[K] extends U ? K : never]: T[K]
|
||||
} & {}
|
||||
|
||||
type Values<T> = T extends Record<string, infer U> ? U : never
|
||||
|
||||
enum Rarity {
|
||||
S = 4,
|
||||
A = 3,
|
||||
B = 2
|
||||
}
|
||||
|
||||
const WeaponId2Data = getMapData('WeaponId2Data') as Map.WeaponId2Data
|
||||
const PartnerId2Data = getMapData('PartnerId2Data') as Map.PartnerId2Data
|
||||
const EquipId2Data = getMapData('EquipId2Data') as Map.EquipId2Data
|
||||
const SuitData = getMapData('SuitData') as Map.SuitData
|
||||
|
||||
const id2zh = {
|
||||
111: '生命值',
|
||||
121: '攻击力',
|
||||
122: '冲击力',
|
||||
131: '防御力',
|
||||
201: '暴击率',
|
||||
211: '暴击伤害',
|
||||
231: '穿透率',
|
||||
232: '穿透值',
|
||||
305: '能量自动回复',
|
||||
312: '异常精通',
|
||||
314: '异常掌控',
|
||||
315: '物理伤害加成',
|
||||
316: '火属性伤害加成',
|
||||
317: '冰属性伤害加成',
|
||||
318: '电属性伤害加成',
|
||||
319: '以太伤害加成'
|
||||
} as const
|
||||
const zh2id = Object.fromEntries(
|
||||
Object.entries(id2zh).map(([key, value]) => [value, +key])
|
||||
) as Record<Zhs, number>
|
||||
|
||||
const id2en = {
|
||||
111: 'HpMax',
|
||||
121: 'Attack',
|
||||
122: 'BreakStun',
|
||||
131: 'Defence',
|
||||
201: 'Crit',
|
||||
211: 'CritDamage',
|
||||
231: 'PenRate',
|
||||
232: 'PenDelta',
|
||||
305: 'SpRecover',
|
||||
312: 'ElementMystery',
|
||||
314: 'ElementAbnormalPower',
|
||||
315: 'PhysDmgBonus',
|
||||
316: 'FireDmgBonus',
|
||||
317: 'IceDmgBonus',
|
||||
318: 'ThunderDmgBonus',
|
||||
319: 'EtherDmgBonus'
|
||||
} as const
|
||||
const en2id = Object.fromEntries(
|
||||
Object.entries(id2en).map(([key, value]) => [value, +key])
|
||||
) as Record<Ens, number>
|
||||
|
||||
type Ids = keyof typeof id2zh
|
||||
type IdsString = `${Ids}`
|
||||
type Zhs = Values<typeof id2zh>
|
||||
type Ens = Values<typeof id2en>
|
||||
|
||||
const percentPropId = [11102, 12102, 12202, 13102, 20103, 21103, 23103, 30502, 31402, 31503, 31603, 31703, 31803, 31903]
|
||||
function get_base(propId: number, value: number) {
|
||||
if (percentPropId.includes(propId)) {
|
||||
const v = value / 100
|
||||
return v.toFixed(v % 1 === 0 ? 0 : 1) + '%'
|
||||
}
|
||||
return Math.trunc(value).toString()
|
||||
}
|
||||
|
||||
export class Equip {
|
||||
readonly enkaEquip: Enka.Equip
|
||||
readonly Equipment: Enka.Equip['Equipment']
|
||||
readonly id: number
|
||||
readonly data: Map.EquipId2Data[string]
|
||||
readonly info: FilterValueType<Mys.Equip, string | number | boolean>
|
||||
readonly equip: Mys.Equip
|
||||
constructor(enkaEquip: Enka.Equip) {
|
||||
this.enkaEquip = enkaEquip
|
||||
this.Equipment = this.enkaEquip.Equipment
|
||||
this.id = this.Equipment.Id
|
||||
this.data = EquipId2Data[`${this.id.toString().slice(0, 3)}00`]
|
||||
if (!this.data) {
|
||||
throw new Error(`驱动盘数据缺失: ${this.id}`)
|
||||
}
|
||||
this.info = this.init()
|
||||
this.equip = this.info as Mys.Equip
|
||||
}
|
||||
|
||||
static main(EquippedList: Enka.Avatar['EquippedList']) {
|
||||
const equips: Mys.Equip[] = []
|
||||
for (const equip of EquippedList) {
|
||||
if (equip.Equipment?.Id) {
|
||||
const e = new Equip(equip)
|
||||
equips.push(e.main())
|
||||
}
|
||||
}
|
||||
// 统计own
|
||||
const cache: Record<number, number> = {}
|
||||
for (const equip of equips) {
|
||||
const suit_id = equip.equip_suit.suit_id
|
||||
cache[suit_id] ??= equips.reduce((acc, cur) => {
|
||||
if (cur.equip_suit.suit_id === suit_id) {
|
||||
return acc + 1
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
equip.equip_suit.own = cache[suit_id]
|
||||
}
|
||||
return equips
|
||||
}
|
||||
|
||||
main() {
|
||||
this.equip.properties = this.properties(this.Equipment.RandomPropertyList, false)
|
||||
this.equip.main_properties = this.properties(this.Equipment.MainPropertyList, true)
|
||||
this.equip.equip_suit = this.equip_suit()
|
||||
return this.equip
|
||||
}
|
||||
|
||||
init(): Equip['info'] {
|
||||
return {
|
||||
id: this.id,
|
||||
level: this.Equipment.Level,
|
||||
name: `${this.data.equip_name}[${this.enkaEquip.Slot}]`,
|
||||
icon: '',
|
||||
rarity: Rarity[+String(this.id)[3]] || 'S',
|
||||
equipment_type: this.enkaEquip.Slot,
|
||||
invalid_property_cnt: 0,
|
||||
all_hit: false
|
||||
}
|
||||
}
|
||||
|
||||
properties(enkaProperties: Enka.Property[], isMain: boolean) {
|
||||
const properties: Mys.Property[] = []
|
||||
for (const p of enkaProperties) {
|
||||
const property = {} as Mys.Property
|
||||
const propId = p.PropertyId
|
||||
property.property_name = id2zh[propId.toString().slice(0, 3) as IdsString]
|
||||
property.property_id = propId
|
||||
const value = p.PropertyValue * (isMain ? (1 + this.info.level * ({
|
||||
S: 0.2,
|
||||
A: 0.25,
|
||||
B: 0.3
|
||||
})[this.info.rarity as 'S' | 'A' | 'B']) : p.PropertyLevel)
|
||||
property.base = get_base(propId, value)
|
||||
property.level = p.PropertyLevel
|
||||
property.valid = false
|
||||
property.system_id = 0
|
||||
property.add = p.PropertyLevel - 1
|
||||
properties.push(property)
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
equip_suit(): Mys.Equip['equip_suit'] {
|
||||
return {
|
||||
suit_id: +`${this.id.toString().slice(0, 3)}00`,
|
||||
name: this.data.equip_name,
|
||||
own: 0,
|
||||
desc1: this.data.desc1,
|
||||
desc2: this.data.desc2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Weapon {
|
||||
readonly id: number
|
||||
readonly data: Map.WeaponId2Data[string]
|
||||
readonly info: FilterValueType<Mys.Weapon, string | number | boolean>
|
||||
readonly weapon: Mys.Weapon
|
||||
constructor(readonly enkaWeapon: Enka.Weapon) {
|
||||
this.id = this.enkaWeapon.Id
|
||||
this.data = WeaponId2Data[this.id]
|
||||
if (!this.data) {
|
||||
throw new Error(`音擎数据缺失: ${this.id}`)
|
||||
}
|
||||
this.info = this.init()
|
||||
this.weapon = this.info as Mys.Weapon
|
||||
}
|
||||
|
||||
static main(enkaWeapon: Enka.Avatar['Weapon']) {
|
||||
if (!enkaWeapon) return null
|
||||
const weapon = new Weapon(enkaWeapon)
|
||||
return weapon.main()
|
||||
}
|
||||
|
||||
main() {
|
||||
this.weapon.properties = this.properties(false)
|
||||
this.weapon.main_properties = this.properties(true)
|
||||
return this.weapon
|
||||
}
|
||||
|
||||
init(): Weapon['info'] {
|
||||
return {
|
||||
id: this.id,
|
||||
level: this.enkaWeapon.Level,
|
||||
name: this.data.Name,
|
||||
star: this.enkaWeapon.UpgradeLevel,
|
||||
icon: '',
|
||||
rarity: this.data.Rarity,
|
||||
talent_title: this.data.Talents[1].Name,
|
||||
talent_content: this.data.Talents[1].Desc,
|
||||
profession: this.data.Profession
|
||||
}
|
||||
}
|
||||
|
||||
properties(isMain: boolean) {
|
||||
const property = {} as Mys.Property
|
||||
const p = this.data[isMain ? 'BaseProperty' : 'RandProperty']
|
||||
const propId = p.Id
|
||||
property.property_name = id2zh[propId.toString().slice(0, 3) as IdsString]
|
||||
if (isMain && property.property_name === '攻击力') {
|
||||
property.property_name = '基础攻击力'
|
||||
}
|
||||
property.property_id = propId
|
||||
// 计算属性值
|
||||
let value: number
|
||||
const baseValue = p.Value
|
||||
if (isMain) { // 基础属性
|
||||
value = baseValue * (1 +
|
||||
(this.data.Level[this.info.level].Rate +
|
||||
this.data.Stars[this.enkaWeapon.BreakLevel].StarRate
|
||||
) / 10000)
|
||||
} else { // 高级属性
|
||||
value = baseValue * (1 + this.data.Stars[this.enkaWeapon.BreakLevel].RandRate / 10000)
|
||||
}
|
||||
property.base = get_base(propId, value)
|
||||
property.level = 0
|
||||
property.valid = false
|
||||
property.system_id = 0
|
||||
property.add = 0
|
||||
return [property]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let isToFixed = true
|
||||
|
||||
type initialProperty = {
|
||||
property_name: string
|
||||
property_id: number
|
||||
base: number
|
||||
add: number
|
||||
final: number
|
||||
}
|
||||
|
||||
export class Property {
|
||||
readonly data: Map.PartnerId2Data[string]
|
||||
readonly keepPercent = [201, 211, 231, 315, 316, 317, 318, 319]
|
||||
constructor(
|
||||
readonly info: FilterValueType<Mys.Avatar, string | number | boolean>,
|
||||
readonly equips: Mys.Avatar['equip'],
|
||||
readonly weapon: Mys.Avatar['weapon'],
|
||||
readonly enkaAvatar: Enka.Avatar
|
||||
) {
|
||||
this.data = PartnerId2Data[this.info.id]
|
||||
}
|
||||
|
||||
static main(info: FilterValueType<Mys.Avatar, string | number | boolean>, equips: Mys.Avatar['equip'], weapon: Mys.Avatar['weapon'], enkaAvatar: Enka.Avatar) {
|
||||
return new Property(info, equips, weapon, enkaAvatar).main()
|
||||
}
|
||||
|
||||
main(): Mys.AvatarProperty[] {
|
||||
const initial = this.initial()
|
||||
const properties = initial.map(prop => {
|
||||
const propId = prop.property_id
|
||||
const isPercent = this.keepPercent.includes(propId)
|
||||
if (!isToFixed) {
|
||||
return {
|
||||
property_name: prop.property_name,
|
||||
property_id: prop.property_id,
|
||||
base: isPercent ? `${prop.base}%` : `${prop.base}`,
|
||||
add: isPercent ? `${prop.add}%` : `${prop.add}`,
|
||||
final: isPercent ? `${prop.final}%` : `${prop.final}`,
|
||||
}
|
||||
}
|
||||
const isTofixed2 = propId === 305 // 能量自动回复
|
||||
return {
|
||||
property_name: prop.property_name,
|
||||
property_id: prop.property_id,
|
||||
base: isPercent ? `${prop.base.toFixed(1)}%` : `${isTofixed2 ? prop.base.toFixed(2) : prop.base}`,
|
||||
add: isPercent ? `${prop.add.toFixed(1)}%` : `${isTofixed2 ? prop.add.toFixed(2) : prop.add}`,
|
||||
final: isPercent ? `${prop.final.toFixed(1)}%` : `${isTofixed2 ? prop.final.toFixed(2) : prop.final}`,
|
||||
}
|
||||
})
|
||||
// 筛选属伤
|
||||
const elementType2PropId: Record<number, number> = {
|
||||
200: 315,
|
||||
201: 316,
|
||||
202: 317,
|
||||
203: 318,
|
||||
205: 319
|
||||
}
|
||||
const elementIds = Object.values(elementType2PropId)
|
||||
const propId = elementType2PropId[this.info.element_type]
|
||||
if (propId) {
|
||||
return properties.filter(prop => !elementIds.includes(prop.property_id) || prop.property_id === propId)
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
/** 计算基础属性 */
|
||||
base() {
|
||||
// const base = new Proxy({} as Record<IdsString, number>, {
|
||||
// get(target, prop: IdsString) {
|
||||
// return target[prop] || 0
|
||||
// },
|
||||
// set(target, prop: IdsString, value: number) {
|
||||
// console.log(`${prop}: ${String(target[prop]).padStart(10, ' ')} => ${String(value).padEnd(10, ' ')} ${value - target[prop]}`)
|
||||
// target[prop] = value
|
||||
// return true
|
||||
// }
|
||||
// })
|
||||
const base = {} as Record<IdsString, number>
|
||||
const ids = Object.keys(id2en).map(Number) as (Ids)[]
|
||||
ids.forEach((id) => {
|
||||
const prop = id2en[id]
|
||||
if (Object.prototype.hasOwnProperty.call(this.data, prop)) {
|
||||
// @ts-expect-error
|
||||
base[id] = +this.data[prop] || 0
|
||||
} else {
|
||||
base[id] = 0
|
||||
}
|
||||
});
|
||||
// 生命、攻击、防御随等级额外提升
|
||||
([111, 121, 131] as const).forEach((id) => {
|
||||
const prop = id2en[id]
|
||||
// 等级提升
|
||||
if (this.info.level) {
|
||||
const growth = ({
|
||||
111: this.data.HpGrowth,
|
||||
121: this.data.AttackGrowth,
|
||||
131: this.data.DefenceGrowth
|
||||
})[id] || 0
|
||||
base[id] += (this.info.level - 1) * growth / 10000
|
||||
}
|
||||
// 突破提升
|
||||
const PromotionLevel = this.enkaAvatar.PromotionLevel || 0
|
||||
base[id] += this.data.Level[PromotionLevel][prop] || 0
|
||||
})
|
||||
// 核心技额外提升
|
||||
const CoreSkillEnhancement = this.enkaAvatar.CoreSkillEnhancement || 0
|
||||
if (CoreSkillEnhancement) {
|
||||
const extra = this.data.ExtraLevel[CoreSkillEnhancement].Extra
|
||||
const extraIds = Object.keys(extra).map(Number)
|
||||
extraIds.forEach((id) => base[id.toString().slice(0, 3) as IdsString] += extra[id].Value || 0)
|
||||
}
|
||||
// 处理音擎基础属性
|
||||
if (this.weapon) {
|
||||
for (const property of this.weapon.main_properties) {
|
||||
base[property.property_id.toString().slice(0, 3) as IdsString] += +property.base
|
||||
}
|
||||
}
|
||||
// console.log('----------------base保留整数----------------')
|
||||
// 保留整数
|
||||
ids.forEach(id => base[id] = Math.trunc(base[id]));
|
||||
([201, 211, 231, 305] as const).forEach(id => base[id] /= 100)
|
||||
return base
|
||||
}
|
||||
|
||||
en2id(data: Record<keyof typeof en2id, number>) {
|
||||
const idData = {} as Record<IdsString, number>
|
||||
const ens = Object.keys(data) as (keyof typeof en2id)[]
|
||||
ens.forEach((en) => {
|
||||
const id = en2id[en] as Ids
|
||||
idData[id] = data[en] || 0
|
||||
})
|
||||
return idData
|
||||
}
|
||||
|
||||
en2zh(data: Record<keyof typeof en2id, number>) {
|
||||
const idData = this.en2id(data)
|
||||
return this.id2zh(idData)
|
||||
}
|
||||
|
||||
id2zh(idData: Record<IdsString, number>) {
|
||||
const zhData = {} as typeof zh2id
|
||||
const ids = Object.keys(idData).map(Number) as Ids[]
|
||||
ids.forEach(id => {
|
||||
const zh = id2zh[id]
|
||||
if (zhData[zh]) {
|
||||
if (!idData[id]) return
|
||||
zhData[zh] += idData[id]
|
||||
} else {
|
||||
zhData[zh] = idData[id] || 0
|
||||
}
|
||||
})
|
||||
return zhData
|
||||
}
|
||||
|
||||
/** 计算初始属性 */
|
||||
initial(base = this.id2zh(this.base())) {
|
||||
const properties = Object.entries(base)
|
||||
.reduce((
|
||||
acc: Record<number, initialProperty>,
|
||||
[key, value]
|
||||
) => {
|
||||
const property_id = zh2id[key as keyof typeof zh2id]
|
||||
acc[property_id] = {
|
||||
property_name: key,
|
||||
property_id,
|
||||
base: value,
|
||||
add: 0,
|
||||
final: 0
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
const all_properties: Mys.Property[] = []
|
||||
// 处理音擎高级属性
|
||||
if (this.weapon?.properties.length) {
|
||||
all_properties.push(...this.weapon.properties)
|
||||
}
|
||||
// 处理驱动盘词条
|
||||
for (const equip of this.equips) {
|
||||
equip.properties.length && all_properties.push(...equip.properties)
|
||||
equip.main_properties.length && all_properties.push(...equip.main_properties)
|
||||
}
|
||||
// 处理驱动盘套装效果
|
||||
const suitIds = Array.from(new Set(this.equips.filter(equip => equip.equip_suit.own >= 2).map(v => v.equip_suit.suit_id)))
|
||||
for (const suitId of suitIds) {
|
||||
const suitData = SuitData[suitId]
|
||||
if (!suitData || !suitData.properties) {
|
||||
logger.warn(`驱动盘套装数据缺失: ${suitId},跳过套装效果计算`)
|
||||
continue
|
||||
}
|
||||
if (!suitData.properties.length) continue
|
||||
all_properties.push(...suitData.properties)
|
||||
}
|
||||
for (const property of all_properties) {
|
||||
const propId = +property.property_id.toString().slice(0, 3) as Ids
|
||||
if (property.base.includes('%')) {
|
||||
if (this.keepPercent.includes(propId)) {
|
||||
properties[propId].add += +property.base.replace('%', '')
|
||||
} else {
|
||||
properties[propId].add += properties[propId].base * +property.base.replace('%', '') / 100
|
||||
}
|
||||
} else {
|
||||
properties[propId].add += +property.base
|
||||
}
|
||||
}
|
||||
// 格式化前特殊处理
|
||||
const sp = special[this.info.id]
|
||||
if (sp && sp.initial_before_format) {
|
||||
sp.initial_before_format(properties, this)
|
||||
}
|
||||
// 格式化、计算final
|
||||
if (isToFixed) {
|
||||
for (const propId in properties) {
|
||||
const property = properties[propId]
|
||||
if (this.keepPercent.includes(+propId)) {
|
||||
property.add = +property.add.toFixed(1)
|
||||
property.final = +(property.base + property.add).toFixed(1)
|
||||
} else if (propId === '305') { // 能量自动回复
|
||||
property.add = +(Math.trunc(property.add * 100) / 100).toFixed(2)
|
||||
property.final = +(property.base + property.add).toFixed(2)
|
||||
} else if (propId === '111') { // 生命值
|
||||
property.add = Math.ceil(property.add)
|
||||
property.final = property.base + property.add
|
||||
} else {
|
||||
property.add = Math.trunc(property.add)
|
||||
property.final = property.base + property.add
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const propId in properties) {
|
||||
const property = properties[propId]
|
||||
property.final = property.base + property.add
|
||||
}
|
||||
}
|
||||
// 格式化后特殊处理
|
||||
if (sp && sp.initial_after_format) {
|
||||
sp.initial_after_format(properties, this)
|
||||
}
|
||||
return Object.values(properties)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Skill {
|
||||
static main(skills: Enka.Avatar['SkillLevelList'], rank: Enka.Avatar['TalentLevel']) {
|
||||
const result = skills.map(skill => ({
|
||||
level: skill.Level,
|
||||
skill_type: skill.Index,
|
||||
items: []
|
||||
}))
|
||||
// >=3额外提升2,>=5额外提升4
|
||||
const rankLevel = rank >= 3 ? (rank >= 5 ? 4 : 2) : 0
|
||||
if (rankLevel) {
|
||||
result.forEach(skill => {
|
||||
if (skill.skill_type === 5) return
|
||||
skill.level += rankLevel
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export function parseInfo(enkaAvatar: Enka.Avatar) {
|
||||
const info = {} as FilterValueType<Mys.Avatar, string | number | boolean>
|
||||
info.id = enkaAvatar.Id
|
||||
const data = PartnerId2Data[info.id]
|
||||
if (!data) return
|
||||
info.name_mi18n = data.name
|
||||
info.level = enkaAvatar.Level
|
||||
info.full_name_mi18n = data.full_name
|
||||
info.element_type = +data.ElementType
|
||||
info.camp_name_mi18n = data.Camp
|
||||
info.avatar_profession = +data.WeaponType
|
||||
info.rarity = data.Rarity
|
||||
info.rank = enkaAvatar.TalentLevel
|
||||
info.us_full_name = data.en_name
|
||||
/** 特殊元素属性 */
|
||||
info.sub_element_type = ({
|
||||
1091: 1, // 星见雅
|
||||
1371: 2, // 仪玄
|
||||
})[info.id] || 0
|
||||
info.group_icon_path = ''
|
||||
info.hollow_icon_path = ''
|
||||
info.role_vertical_painting_url = ''
|
||||
info.vertical_painting_color = ''
|
||||
info.role_square_url = ''
|
||||
return info
|
||||
}
|
||||
|
||||
export function Enka2Mys(enkaAvatar: Enka.Avatar, __isToFixed__?: boolean): Mys.Avatar
|
||||
export function Enka2Mys(enkaAvatars: Enka.Avatar[], __isToFixed__?: boolean): Mys.Avatar[]
|
||||
export function Enka2Mys(enkaAvatars: Enka.Avatar | Enka.Avatar[], __isToFixed__ = true) {
|
||||
isToFixed = __isToFixed__
|
||||
const avatars = Array.isArray(enkaAvatars) ? enkaAvatars : [enkaAvatars]
|
||||
const results: Mys.Avatar[] = []
|
||||
for (const enkaAvatar of avatars) {
|
||||
try {
|
||||
const info = parseInfo(enkaAvatar)
|
||||
if (!info) {
|
||||
throw new Error(`角色数据缺失: ${enkaAvatar.Id}`)
|
||||
}
|
||||
const avatar = info as Mys.Avatar
|
||||
avatar.ranks = []
|
||||
avatar.equip = Equip.main(enkaAvatar.EquippedList)
|
||||
avatar.weapon = Weapon.main(enkaAvatar.Weapon)
|
||||
avatar.properties = Property.main(info, avatar.equip, avatar.weapon, enkaAvatar)
|
||||
avatar.skills = Skill.main(enkaAvatar.SkillLevelList, enkaAvatar.TalentLevel)
|
||||
results.push(avatar)
|
||||
} catch (error) {
|
||||
logger.warn(`Enka数据失败 ID: ${enkaAvatar.Id}\n`, error)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return Array.isArray(enkaAvatars) ? results : results[0]
|
||||
}
|
||||
|
||||
/** 特殊处理 */
|
||||
const special: Record<number, {
|
||||
id: number
|
||||
name: string
|
||||
initial_before_format?: (
|
||||
properties: Record<number, initialProperty>,
|
||||
propertyClient: Property
|
||||
) => void
|
||||
initial_after_format?: (
|
||||
properties: Record<number, initialProperty>,
|
||||
propertyClient: Property
|
||||
) => void
|
||||
}> = {
|
||||
1121: {
|
||||
id: 1121,
|
||||
name: '本',
|
||||
initial_before_format: (properties, { enkaAvatar }) => {
|
||||
const core = [0.4, 0.46, 0.52, 0.6, 0.66, 0.72, 0.8]
|
||||
const { CoreSkillEnhancement } = enkaAvatar
|
||||
const value = (core[CoreSkillEnhancement] || 0) * Math.trunc(properties[131].base + properties[131].add)
|
||||
properties[121].add += Math.trunc(value)
|
||||
}
|
||||
},
|
||||
1371: {
|
||||
id: 1371,
|
||||
name: '仪玄',
|
||||
initial_after_format: (properties) => {
|
||||
delete properties[231], delete properties[232]
|
||||
delete properties[305]
|
||||
const sheerForce =
|
||||
Math.trunc(0.1 * properties[111].final) +
|
||||
Math.trunc(0.3 * properties[121].final)
|
||||
properties[19] = {
|
||||
property_name: '贯穿力',
|
||||
property_id: 19,
|
||||
base: 0,
|
||||
add: sheerForce,
|
||||
final: sheerForce
|
||||
}
|
||||
properties[20] = {
|
||||
property_name: '闪能自动累积',
|
||||
property_id: 20,
|
||||
base: 2,
|
||||
add: 0,
|
||||
final: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
model/Enka/interface.js
Normal file
1
model/Enka/interface.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
597
model/Enka/interface.ts
Normal file
597
model/Enka/interface.ts
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
import type { Client, segment as segmentSource } from 'icqq'
|
||||
import type redisM from 'redis'
|
||||
import type chalk from 'chalk'
|
||||
|
||||
declare global {
|
||||
var Bot: typeof Client.prototype
|
||||
var redis: redisM.RedisClientType
|
||||
var segment: typeof segmentSource
|
||||
var logger: {
|
||||
chalk: typeof chalk
|
||||
red: typeof chalk
|
||||
yellow: typeof chalk
|
||||
blue: typeof chalk
|
||||
magenta: typeof chalk
|
||||
cyan: typeof chalk
|
||||
green: typeof chalk
|
||||
trace: (...args: any[]) => void
|
||||
debug: (...args: any[]) => void
|
||||
info: (...args: any[]) => void
|
||||
error: (...args: any[]) => void
|
||||
warn: (...args: any[]) => void
|
||||
fatal: (...args: any[]) => void
|
||||
mark: (...args: any[]) => void
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Mys {
|
||||
|
||||
export interface Avatar {
|
||||
id: number
|
||||
level: number
|
||||
name_mi18n: string
|
||||
full_name_mi18n: string
|
||||
element_type: number
|
||||
camp_name_mi18n: string
|
||||
avatar_profession: number
|
||||
rarity: string
|
||||
group_icon_path: string
|
||||
hollow_icon_path: string
|
||||
equip: Equip[]
|
||||
weapon: Weapon | null
|
||||
properties: AvatarProperty[]
|
||||
skills: Skill[]
|
||||
rank: number
|
||||
ranks: Rank[]
|
||||
role_vertical_painting_url: string
|
||||
equip_plan_info: EquipPlanInfo | null
|
||||
us_full_name: string
|
||||
vertical_painting_color: string
|
||||
sub_element_type: number
|
||||
skin_list: Skin[]
|
||||
role_square_url: string
|
||||
}
|
||||
|
||||
export interface Equip {
|
||||
id: number
|
||||
level: number
|
||||
name: string
|
||||
icon: string
|
||||
rarity: string
|
||||
properties: Property[]
|
||||
main_properties: Property[]
|
||||
equip_suit: EquipSuit
|
||||
equipment_type: number
|
||||
/** 未命中词条数 */
|
||||
invalid_property_cnt: number
|
||||
/** 词条全命中 */
|
||||
all_hit: boolean
|
||||
}
|
||||
|
||||
export interface EquipSuit {
|
||||
suit_id: number
|
||||
name: string
|
||||
own: number
|
||||
desc1: string
|
||||
desc2: string
|
||||
}
|
||||
|
||||
export interface Property {
|
||||
property_name: string
|
||||
property_id: number
|
||||
base: string
|
||||
level: number
|
||||
/** 是否是有效词条 */
|
||||
valid: boolean
|
||||
system_id: number
|
||||
add: number
|
||||
}
|
||||
|
||||
export interface EquipPlanInfo {
|
||||
type: number
|
||||
game_default: CustomInfo
|
||||
cultivate_info: CultivateInfo
|
||||
custom_info: CustomInfo
|
||||
valid_property_cnt: number
|
||||
plan_only_special_property: boolean
|
||||
equip_rating: string
|
||||
plan_effective_property_list: PlanProperty[]
|
||||
equip_rating_score: number
|
||||
}
|
||||
|
||||
export interface CultivateInfo {
|
||||
name: string
|
||||
plan_id: string
|
||||
is_delete: boolean
|
||||
old_plan: boolean
|
||||
}
|
||||
|
||||
export interface CustomInfo {
|
||||
property_list: PlanProperty[]
|
||||
}
|
||||
|
||||
export interface PlanProperty {
|
||||
id: number
|
||||
name: string
|
||||
full_name: string
|
||||
system_id: number
|
||||
is_select: boolean
|
||||
}
|
||||
|
||||
export interface AvatarProperty {
|
||||
property_name: string
|
||||
property_id: number
|
||||
base: string
|
||||
add: string
|
||||
final: string
|
||||
}
|
||||
|
||||
export interface Rank {
|
||||
id: number
|
||||
name: string
|
||||
desc: string
|
||||
pos: number
|
||||
is_unlocked: boolean
|
||||
}
|
||||
|
||||
export interface Skill {
|
||||
level: number
|
||||
skill_type: number
|
||||
items: Item[]
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
title: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface Skin {
|
||||
skin_id: number
|
||||
skin_name: string
|
||||
skin_vertical_painting_url: string
|
||||
skin_square_url: string
|
||||
skin_hollow_icon_path: string
|
||||
skin_vertical_painting_color: string
|
||||
unlocked: boolean
|
||||
rarity: string
|
||||
is_original: boolean
|
||||
}
|
||||
|
||||
export interface Weapon {
|
||||
id: number
|
||||
level: number
|
||||
name: string
|
||||
star: number
|
||||
icon: string
|
||||
rarity: string
|
||||
properties: Property[]
|
||||
main_properties: Property[]
|
||||
talent_title: string
|
||||
talent_content: string
|
||||
profession: number
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Enka {
|
||||
|
||||
export interface Avatar {
|
||||
TalentToggles: boolean[]
|
||||
SkillLevelList: Skill[]
|
||||
EquippedList: Equip[]
|
||||
ClaimedRewards: number[]
|
||||
WeaponEffectState: number
|
||||
IsFavorite: boolean
|
||||
Id: number
|
||||
Level: number
|
||||
/** 突破数 */
|
||||
PromotionLevel: number
|
||||
Exp: number
|
||||
SkinId: number
|
||||
/** 影画数 */
|
||||
TalentLevel: number
|
||||
/** 核心技等级 */
|
||||
CoreSkillEnhancement: number
|
||||
WeaponUid: number
|
||||
ObtainmentTimestamp: number
|
||||
Weapon: Weapon | null
|
||||
}
|
||||
|
||||
export interface Equip {
|
||||
Slot: number
|
||||
Equipment: Equipment
|
||||
}
|
||||
|
||||
export interface Equipment {
|
||||
RandomPropertyList: Property[]
|
||||
MainPropertyList: Property[]
|
||||
IsAvailable: boolean
|
||||
IsLocked: boolean
|
||||
IsTrash: boolean
|
||||
Id: number
|
||||
Uid: number
|
||||
Level: number
|
||||
BreakLevel: number
|
||||
Exp: number
|
||||
}
|
||||
|
||||
export interface Property {
|
||||
PropertyId: number
|
||||
PropertyValue: number
|
||||
PropertyLevel: number
|
||||
}
|
||||
|
||||
export interface Skill {
|
||||
Level: number
|
||||
Index: number
|
||||
}
|
||||
|
||||
export interface Weapon {
|
||||
IsAvailable: boolean
|
||||
IsLocked: boolean
|
||||
Id: number
|
||||
Uid: number
|
||||
Level: number
|
||||
BreakLevel: number
|
||||
Exp: number
|
||||
UpgradeLevel: number
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Map {
|
||||
|
||||
export interface WeaponId2Data {
|
||||
[id: string]: {
|
||||
Id: number
|
||||
CodeName: string
|
||||
Name: string
|
||||
Desc: string
|
||||
Desc2: string
|
||||
Desc3: string
|
||||
Rarity: string
|
||||
Icon: string
|
||||
WeaponType: { [key: string]: string }
|
||||
BaseProperty: {
|
||||
Name: string
|
||||
Name2: string
|
||||
Format: string
|
||||
Value: number
|
||||
Id: number
|
||||
}
|
||||
RandProperty: {
|
||||
Name: string
|
||||
Name2: string
|
||||
Format: string
|
||||
Value: number
|
||||
Id: number
|
||||
}
|
||||
Level: {
|
||||
[key: string]: {
|
||||
Exp: number
|
||||
Rate: number
|
||||
Rate2: number
|
||||
}
|
||||
}
|
||||
Stars: {
|
||||
[key: string]: {
|
||||
StarRate: number
|
||||
RandRate: number
|
||||
}
|
||||
}
|
||||
Materials: string
|
||||
Talents: {
|
||||
[key: string]: {
|
||||
Name: string
|
||||
Desc: string
|
||||
}
|
||||
}
|
||||
Profession: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface PartnerId2Data {
|
||||
[id: string]: {
|
||||
sprite_id: string
|
||||
name: string
|
||||
full_name: string
|
||||
en_name: string
|
||||
WeaponType: string
|
||||
ElementType: string
|
||||
Camp: string
|
||||
HitType: string
|
||||
Rarity: string
|
||||
Attack: number
|
||||
AttackGrowth: number
|
||||
BreakStun: number
|
||||
Defence: number
|
||||
DefenceGrowth: number
|
||||
HpMax: number
|
||||
HpGrowth: number
|
||||
Crit: number
|
||||
CritDamage: number
|
||||
ElementAbnormalPower: number
|
||||
ElementMystery: number
|
||||
PenDelta: number
|
||||
PenRate: number
|
||||
SpRecover: number
|
||||
/** 角色等级提升 */
|
||||
Level: {
|
||||
[level: string]: {
|
||||
HpMax: number
|
||||
Attack: number
|
||||
Defence: number
|
||||
LevelMax: number
|
||||
LevelMin: number
|
||||
Materials: { [key: string]: number }
|
||||
}
|
||||
}
|
||||
/** 核心技等级提升 */
|
||||
ExtraLevel: {
|
||||
[level: string]: {
|
||||
MaxLevel: number
|
||||
Extra: {
|
||||
[key: string]: {
|
||||
Prop: number
|
||||
Name: string
|
||||
Format: string
|
||||
Value: number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface EquipId2Data {
|
||||
[id: string]: {
|
||||
equip_id_list: number[]
|
||||
sprite_file: string
|
||||
equip_name: string
|
||||
desc1: string
|
||||
desc2: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface SuitData {
|
||||
[suit_id: string]: {
|
||||
name: string
|
||||
desc2: string
|
||||
desc4: string
|
||||
properties: Mys.Property[]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Hakush {
|
||||
export interface PartnerData {
|
||||
Id: number
|
||||
Icon: string
|
||||
Name: string
|
||||
CodeName: string
|
||||
Rarity: number
|
||||
WeaponType: {
|
||||
[profession_id: string]: string
|
||||
}
|
||||
ElementType: {
|
||||
[element_id: string]: string
|
||||
}
|
||||
SpecialElementType: {
|
||||
Name: string
|
||||
Title: string
|
||||
Desc: string
|
||||
Icon: string
|
||||
}
|
||||
HitType: {
|
||||
[hit_id: string]: string
|
||||
}
|
||||
Camp: {
|
||||
[id: string]: string
|
||||
}
|
||||
Gender: number
|
||||
PartnerInfo: {
|
||||
Birthday: string
|
||||
FullName: string
|
||||
Gender: string
|
||||
IconPath: string
|
||||
ImpressionF: string
|
||||
ImpressionM: string
|
||||
Name: string
|
||||
OutlookDesc: string
|
||||
ProfileDesc: string
|
||||
Race: string
|
||||
RoleIcon: string
|
||||
Stature: string
|
||||
UnlockCondition: string[]
|
||||
TrustLv: { [key: string]: string }
|
||||
}
|
||||
Skin: {
|
||||
[skin_id: string]: {
|
||||
Name: string
|
||||
Desc: string
|
||||
Image: string
|
||||
}
|
||||
}
|
||||
Stats: {
|
||||
Armor: number
|
||||
ArmorGrowth: number
|
||||
Attack: number
|
||||
AttackGrowth: number
|
||||
AvatarPieceId: number
|
||||
BreakStun: number
|
||||
Crit: number
|
||||
CritDamage: number
|
||||
CritDmgRes: number
|
||||
CritRes: number
|
||||
Defence: number
|
||||
DefenceGrowth: number
|
||||
ElementAbnormalPower: number
|
||||
ElementMystery: number
|
||||
Endurance: number
|
||||
HpGrowth: number
|
||||
HpMax: number
|
||||
PenDelta: number
|
||||
PenRate: number
|
||||
Rbl: number
|
||||
RblCorrectionFactor: number
|
||||
RblProbability: number
|
||||
Shield: number
|
||||
ShieldGrowth: number
|
||||
SpBarPoint: number
|
||||
SpRecover: number
|
||||
Stun: number
|
||||
Tags: string[]
|
||||
RpMax: number
|
||||
RpRecover: number
|
||||
}
|
||||
Level: {
|
||||
[key: string]: {
|
||||
HpMax: number
|
||||
Attack: number
|
||||
Defence: number
|
||||
LevelMax: number
|
||||
LevelMin: number
|
||||
Materials: { [key: string]: number }
|
||||
}
|
||||
}
|
||||
ExtraLevel: {
|
||||
[key: string]: {
|
||||
MaxLevel: number
|
||||
Extra: { [key: string]: Part4 }
|
||||
}
|
||||
}
|
||||
LevelEXP: number[]
|
||||
Skill: {
|
||||
Basic: {
|
||||
Description: AssistDescription[]
|
||||
Material: { [key: string]: { [key: string]: number } }
|
||||
}
|
||||
Dodge: {
|
||||
Description: AssistDescription[]
|
||||
Material: { [key: string]: { [key: string]: number } }
|
||||
}
|
||||
Special: {
|
||||
Description: {
|
||||
Name: string
|
||||
Desc?: string
|
||||
Potential: any[]
|
||||
Param?: {
|
||||
Name: string
|
||||
Desc: string
|
||||
Param?: {
|
||||
[key: string]: {
|
||||
Main: number
|
||||
Growth: number
|
||||
Format: string
|
||||
DamagePercentage: number
|
||||
DamagePercentageGrowth: number
|
||||
StunRatio: number
|
||||
StunRatioGrowth: number
|
||||
SpRecovery: number
|
||||
SpRecoveryGrowth: number
|
||||
FeverRecovery: number
|
||||
FeverRecoveryGrowth: number
|
||||
AttributeInfliction: number
|
||||
SpConsume: number
|
||||
AttackData: any[]
|
||||
RpRecovery: number
|
||||
RpRecoveryGrowth: number
|
||||
}
|
||||
}
|
||||
Potential: any[]
|
||||
}[]
|
||||
}[]
|
||||
Material: { [key: string]: { [key: string]: number } }
|
||||
}
|
||||
Chain: {
|
||||
Description: AssistDescription[]
|
||||
Material: { [key: string]: { [key: string]: number } }
|
||||
}
|
||||
Assist: {
|
||||
Description: AssistDescription[]
|
||||
Material: { [key: string]: { [key: string]: number } }
|
||||
}
|
||||
}
|
||||
SkillList: {
|
||||
[key: string]: {
|
||||
Name: string
|
||||
Desc: string
|
||||
ElementType: number
|
||||
HitType: number
|
||||
Potential: any[]
|
||||
}
|
||||
}
|
||||
Passive: {
|
||||
Level: {
|
||||
[key: string]: {
|
||||
Level: number
|
||||
Id: number
|
||||
Name: string[]
|
||||
Desc: string[]
|
||||
ExtraProperty: {}
|
||||
Potential: any[]
|
||||
}
|
||||
}
|
||||
Materials: { [key: string]: { [key: string]: number } }
|
||||
}
|
||||
Talent: {
|
||||
[key: string]: {
|
||||
Level: number
|
||||
Name: string
|
||||
Desc: string
|
||||
Desc2: string
|
||||
}
|
||||
}
|
||||
FairyRecommend: {
|
||||
Slot4: number
|
||||
Slot2: number
|
||||
SlotSub: number
|
||||
Part4: Part4
|
||||
Part5: Part4
|
||||
Part6: Part4
|
||||
PartSub: Part4
|
||||
}
|
||||
Potential: any[]
|
||||
}
|
||||
|
||||
interface Part4 {
|
||||
Prop: number
|
||||
Name: string
|
||||
Format: string
|
||||
Value?: number
|
||||
Icon?: string
|
||||
}
|
||||
|
||||
interface AssistDescription {
|
||||
Name: string
|
||||
Desc?: string
|
||||
Potential: any[]
|
||||
Param?: {
|
||||
Name: string
|
||||
Desc: string
|
||||
Param: {
|
||||
[key: string]: {
|
||||
Main: number
|
||||
Growth: number
|
||||
Format: string
|
||||
DamagePercentage: number
|
||||
DamagePercentageGrowth: number
|
||||
StunRatio: number
|
||||
StunRatioGrowth: number
|
||||
SpRecovery: number
|
||||
SpRecoveryGrowth: number
|
||||
FeverRecovery: number
|
||||
FeverRecoveryGrowth: number
|
||||
AttributeInfliction: number
|
||||
SpConsume: number
|
||||
AttackData: any[]
|
||||
RpRecovery: number
|
||||
RpRecoveryGrowth: number
|
||||
}
|
||||
}
|
||||
Potential: any[]
|
||||
}[]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
示例图:
|
||||
|
||||
<p align="center">
|
||||
<img width=800 src="https://s2.loli.net/2025/03/27/OcuIPDyE5sHSJZw.jpg" title="词条权重自定义基础步骤">
|
||||
<img width=800 src="https://s2.loli.net/2025/06/01/aTyPoZ4gi89MqRC.jpg" title="词条权重自定义基础步骤">
|
||||
</p>
|
||||
|
||||
### 进阶操作
|
||||
|
|
@ -169,7 +169,7 @@ Buff来源可分为三大类:武器、套装、角色(影画、核心被动
|
|||
|
||||
- 主词条的提升会自动注册,无需处理
|
||||
|
||||
- 二件套效果只有**属性伤害提升**需要注册,其他已包含于初始属性
|
||||
- 二件套效果只有**属性伤害提升**和**局外面板以外的效果**(如[追加攻击]和[冲刺攻击]造成的伤害提升)需要注册,其他已包含于初始属性
|
||||
|
||||
- 四件套效果需要单独注册
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ async function init() {
|
|||
// debug模式下监听文件变化
|
||||
const isWatch = await (async () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return (await import('../../../../lib/config/config.js')).default.bot.log_level === 'debug'
|
||||
} catch {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default function (avatar) {
|
|||
"暴击伤害": 1,
|
||||
"穿透率": 0.75,
|
||||
"穿透值": 0.25,
|
||||
"能量回复": 0,
|
||||
"能量自动回复": 0,
|
||||
"异常精通": 0,
|
||||
"异常掌控": 0,
|
||||
"冰属性伤害加成": 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue