初步支持星铁面板请求

This commit is contained in:
Kokomi 2023-05-15 04:19:33 +08:00
parent e7ec276953
commit 838f102996
17 changed files with 322 additions and 484 deletions

View File

@ -14,6 +14,14 @@ let app = App.init({
})
app.reg({
profileList: {
name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表',
fn: ProfileList.render,
rule: /^#(星铁|原神)?(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/
},
profileDetail: {
name: '角色面板',
fn: ProfileDetail.detail,
@ -62,13 +70,6 @@ app.reg({
rule: /^#圣遗物列表\s*(\d{9})?$/
},
profileList: {
name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表',
fn: ProfileList.render,
rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/
},
profileStat: {
name: '面板练度统计',
fn: ProfileStat.stat,
@ -102,7 +103,7 @@ app.reg({
name: '面板更新',
describe: '【#角色】 获取游戏橱窗详情数据',
fn: ProfileList.refresh,
rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/
rule: /^#(星铁|原神)?(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/
},
uploadImg: {

View File

@ -79,6 +79,7 @@ const ProfileList = {
await player.refresh({ profile: true })
}
if (!player.hasProfile) {
console.log(player.game)
e.reply(`本地暂无uid${uid}的面板数据...`)
return true
}

View File

@ -36,6 +36,13 @@ export const hutaoApi = {
}
}
export const luluApi = {
url: 'https://mhy.fuckmys.tk/sr_info',
listApi: ({ url, uid, diyCfg }) => {
return `${url}/${uid}`
}
}
export const requestInterval = 3
export const isSys = true

View File

@ -27,6 +27,9 @@ export default class AvatarArtis extends Base {
}
get hasAttr () {
if (this.isSr) {
return true
}
return ArtisMark.hasAttr(this.artis)
}

View File

@ -9,7 +9,7 @@ import Profile from './player/Profile.js'
const charKey = 'name,abbr,sName,star,imgs,face,side,gacha,weaponTypeName'.split(',')
export default class AvatarData extends Base {
constructor (ds = {}, source) {
constructor (ds = {}, source, game = 'gs') {
super()
let char = Character.get({ id: ds.id, elem: ds.elem })
if (!char) {
@ -17,6 +17,7 @@ export default class AvatarData extends Base {
}
this.id = char.id
this.char = char
this.game = char.game || game
this.initArtis()
this.setAvatar(ds, source)
}
@ -82,8 +83,8 @@ export default class AvatarData extends Base {
return ''
}
static create (ds, source) {
let avatar = new AvatarData(ds)
static create (ds, source = '', game = 'gs') {
let avatar = new AvatarData(ds, source, game)
if (!avatar) {
return false
}
@ -141,6 +142,7 @@ export default class AvatarData extends Base {
}
setWeapon (ds = {}) {
let w = Weapon.get(ds.name)
if (!w) {
return false
@ -199,9 +201,16 @@ export default class AvatarData extends Base {
getDetail (keys = '') {
let imgs = this.char.getImgs(this.costume)
return {
...(this.getData(keys || 'id,name,level,star,cons,fetter,elem,abbr,weapon,talent,artisSet') || {}),
...Data.getData(imgs, 'face,qFace,side,gacha')
if (this.isGs) {
return {
...(this.getData(keys || 'id,name,level,star,cons,fetter,elem,abbr,weapon,talent,artisSet') || {}),
...Data.getData(imgs, 'face,qFace,side,gacha')
}
} else {
return {
...(this.getData(keys || 'id,name,level,star,cons,fetter,elem,abbr,weapon,talent,artisSet') || {}),
...Data.getData(imgs, 'face,qFace,gacha,preview')
}
}
}

View File

@ -12,6 +12,7 @@ let metaMap = {}
export default class Base {
constructor () {
this.game = 'gs'
let proxy = new Proxy(this, {
get (self, key, receiver) {
if (self._uuid && key === 'meta') {
@ -92,4 +93,12 @@ export default class Base {
delete cacheMap[id]
delete metaMap[id]
}
isSr () {
return this.game === 'sr'
}
isGs () {
return !this.isSr
}
}

View File

@ -15,15 +15,19 @@ import CharCfg from './character/CharCfg.js'
let { wifeMap, idSort, idMap } = CharId
let getMeta = function (name) {
return Data.readJSON(`resources/meta/character/${name}/data.json`, 'miao')
let getMeta = function (name, game = 'gs') {
if (game === 'gs') {
return Data.readJSON(`resources/meta/character/${name}/data.json`, 'miao')
} else {
return CharId.getSrMeta(name)
}
}
class Character extends Base {
// 默认获取的数据
_dataKey = 'id,name,abbr,title,star,elem,allegiance,weapon,birthday,astro,cncv,jpcv,ver,desc,talentCons'
constructor ({ id, name = '', elem = '' }) {
constructor ({ id, name = '', elem = '', game = 'gs' }) {
super()
// 检查缓存
let cacheObj = this._getCache(CharId.isTraveler(id) ? `character:${id}:${elem || 'anemo'}` : `character:${id}`)
@ -33,10 +37,13 @@ class Character extends Base {
// 设置数据
this._id = id
this.name = name
this.game = game
if (!this.isCustom) {
let meta = getMeta(name)
let meta = getMeta(name, game)
this.meta = meta
this.elem = Format.elem(elem || meta.elem, 'anemo')
if (this.isGs) {
this.elem = Format.elem(elem || meta.elem, 'anemo')
}
} else {
this.meta = {}
}
@ -45,7 +52,7 @@ class Character extends Base {
// 是否为官方角色
get isOfficial () {
return /[12]0\d{6}/.test(this._id)
return this.game === 'sr' || /[12]0\d{6}/.test(this._id)
}
// 是否为实装官方角色
@ -61,13 +68,21 @@ class Character extends Base {
// 是否为自定义角色
get isCustom () {
return !/[12]0\d{6}/.test(this._id)
return !this.isOfficial
}
get id () {
return this.isCustom ? this._id : this._id * 1
}
get isGs () {
return this.game === 'gs'
}
get isSr () {
return this.game === 'sr'
}
// 获取短名字
get sName () {
let name = this.name
@ -77,7 +92,7 @@ class Character extends Base {
// 是否是旅行者
get isTraveler () {
return CharId.isTraveler(this.id)
return this.isGs && CharId.isTraveler(this.id)
}
get weaponType () {
@ -86,6 +101,9 @@ class Character extends Base {
// 获取武器类型
get weaponTypeName () {
if (this.isSr) {
return this.weapon
}
const map = {
sword: '单手剑',
catalyst: '法器',
@ -99,6 +117,9 @@ class Character extends Base {
// 获取元素名称
get elemName () {
if (this.isSr) {
return this.elem
}
return Format.elemName(this.elem)
}
@ -114,6 +135,9 @@ class Character extends Base {
// 获取侧脸图像
get side () {
if (this.isSr) {
return this.getImgs().face
}
return this.getImgs().side
}
@ -134,6 +158,9 @@ class Character extends Base {
// 获取命座天赋等级
get talentCons () {
if (this.isSr) {
return this.meta?.talentCons || {}
}
if (this.isTraveler) {
return this.elem === 'dendro' ? { e: 3, q: 5 } : { e: 5, q: 3 }
}
@ -151,17 +178,20 @@ class Character extends Base {
}
// 基于角色名获取Character
static get (val) {
let id = CharId.getId(val, Character.gsCfg)
static get (val, game = 'gs') {
let id = CharId.getId(val, Character.gsCfg, game)
if (!id) {
return false
}
return new Character(id)
}
static forEach (fn, type = 'all') {
static forEach (fn, type = 'all', game = 'gs') {
lodash.forEach(idMap, (name, id) => {
let char = Character.get({ id, name })
if (char.game !== 'game') {
return true
}
if (type === 'release' && !char.isRelease) {
return true
}
@ -197,7 +227,6 @@ class Character extends Base {
}
// 设置旅行者数据
getAvatarTalent (talent = {}, cons = 0, mode = 'original') {
return CharTalent.getAvatarTalent(this.id, talent, cons, mode, this.talentCons)
}
@ -230,12 +259,16 @@ class Character extends Base {
this._imgs = {}
}
if (!this._imgs[cacheId]) {
this._imgs[cacheId] = CharImg.getImgs(this.name, costumeIdx, this.isTraveler ? this.elem : '', this.weaponType, this.talentCons)
if (this.isSr) {
this._imgs[cacheId] = CharImg.getImgsSr(this.name, this.talentCons)
} else {
this._imgs[cacheId] = CharImg.getImgs(this.name, costumeIdx, this.isTraveler ? this.elem : '', this.weaponType, this.talentCons)
}
}
let imgs = this._imgs[cacheId]
return {
...imgs,
qFace: Cfg.get('qFace') ? imgs.qFace : imgs.face
qFace: Cfg.get('qFace') ? (imgs.qFace || imgs.face) : imgs.face
}
}
@ -249,7 +282,7 @@ class Character extends Base {
if (this.isCustom) {
return {}
}
const path = 'resources/meta/character'
const path = this.isSr ? 'resources/meta-sr/character' : 'resources/meta/character'
try {
if (this.isTraveler) {

View File

@ -13,9 +13,12 @@ import MysAvatar from './player/MysAvatar.js'
import Profile from './player/Profile.js'
Data.createDir('/data/UserData', 'root')
Data.createDir('/data/PlayerData/gs', 'root')
Data.createDir('/data/PlayerData/sr', 'root')
export default class Player extends Base {
constructor (uid) {
constructor (uid, game = 'gs') {
super()
uid = uid?._mys?.uid || uid?.uid || uid
if (!uid) {
@ -26,6 +29,7 @@ export default class Player extends Base {
return cacheObj
}
this.uid = uid
this.game = game
this.reload()
return this._cache(100)
}
@ -41,25 +45,33 @@ export default class Player extends Base {
return ret
}
static create (e) {
get _file () {
if (this.isSr) {
return `/data/PlayerData/sr/${this.uid}.json`
} else {
return `/data/UserData/${this.uid}.json`
}
}
static create (e, game = 'gs') {
if (e?._mys?.uid || e.uid) {
// 传入为e
let player = new Player(e?._mys?.uid || e.uid)
let player = new Player(e?._mys?.uid || e.uid, (game === 'sr' || e.isSr) ? 'sr' : 'gs')
player.e = e
return player
} else {
return new Player(e)
return new Player(e, game)
}
}
// 获取面板更新服务名
static getProfileServName (uid) {
let Serv = Profile.getServ(uid)
static getProfileServName (uid, game = 'gs') {
let Serv = Profile.getServ(uid, game)
return Serv.name
}
static delByUid (uid) {
let player = Player.create(uid)
static delByUid (uid, game = 'gs') {
let player = Player.create(uid, game)
if (player) {
player.del()
}
@ -70,7 +82,7 @@ export default class Player extends Base {
*/
reload () {
let data
data = Data.readJSON(`/data/UserData/${this.uid}.json`, 'root')
data = Data.readJSON(this._file, 'root')
this.setBasicData(data)
if (data.chars) {
this.setAvatars(data.chars)
@ -104,13 +116,17 @@ export default class Player extends Base {
if (this._ck) {
ret._ck = this._ck
}
Data.writeJSON(`/data/UserData/${this.uid}.json`, ret, 'root')
if (this.isSr) {
Data.writeJSON(`/data/PlayerData/sr/${this.uid}.json`, ret, 'root')
} else {
Data.writeJSON(`/data/UserData/${this.uid}.json`, ret, 'root')
}
}
del () {
try {
Data.delFile(`/data/UserData/${this.uid}.json`, 'root')
ProfileRank.delUidInfo(this.uid)
Data.delFile(this._file, 'root')
ProfileRank.delUidInfo(this.uid, this.game)
this._delCache()
Bot.logger.mark(`【面板数据删除】${this.uid}本地文件数据已删除...`)
} catch (e) {
@ -153,10 +169,13 @@ export default class Player extends Base {
// 获取Avatar角色
getAvatar (id, create = false) {
let char = Character.get(id)
console.log('getAvatar', char.id)
let avatars = this._avatars
// 兼容处理旅行者的情况
if (char.isTraveler && !create) {
id = avatars['10000005'] ? 10000005 : 10000007
if (this.isGs) {
// 兼容处理旅行者的情况
if (char.isTraveler && !create) {
id = avatars['10000005'] ? 10000005 : 10000007
}
}
if (!avatars[id] && create) {
avatars[id] = AvatarData.create({ id })

View File

@ -8,7 +8,7 @@ import CharImg from './character/CharImg.js'
export default class ProfileData extends AvatarData {
constructor (ds = {}, calc = true) {
super(ds)
if (calc) {
if (calc && !this.isSr) {
this.calcAttr()
}
}

View File

@ -70,7 +70,7 @@ export default class ProfileReq extends Base {
let reqParam = await serv.getReqParam(uid)
let cdTime = await this.inCd()
if (cdTime && !process.argv.includes('web-debug')) {
return this.err(`请求过快,请${cdTime}秒后重试..`)
// return this.err(`请求过快,请${cdTime}秒后重试..`)
}
await this.setCd(20)
// 若3秒后还未响应则返回提示

View File

@ -5,6 +5,7 @@ import lodash from 'lodash'
import { Data, Format } from '#miao'
import { charPosIdx } from './CharMeta.js'
// 别名表
let aliasMap = {}
// ID表
@ -16,8 +17,23 @@ let wifeMap = {}
// id排序
let idSort = {}
let gameMap = {}
let srData = Data.readJSON('/resources/meta-sr/character/data.json', 'miao')
async function init () {
let { sysCfg, diyCfg } = await Data.importCfg('character')
lodash.forEach(srData, (ds) => {
let { id, name } = ds
aliasMap[id] = id
aliasMap[name] = id
idMap[id] = name
gameMap[id] = 'sr'
})
lodash.forEach([diyCfg.customCharacters, sysCfg.characters], (roleIds) => {
lodash.forEach(roleIds || {}, (aliases, id) => {
aliases = aliases || []
@ -31,6 +47,7 @@ async function init () {
})
aliasMap[id] = id
idMap[id] = aliases[0]
gameMap[id] = 'gs'
})
})
@ -66,15 +83,26 @@ lodash.forEach(charPosIdx, (chars, pos) => {
const CharId = {
aliasMap,
idMap,
gameMap,
abbrMap,
wifeMap,
idSort,
getId (ds = '', gsCfg = null) {
isGs (id) {
return gameMap[id] === 'gs'
},
isSr (id) {
return gameMap[id] === 'sr'
},
getId (ds = '', gsCfg = null, game = 'gs') {
if (!ds) {
return false
}
const ret = (id, elem = '') => {
return { id, elem, name: idMap[id] }
if (CharId.isSr(id)) {
return { id, name: idMap[id], game: 'sr' }
} else {
return { id, elem, name: idMap[id], game: 'gs' }
}
}
if (!lodash.isObject(ds)) {
let original = lodash.trim(ds || '')
@ -114,15 +142,19 @@ const CharId = {
return false
},
isTraveler (id) {
isTraveler (id, game = 'gs') {
if (id) {
return [10000007, 10000005, 20000000].includes(id * 1)
}
return false
},
getTravelerId (id) {
getTravelerId (id, game = 'gs') {
return id * 1 === 10000005 ? 10000005 : 10000007
},
getSrMeta (name) {
return srData?.[aliasMap[name]] || {}
}
}
export default CharId

View File

@ -118,6 +118,31 @@ const CharImg = {
imgs.e = talentCons.e === 3 ? imgs['cons3'] : imgs['cons5']
imgs.q = talentCons.q === 5 ? imgs['cons5'] : imgs['cons3']
return imgs
},
getImgsSr (name, talentCons) {
let fileType = 'webp'
const nPath = `/meta-sr/character/${name}/`
let imgs = {}
let add = (key, path, path2) => {
imgs[key] = `${nPath}${path}.${fileType}`
}
add('face', 'imgs/face')
add('splash', 'imgs/splash')
add('preview', 'imgs/preview')
for (let i = 1; i <= 3; i++) {
add(`tree${i}`, `imgs/tree-${i}`)
}
for (let key of ['a', 'e', 'q', 't', 'z']) {
add(key, `imgs/talent-${key}`)
}
for (let i = 1; i <= 6; i++) {
if (i !==3 && i !== 5) {
add(`cons${i}`, `imgs/cons-${i}`)
}
}
imgs.cons3 = imgs[talentCons[3]]
imgs.cons5 = imgs[talentCons[5]]
return imgs
}
}
export default CharImg

View File

@ -0,0 +1,6 @@
import Character from '../Character.js'
class Character extends Character {
}

109
models/player/LuluApi.js Normal file
View File

@ -0,0 +1,109 @@
import lodash from 'lodash'
import EnkaData from './EnkaData.js'
import { Data } from '#miao'
import { Character } from '#miao.models'
export default {
id: 'lulu',
name: '路路Api',
cfgKey: 'luluApi',
// 处理请求参数
async request (api) {
let params = {
headers: { 'User-Agent': this.getCfg('userAgent') }
}
return { api, params }
},
// 处理服务返回
async response (data, req) {
if (!data.PlayerDetailInfo) {
return req.err('error', 60)
}
let ds = data.PlayerDetailInfo
let ac = ds.AssistAvatar
let avatars = {}
if (ac && !lodash.isEmpty(ac)) {
avatars[ac.AvatarID] = ac
}
lodash.forEach(ds.DisplayAvatarList, (ds) => {
avatars[ds.AvatarID] = ds
})
if (lodash.isEmpty(avatars)) {
return req.err('empty', 5 * 60)
}
data.avatars = avatars
return data
},
updatePlayer (player, data) {
player.setBasicData(Data.getData(data, 'name:NickName,face:HeadIconID,level:Level,word:WorldLevel,sign:Signature'))
console.log('avatars', data.avatars)
lodash.forEach(data.avatars, (ds, id) => {
console.log('ret1', ds)
let ret = LuluData.setAvatar(player, ds)
console.log('ret2', ret, ds)
if (ret) {
console.log('done', id)
player._update.push(id)
}
})
},
// 获取冷却时间
cdTime (data) {
return data.ttl || 60
}
}
const LuluData = {
setAvatar (player, data) {
console.log('data', data.AvatarID, data)
console.log('data.ID', data.AvatarID)
let char = Character.get(data.AvatarID)
console.log('char.id', char.id, char.name)
if (!char) {
return false
}
let avatar = player.getAvatar(char.id, true)
console.log('setAvatar', avatar)
let setData = {
level: data.Level,
promote: data.Promotion,
cons: data.Rank || 0,
weapon: Data.getData(data.EquipmentID, 'id:ID,promote:Promotion,level:Level'),
...LuluData.getTalent(data.BehaviorList, char.talentId),
artis: LuluData.getArtis(data.RelicList)
}
console.log('char.setData', setData)
avatar.setAvatar(setData, 'lulu')
return avatar
},
getTalent (ds, talentId = {}) {
let talent = {}
let behaviors = []
lodash.forEach(ds, (d) => {
let key = talentId[d.BehaviorID]
if (key || d.Level > 1) {
talent[key || d.BehaviorID] = d.Level
} else {
behaviors.push(d.BehaviorID)
}
})
return { talent, behaviors }
},
getArtis (artis) {
let ret = {}
lodash.forEach(artis, (ds) => {
let tmp = Data.getData('id:ID,main:MainAffixID,level:Level')
tmp.attrs = []
lodash.forEach(ds.RelicSubAffix, (s) => {
tmp.attrs.push(Data.getData(s, 'id:SubAffixID,count:Cnt,step:Step'))
})
ret[ds.Type] = tmp
})
return ret
}
}

View File

@ -6,6 +6,7 @@ import enkaApi from './EnkaApi.js'
import miaoApi from './MiaoApi.js'
import mggApi from './MggApi.js'
import hutaoApi from './HutaoApi.js'
import luluApi from './LuluApi.js'
let { diyCfg } = await Data.importCfg('profile')
@ -17,7 +18,8 @@ const Profile = {
miao: miaoApi,
mgg: mggApi,
enka: enkaApi,
hutao: hutaoApi
hutao: hutaoApi,
lulu: luluApi
}[key])
}
return Profile.servs[key]
@ -26,9 +28,13 @@ const Profile = {
/**
* 根据UID分配请求服务器
* @param uid
* @param game
* @returns {ProfileServ}
*/
getServ (uid) {
getServ (uid, game = 'gs') {
if (game === 'sr') {
return Profile.serv('lulu')
}
let token = diyCfg?.miaoApi?.token
let qq = diyCfg?.miaoApi?.qq
let hasToken = !!(qq && token && token.length === 32 && !/^test/.test(token))
@ -72,7 +78,8 @@ const Profile = {
if (!req) {
return false
}
let serv = Profile.getServ(uid)
console.log('game', player.game)
let serv = Profile.getServ(uid, player.game)
try {
await req.requestProfile(player, serv)
player._profile = new Date() * 1
@ -82,15 +89,22 @@ const Profile = {
if (!e._isReplyed) {
e.reply(`UID:${uid}更新面板失败,更新服务:${serv.name}`)
}
console.log(err)
return false
}
},
isProfile (avatar) {
console.log('is Sr', avatar.isSr, avatar._source)
if (avatar.isSr) {
return true
}
// 检查数据源
if (!avatar._source || !['enka', 'change', 'miao', 'mgg', 'hutao'].includes(avatar._source)) {
if (!avatar._source || !['enka', 'change', 'miao', 'mgg', 'hutao', 'lulu'].includes(avatar._source)) {
return false
}
// 检查武器及天赋
if (!avatar.weapon || !avatar.talent) {
return false

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

View File

@ -1,430 +0,0 @@
{
"2": {
"main": {
"hp": {
"base": 45.1584,
"add": 15.80544
},
"atk": {
"base": 22.5792,
"add": 7.90272
},
"hpPct": {
"base": 2.7647999999999997,
"add": 0.9677
},
"atkPct": {
"base": 2.7647999999999997,
"add": 0.9677
},
"defPct": {
"base": 3.4560002,
"add": 1.2096001
},
"cpct": {
"base": 2.0736001,
"add": 0.7258
},
"cdmg": {
"base": 4.1472,
"add": 1.4515
},
"heal": {
"base": 2.2117999999999998,
"add": 0.77410005
},
"effPct": {
"base": 2.7647999999999997,
"add": 0.9677
},
"speed": {
"base": 1.6128,
"add": 1
}
},
"sub": {
"hp": {
"base": 13.548016,
"add": 13.548016,
"step": 1.693502
},
"atk": {
"base": 6.774008,
"add": 6.774008,
"step": 0.846751
},
"def": {
"base": 6.774008,
"add": 6.774008,
"step": 0.846751
},
"hpPct": {
"base": 1.3824001000000001,
"add": 1.3824001000000001,
"step": 0.17280006
},
"atkPct": {
"base": 1.3824001000000001,
"add": 1.3824001000000001,
"step": 0.17280006
},
"defPct": {
"base": 1.7280001,
"add": 1.7280001,
"step": 0.21600004
},
"speed": {
"base": 1,
"add": 1,
"step": 0.1
},
"cpct": {
"base": 1.0368001,
"add": 1.0368001,
"step": 0.12960007999999998
},
"cdmg": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
},
"effPct": {
"base": 1.3824001000000001,
"add": 1.3824001000000001,
"step": 0.17280006
},
"effDef": {
"base": 1.3824001000000001,
"add": 1.3824001000000001,
"step": 0.17280006
},
"stance": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
}
},
"maxLv": 6
},
"3": {
"main": {
"hp": {
"base": 67.7376,
"add": 23.70816
},
"atk": {
"base": 33.8688,
"add": 11.85408
},
"hpPct": {
"base": 4.1472,
"add": 1.4515
},
"atkPct": {
"base": 4.1472,
"add": 1.4515
},
"defPct": {
"base": 5.183999999999999,
"add": 1.8144
},
"cpct": {
"base": 3.1104,
"add": 1.0886000999999998
},
"cdmg": {
"base": 6.2208,
"add": 2.1773001
},
"heal": {
"base": 3.3178,
"add": 1.1612
},
"effPct": {
"base": 4.1472,
"add": 1.4515
},
"speed": {
"base": 2.4192,
"add": 1
}
},
"sub": {
"hp": {
"base": 20.322023,
"add": 20.322023,
"step": 2.540253
},
"atk": {
"base": 10.161012,
"add": 10.161012,
"step": 1.270126
},
"def": {
"base": 10.161012,
"add": 10.161012,
"step": 1.270126
},
"hpPct": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
},
"atkPct": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
},
"defPct": {
"base": 2.5919999999999996,
"add": 2.5919999999999996,
"step": 0.32400002
},
"speed": {
"base": 1.2,
"add": 1.2,
"step": 0.1
},
"cpct": {
"base": 1.5552,
"add": 1.5552,
"step": 0.19440008
},
"cdmg": {
"base": 3.1104,
"add": 3.1104,
"step": 0.3888001
},
"effPct": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
},
"effDef": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
},
"stance": {
"base": 3.1104,
"add": 3.1104,
"step": 0.3888001
}
},
"maxLv": 9
},
"4": {
"main": {
"hp": {
"base": 90.3168,
"add": 31.61088
},
"atk": {
"base": 45.1584,
"add": 15.80544
},
"hpPct": {
"base": 5.529599999999999,
"add": 1.9354
},
"atkPct": {
"base": 5.529599999999999,
"add": 1.9354
},
"defPct": {
"base": 6.912,
"add": 2.4192
},
"cpct": {
"base": 4.1472,
"add": 1.4515
},
"cdmg": {
"base": 8.2944,
"add": 2.9029999
},
"heal": {
"base": 4.4237,
"add": 1.5483
},
"effPct": {
"base": 5.529599999999999,
"add": 1.9354
},
"speed": {
"base": 3.2256,
"add": 1.1
}
},
"sub": {
"hp": {
"base": 27.096031,
"add": 27.096031,
"step": 3.387004
},
"atk": {
"base": 13.548016,
"add": 13.548016,
"step": 1.693502
},
"def": {
"base": 13.548016,
"add": 13.548016,
"step": 1.693502
},
"hpPct": {
"base": 2.7647999999999997,
"add": 2.7647999999999997,
"step": 0.34560005
},
"atkPct": {
"base": 2.7647999999999997,
"add": 2.7647999999999997,
"step": 0.34560005
},
"defPct": {
"base": 3.4560002,
"add": 3.4560002,
"step": 0.43200003
},
"speed": {
"base": 1.6,
"add": 1.6,
"step": 0.2
},
"cpct": {
"base": 2.0736001,
"add": 2.0736001,
"step": 0.25920009
},
"cdmg": {
"base": 4.1472,
"add": 4.1472,
"step": 0.5184000400000001
},
"effPct": {
"base": 2.7647999999999997,
"add": 2.7647999999999997,
"step": 0.34560005
},
"effDef": {
"base": 2.7647999999999997,
"add": 2.7647999999999997,
"step": 0.34560005
},
"stance": {
"base": 4.1472,
"add": 4.1472,
"step": 0.5184000400000001
}
},
"maxLv": 12
},
"5": {
"main": {
"hp": {
"base": 112.896,
"add": 39.5136
},
"atk": {
"base": 56.448,
"add": 19.7568
},
"hpPct": {
"base": 6.912,
"add": 2.4192
},
"atkPct": {
"base": 6.912,
"add": 2.4192
},
"defPct": {
"base": 8.64,
"add": 3.0240001
},
"cpct": {
"base": 5.183999999999999,
"add": 1.8144
},
"cdmg": {
"base": 10.367999999999999,
"add": 3.6288
},
"heal": {
"base": 5.529599999999999,
"add": 1.9354
},
"effPct": {
"base": 6.912,
"add": 2.4192
},
"speed": {
"base": 4.032,
"add": 1.4
}
},
"sub": {
"hp": {
"base": 33.87004,
"add": 33.87004,
"step": 4.233755
},
"atk": {
"base": 16.935019,
"add": 16.935019,
"step": 2.116877
},
"def": {
"base": 16.935019,
"add": 16.935019,
"step": 2.116877
},
"hpPct": {
"base": 3.4560002,
"add": 3.4560002,
"step": 0.43200003
},
"atkPct": {
"base": 3.4560002,
"add": 3.4560002,
"step": 0.43200003
},
"defPct": {
"base": 4.32,
"add": 4.32,
"step": 0.54
},
"speed": {
"base": 2,
"add": 2,
"step": 0.3
},
"cpct": {
"base": 2.5919999999999996,
"add": 2.5919999999999996,
"step": 0.32400002
},
"cdmg": {
"base": 5.183999999999999,
"add": 5.183999999999999,
"step": 0.64800004
},
"effPct": {
"base": 3.4560002,
"add": 3.4560002,
"step": 0.43200003
},
"effDef": {
"base": 3.4560002,
"add": 3.4560002,
"step": 0.43200003
},
"stance": {
"base": 5.183999999999999,
"add": 5.183999999999999,
"step": 0.64800004
}
},
"maxLv": 15
}
}