优化角色评分计算;评分权重预定义模板规则,多流派选择 close #144 #145; fix #146

This commit is contained in:
UCPr 2025-09-08 16:06:49 +08:00
parent e9ca415922
commit bc3c641928
10 changed files with 640 additions and 575 deletions

View file

@ -1,11 +1,27 @@
import type { ZZZAvatarInfo } from '../avatar.js'
import type { Equip } from '../equip.js'
import type { scoreWeight } from '../../lib/score.js'
import { baseValueData, formatScoreWeight } from '../../lib/score.js'
import { idToName } from '../../lib/convert/property.js'
import { baseValueData } from '../../lib/score.js'
import { aliasToID } from '../../lib/convert/char.js'
import { getMapData } from '../../utils/file.js'
import { scoreFnc } from './avatar.js'
enum rarity { S, A, B }
type Weight = { [propID: string]: number }
//@ts-expect-error
const equipScore = getMapData('EquipScore') as { [charID: string]: string[] | { rules?: string[], [propID: string]: number } }
for (const charName in equipScore) {
const charID = +charName || aliasToID(charName)
if (!charID) {
logger.warn(`驱动盘评分:未找到角色${charName}的角色ID`)
delete equipScore[charName]
continue
}
equipScore[charID] = equipScore[charName]
delete equipScore[charName]
}
/** 主词条可能属性 */
const mainStats = getMapData('EquipMainStats') as { [partition: string]: number[] }
/** 副词条可能属性 */
@ -14,12 +30,12 @@ const subStats = Object.keys(baseValueData).map(Number)
export default class Score {
protected equip: Equip
/** 词条权重 */
protected weight: typeof scoreWeight[string]
protected weight: Weight
/** 驱动盘n号位 */
protected partition: number
/** 用户主词条 */
protected userMainStat: number
constructor(equip: Equip, weight: Score['weight']) {
constructor(equip: Equip, weight: Weight) {
this.equip = equip
this.weight = weight
this.partition = this.equip.equipment_type
@ -101,7 +117,7 @@ export default class Score {
return score
}
static main(equip: Equip, weight: Score['weight']) {
static main(equip: Equip, weight: Weight) {
try {
return new Score(equip, weight).get_score()
} catch (err) {
@ -110,4 +126,253 @@ export default class Score {
}
}
static getFinalWeight(avatar: ZZZAvatarInfo): [name: string, Weight] {
let def_weight = equipScore[avatar.id]
// 无预设权重且无计算函数(新角色),选择相应基本规则
if (!def_weight && !scoreFnc[avatar.id]) {
switch (avatar.avatar_profession) {
case 1: // 强攻
def_weight = ['主C·双爆']
break
case 2: // 击破
def_weight = ['冲击·双爆', '冲击·攻击']
break
case 3: // 异常
def_weight = ['主C·异常', '辅助·异常']
break
case 4: // 支援
case 5: // 防护
def_weight = ['辅助·双爆', '辅助·异常']
break
case 6: // 命破
def_weight = ['命破·双爆']
break
}
}
/** 选择第一个符合条件的规则,若皆不符合则选择第一个有效规则 */
const delRules = (rules: string[]) => {
if (rules.length === 1) {
rule_name = rules[0]
final_weight = predefinedWeights[rules[0]]?.value
} else {
for (const name of rules) {
if (predefinedWeights[name]?.rule(avatar)) {
rule_name = name
final_weight = predefinedWeights[name].value
break
}
}
if (!final_weight) {
for (const name of rules) {
if (predefinedWeights[name]) {
rule_name = name
final_weight = predefinedWeights[name].value
break
}
}
}
}
final_weight = { ...final_weight }
}
let rule_name = '默认', final_weight: Weight | undefined
if (Array.isArray(def_weight)) {
delRules(def_weight)
} else if (def_weight.rules) {
const { rules, ...rest } = def_weight
delRules(rules)
if (Object.keys(rest).length) {
rule_name += '·改'
Object.assign(final_weight!, rest)
}
} else {
final_weight = def_weight
}
// console.log(avatar.name_mi18n, 'default_final_weight', final_weight)
final_weight = formatScoreWeight(final_weight, avatar.id)
const calc_weight = scoreFnc[avatar.id] && scoreFnc[avatar.id](avatar)
if (calc_weight) {
rule_name = calc_weight[0]
final_weight = { ...final_weight, ...formatScoreWeight(calc_weight[1], avatar.id) }
}
// 小生命、小攻击、小防御动态映射为大生命、大攻击、大防御相对于基础属性的等效权重
for (const [small, big, name] of [[11103, 11102, 'HP'], [12103, 12102, 'ATK'], [13103, 13102, 'DEF']] as const) {
if (final_weight[big]) {
final_weight[small] ??= +(baseValueData[small] * 100 / (baseValueData[big] * avatar.base_properties[name]) * final_weight[big]).toFixed(2)
}
}
// console.log(avatar.name_mi18n, rule_name, final_weight)
return [rule_name, final_weight]
}
}
/** 预定义权重规则 */
const predefinedWeights: Record<string, {
rule: (avatar: ZZZAvatarInfo) => boolean,
value: Record<string, number>
}> = {
C·: {
rule: (avatar) => {
const { ATK, CRITRate, CRITDMG, AnomalyMastery, AnomalyProficiency } = avatar.initial_properties
return ATK > 2400 && CRITRate * 2 + CRITDMG >= 2.2 && AnomalyMastery < 150 && AnomalyProficiency < 200
},
value: {
"生命值百分比": 0,
"攻击力百分比": 0.75,
"防御力百分比": 0,
"冲击力": 0,
"暴击率": 1,
"暴击伤害": 1,
"穿透率": 1,
"穿透值": 0.25,
"能量自动回复": 0,
"异常精通": 0,
"异常掌控": 0,
"属性伤害加成": 1
}
},
C·: {
rule: (avatar) => {
const { ATK, CRITRate, CRITDMG, AnomalyMastery, AnomalyProficiency } = avatar.initial_properties
if (CRITRate * 2 + CRITDMG >= 2) return false
if (ATK < 2400) return false
if (AnomalyMastery >= 180 && AnomalyProficiency >= 200) return true
if (AnomalyMastery >= 120 && AnomalyProficiency >= 300) return true
if (AnomalyMastery >= 150 && AnomalyProficiency >= 250) return true
return false
},
value: {
"生命值百分比": 0,
"攻击力百分比": 0.75,
"防御力百分比": 0,
"冲击力": 0,
"暴击率": 0,
"暴击伤害": 0,
"穿透率": 1,
"穿透值": 0.25,
"能量自动回复": 0,
"异常精通": 1,
"异常掌控": 1,
"属性伤害加成": 1
}
},
·: {
rule: (avatar) => {
return true
},
value: {
"生命值百分比": 0,
"攻击力百分比": 0.25,
"防御力百分比": 0,
"冲击力": 0,
"暴击率": 1,
"暴击伤害": 1,
"穿透率": 0,
"穿透值": 0,
"能量自动回复": 0,
"异常精通": 0,
"异常掌控": 0,
"属性伤害加成": 1
}
},
·: {
rule: (avatar) => {
const { CRITRate, CRITDMG, AnomalyProficiency } = avatar.initial_properties
return CRITRate * 2 + CRITDMG >= 1.5 && AnomalyProficiency < 200
},
value: {
"生命值百分比": 0,
"攻击力百分比": 0.75,
"防御力百分比": 0,
"冲击力": 0,
"暴击率": 1,
"暴击伤害": 1,
"穿透率": 0.75,
"穿透值": 0.25,
"能量自动回复": 1,
"异常精通": 0,
"异常掌控": 0,
"属性伤害加成": 1
}
},
·: {
rule: (avatar) => {
const { CRITRate, CRITDMG } = avatar.initial_properties
return CRITRate * 2 + CRITDMG >= 1.5
},
value: {
"生命值百分比": 0,
"攻击力百分比": 1,
"防御力百分比": 0,
"冲击力": 0,
"暴击率": 1,
"暴击伤害": 0.75,
"穿透率": 0.75,
"穿透值": 0.25,
"能量自动回复": 1,
"异常精通": 0,
"异常掌控": 0,
"属性伤害加成": 1
}
},
·: {
rule: (avatar) => {
const { CRITRate, CRITDMG, AnomalyProficiency } = avatar.initial_properties
return CRITRate * 2 + CRITDMG < 2 && AnomalyProficiency >= 200
},
value: {
"生命值百分比": 0,
"攻击力百分比": 0.75,
"防御力百分比": 0,
"冲击力": 0,
"暴击率": 0,
"暴击伤害": 0,
"穿透率": 0.75,
"穿透值": 0.25,
"能量自动回复": 1,
"异常精通": 1,
"异常掌控": 1,
"属性伤害加成": 1
}
},
·: {
rule: (avatar) => {
const { CRITRate, CRITDMG } = avatar.initial_properties
return CRITRate * 2 + CRITDMG >= 1.5
},
value: {
"生命值百分比": 0,
"攻击力百分比": 0.75,
"防御力百分比": 0,
"冲击力": 1,
"暴击率": 1,
"暴击伤害": 1,
"穿透率": 0.75,
"穿透值": 0.25,
"能量自动回复": 0,
"异常精通": 0,
"异常掌控": 0,
"属性伤害加成": 1
}
},
·: {
rule: (avatar) => {
const { ATK, CRITRate, CRITDMG } = avatar.initial_properties
return ATK > 2000 && CRITRate * 2 + CRITDMG >= 1
},
value: {
"生命值百分比": 0,
"攻击力百分比": 1,
"防御力百分比": 0,
"冲击力": 1,
"暴击率": 1,
"暴击伤害": 0.75,
"穿透率": 0.75,
"穿透值": 0.25,
"能量自动回复": 0,
"异常精通": 0,
"异常掌控": 0,
"属性伤害加成": 1
}
},
}