diff --git a/assets/script/game/common/config/heroSet.ts b/assets/script/game/common/config/heroSet.ts index 704fb8a7..7e64c6b3 100644 --- a/assets/script/game/common/config/heroSet.ts +++ b/assets/script/game/common/config/heroSet.ts @@ -417,9 +417,7 @@ export interface heroInfo { dodge?: number; // 闪避率(百分点,缺省由初始化处兜底为全局默认值) accuracy?: number; // 命中率(百分点,缺省由初始化处兜底为全局默认值) skills: Record; // 携带技能ID列表 - ap_bonus?: LvBonusEntry[]; // 额外攻击加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档) - hp_bonus?: LvBonusEntry[]; // 额外生命加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档) - def_bonus?: LvBonusEntry[]; // 额外防御加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档) + bonus?: LvBonusAttrsEntry[]; // 额外属性加成(按 lv 升序,运行时累加所有 lv ≤ 英雄等级 的档位 attrs;hp/ap/def 按"基础×品质+成长"快照百分比加成,crt/frz/stun/para 为固定百分点累加) info: string; // 描述文案 /** * @deprecated 已废弃:无任何 UI/运行时读取。技能提升说明由 HeroSkillText 依据 @@ -526,11 +524,15 @@ export interface LvFieldEntry { } /** - * 额外属性加成按等级叠加:运行时累加所有 ≤英雄等级 的档位。 + * 额外属性加成按等级叠加:运行时累加所有 ≤英雄等级 的档位 attrs。 + * 口径约定:hp/ap/def 为百分比(基数取"基础×品质+成长"快照,多档同键加法叠加不滚雪球); + * crt/frz/stun/para 为固定百分点(直接累加到对应概率属性)。 */ -export interface LvBonusEntry { +export type BonusAttrKey = 'hp' | 'ap' | 'def' | 'crt' | 'frz' | 'stun' | 'para'; + +export interface LvBonusAttrsEntry { lv: number; - value: number; + attrs: Partial>; } /* *=============英雄配置列表================ @@ -1089,7 +1091,7 @@ export function resolveFieldByLv(field: LvFieldEntry[] | undefined, lv: number): return pick ? pick.uuids : []; } -/** 计算目标等级下额外属性加成(hp/ap/def_bonus 一次性奖励 + 成长类型每级递增) */ +/** 计算目标等级下额外属性加成(成长类型每级递增 + bonus 档位累加;bonus 中 hp/ap/def 为百分比,基数取"基础+成长"快照,多档加法叠加) */ function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: number, def: number } { const lv = Math.max(1, targetLv); // 成长类型线性增量(仅英雄生效,怪物无 grow_type 时走 Warrior 基准但不影响平衡,因怪物通常不填) @@ -1097,20 +1099,22 @@ function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: n let hp = grow.hp; let ap = grow.ap; let def = grow.def; - if (hero.hp_bonus) { - for (const e of hero.hp_bonus) { - if (e.lv <= lv) hp += e.value; - } - } - if (hero.ap_bonus) { - for (const e of hero.ap_bonus) { - if (e.lv <= lv) ap += e.value; - } - } - if (hero.def_bonus) { - for (const e of hero.def_bonus) { - if (e.lv <= lv) def += e.value; + if (hero.bonus) { + // 百分比基数:基础值 + 成长快照(不含 bonus 自身,避免滚雪球) + const snapHp = hero.hp + grow.hp; + const snapAp = hero.ap + grow.ap; + const snapDef = hero.def + grow.def; + let hpP = 0, apP = 0, defP = 0; + for (const e of hero.bonus) { + if (e.lv > lv) continue; + hpP += e.attrs.hp ?? 0; + apP += e.attrs.ap ?? 0; + defP += e.attrs.def ?? 0; + // crt/frz/stun/para 为固定百分点,由运行时直接累加到概率属性,此处不结算 } + hp += Math.round(snapHp * hpP / 100); + ap += Math.round(snapAp * apP / 100); + def += Math.round(snapDef * defP / 100); } return { hp, ap, def }; } diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index f87cce18..a59b7b43 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -137,9 +137,7 @@ export class Hero extends ecs.Entity { model.atking = hero.atking; model.atked = hero.atked; model.field = hero.field; - model.ap_bonus = hero.ap_bonus; - model.hp_bonus = hero.hp_bonus; - model.def_bonus = hero.def_bonus; + model.bonus = hero.bonus; // 基础属性:固定基础值 × 品质系数(相邻品质差 5%),成长由 grow_type 线性递增提供 // TODO: 局外升级机制落地后,此处基础值将与存档强化累加重算 @@ -184,26 +182,30 @@ export class Hero extends ecs.Entity { model.runtime_field = resolveFieldByLv(model.field, hero_lv); // 复活已纳入 dead 触发组,无需单独解析 revive - // 应用额外属性加成(成长类型每级递增 + hp/ap/def_bonus 一次性奖励累加) + // 应用额外属性加成(成长类型每级递增 + bonus 档位累加) // 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计 const grow = calcGrowBonus(hero, hero_lv); model.hp += grow.hp; model.hp_max += grow.hp; model.ap += grow.ap; model.defense += grow.def; - if (model.hp_bonus) { - let hpExtra = 0; - for (const e of model.hp_bonus) { if (e.lv <= hero_lv) hpExtra += e.value; } - model.hp += hpExtra; model.hp_max += hpExtra; - } - if (model.ap_bonus) { - let apExtra = 0; - for (const e of model.ap_bonus) { if (e.lv <= hero_lv) apExtra += e.value; } - model.ap += apExtra; - } - if (model.def_bonus) { - let defExtra = 0; - for (const e of model.def_bonus) { if (e.lv <= hero_lv) defExtra += e.value; } - model.defense += defExtra; + if (model.bonus) { + // 百分比基数:基础×品质 + 成长快照(不含 bonus 自身,多档加法叠加不滚雪球) + const snapHp = model.hp_max, snapAp = model.ap, snapDef = model.defense; + let hpP = 0, apP = 0, defP = 0; + for (const e of model.bonus) { + if (e.lv > hero_lv) continue; + const a = e.attrs; + hpP += a.hp ?? 0; apP += a.ap ?? 0; defP += a.def ?? 0; + // 概率类:固定百分点直接累加 + model.critical += a.crt ?? 0; + model.freeze_chance += a.frz ?? 0; + model.stun_chance += a.stun ?? 0; + model.paralyze_chance += a.para ?? 0; + } + const hpAdd = Math.round(snapHp * hpP / 100); + model.hp += hpAdd; model.hp_max += hpAdd; + model.ap += Math.round(snapAp * apP / 100); + model.defense += Math.round(snapDef * defP / 100); } // lv12 职业属性专项:达到 12 级时按职业对单项基础属性 +50% diff --git a/assets/script/game/hero/HeroAttrsComp.ts b/assets/script/game/hero/HeroAttrsComp.ts index 2637ef2c..0304ff84 100644 --- a/assets/script/game/hero/HeroAttrsComp.ts +++ b/assets/script/game/hero/HeroAttrsComp.ts @@ -1,5 +1,5 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; -import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvBonusEntry, FieldEntry, HRole } from "../common/config/heroSet"; +import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvBonusAttrsEntry, FieldEntry, HRole } from "../common/config/heroSet"; import { mLogger } from "../common/Logger"; import { FacSet, FightSet } from "../common/config/GameSet"; import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet"; @@ -53,10 +53,8 @@ export class HeroAttrsComp extends ecs.Comp { [SkillTriggerType.Atked]?: TriggerGrouped; /** 驻场光环(按 lv 解锁,存活期间全局生效) */ [SkillTriggerType.Field]?: LvFieldEntry[]; - /** 额外攻击/生命/防御加成(按 lv 累加) */ - ap_bonus?: LvBonusEntry[]; - hp_bonus?: LvBonusEntry[]; - def_bonus?: LvBonusEntry[]; + /** 额外属性加成(按 lv 累加:hp/ap/def 百分比、 crt/frz/stun/para 固定百分点) */ + bonus?: LvBonusAttrsEntry[]; // ==================== 运行时解析缓存(按当前等级 resolve 后的有效列表) ==================== /** 当前等级下生效的攻击触发条目(供 SkillTriggerHelper 消费) */ @@ -704,9 +702,7 @@ export class HeroAttrsComp extends ecs.Comp { this.atking = undefined; this.atked = undefined; this.field = undefined; - this.ap_bonus = undefined; - this.hp_bonus = undefined; - this.def_bonus = undefined; + this.bonus = undefined; this.runtime_atking = undefined; this.runtime_atked = undefined; this.runtime_call = undefined; diff --git a/assets/script/game/map/HeroBoxComp.ts b/assets/script/game/map/HeroBoxComp.ts index 84461143..5d581382 100644 --- a/assets/script/game/map/HeroBoxComp.ts +++ b/assets/script/game/map/HeroBoxComp.ts @@ -215,8 +215,8 @@ export class HeroBoxComp extends CCComp { // ---- 属性成长(固定基础值 + 成长类型指数递增 + 一次性加成) ---- const attrLine = (lv: number): string => { const grow = calcGrowBonus(hero, lv); - const ap = Math.floor(hero.ap + grow.ap + HeroBoxComp.sumBonus(hero.ap_bonus, lv)); - const hp = Math.floor(hero.hp + grow.hp + HeroBoxComp.sumBonus(hero.hp_bonus, lv)); + const ap = Math.floor(hero.ap + grow.ap + HeroBoxComp.sumBonusPct(hero.ap + grow.ap, hero.bonus, 'ap', lv)); + const hp = Math.floor(hero.hp + grow.hp + HeroBoxComp.sumBonusPct(hero.hp + grow.hp, hero.bonus, 'hp', lv)); return `攻击 ${ap} 生命 ${hp}`; }; @@ -237,11 +237,12 @@ export class HeroBoxComp extends CCComp { } /** 累加等级额外加成(≤目标等级的所有档位) */ - private static sumBonus(entries: { lv: number; value: number }[] | undefined, lv: number): number { + /** 累加 bonus 中某百分比键的档位总量,并按"基础+成长"快照换算为固定增量(≤目标等级的所有档位) */ + private static sumBonusPct(snap: number, entries: HeroInfo[number]['bonus'], key: 'hp' | 'ap' | 'def', lv: number): number { if (!entries) return 0; - let total = 0; - for (const e of entries) { if (e.lv <= lv) total += e.value; } - return total; + let pct = 0; + for (const e of entries) { if (e.lv <= lv) pct += e.attrs[key] ?? 0; } + return Math.round(snap * pct / 100); } /** @@ -543,18 +544,18 @@ export class HeroBoxComp extends CCComp { } // ---- AP / HP ---- - // plus(附加值)= 最终值 - 当前等级基础值;基础值 = 1 级原始 base + 等级成长 + ap/hp_bonus 一次性奖励。 + // plus(附加值)= 最终值 - 当前等级基础值;基础值 = 1 级原始 base + 等级成长 + bonus 百分比加成。 // Why: base_ap/base_hp 是不含任何等级增长的原始值,直接用 final-base 会把升级带来的 // 属性增长也计入增量。这里补齐等级增长作为基准,使 plus 仅反映 buff/光环等临时增益。 const lv = Math.max(1, Math.floor(this.model.lv ?? 1)); const grow = hero ? calcGrowBonus(hero, lv) : { ap: 0, hp: 0 }; const finalAp = Math.max(0, Math.floor(this.model.getFinalAp())); - const lvBaseAp = Math.max(0, Math.floor((this.model.base_ap ?? 0) + grow.ap + HeroBoxComp.sumBonus(hero?.ap_bonus, lv))); + const lvBaseAp = Math.max(0, Math.floor((this.model.base_ap ?? 0) + grow.ap + HeroBoxComp.sumBonusPct((this.model.base_ap ?? 0) + grow.ap, hero?.bonus, 'ap', lv))); this.setStatRow(this.ap_all_label, this.ap_plus_label, finalAp, finalAp - lvBaseAp); const finalHp = Math.max(0, Math.floor(this.model.getFinalHpMax())); - const lvBaseHp = Math.max(0, Math.floor((this.model.base_hp ?? 0) + grow.hp + HeroBoxComp.sumBonus(hero?.hp_bonus, lv))); + const lvBaseHp = Math.max(0, Math.floor((this.model.base_hp ?? 0) + grow.hp + HeroBoxComp.sumBonusPct((this.model.base_hp ?? 0) + grow.hp, hero?.bonus, 'hp', lv))); this.setStatRow(this.hp_all_label, this.hp_plus_label, finalHp, finalHp - lvBaseHp); // ---- 图标(UUID 变化时重新加载首帧动画) ---- diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index a2be2634..0911206c 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -2142,7 +2142,7 @@ export class MissionCardComp extends CCComp { * 1. 同步刷新 base_ap/base_hp 快照,下游 UI/复活读到当前 lv 的基础值 * 2. 走 getRuntimeAp/getRuntimeHp 应用驻场光环乘区 * 3. 重新解析 runtime_* 触发缓存与光环/复活,避免升级后停在旧等级快照 - * 4. 按 ≤nextLv 累加 hp_bonus/ap_bonus + * 4. 按 ≤nextLv 累加 bonus 档位(hp/ap/def 百分比 + crt/frz/stun/para 百分点) */ public applyHeroLevel(model: HeroAttrsComp, targetLv: number) { const hero = HeroInfo[model.hero_uuid]; @@ -2167,28 +2167,38 @@ export class MissionCardComp extends CCComp { model.hp_max = base_hp; } - // 按 ≤nextLv 累加:成长类型每级递增 + hp/ap/def_bonus 一次性奖励(沿用 Hero.load 的加法区规则) + // 按 ≤nextLv 累加:成长类型每级递增 + bonus 档位(与 Hero.load 同口径:hp/ap 百分比、基数取"基础+成长"快照) const grow = calcGrowBonus(hero, nextLv); model.hp_max += grow.hp; model.ap += grow.ap; - if (model.hp_bonus) { - let hpExtra = 0; - for (const e of model.hp_bonus) { if (e.lv <= nextLv) hpExtra += e.value; } - model.hp_max += hpExtra; - } - if (model.ap_bonus) { - let apExtra = 0; - for (const e of model.ap_bonus) { if (e.lv <= nextLv) apExtra += e.value; } - model.ap += apExtra; + let bonusHpP = 0, bonusApP = 0, bonusDefP = 0; + if (model.bonus) { + for (const e of model.bonus) { + if (e.lv > nextLv) continue; + bonusHpP += e.attrs.hp ?? 0; + bonusApP += e.attrs.ap ?? 0; + bonusDefP += e.attrs.def ?? 0; + } + model.hp_max += Math.round((base_hp + grow.hp) * bonusHpP / 100); + model.ap += Math.round((base_ap + grow.ap) * bonusApP / 100); } // 天赋加成层(与 Hero.load 对齐):先重置天赋可能修改的字段,避免升级重算时重复叠加 // Why: ap/hp_max 已由上方公式从 base 重算(天然幂等),但 defense 与概率类字段是累加语义,必须先归零再注入 if (model.fac === FacSet.HERO) { - // defense 从品质系数后的基础值重算,叠加 def 成长 + def_bonus + lv12 专项,幂等 + // defense 从品质系数后的基础值重算,叠加 def 成长 + def 百分比 bonus + lv12 专项,幂等 let base_def = Math.round(hero.def * qualityCoef) + grow.def; - if (model.def_bonus) { - for (const e of model.def_bonus) { if (e.lv <= nextLv) base_def += e.value; } + base_def += Math.round(base_def * bonusDefP / 100); + // bonus 概率类:固定百分点累加(在下方归零后注入,避免重复叠加) + let bonusCrt = 0, bonusFrz = 0, bonusStun = 0, bonusPara = 0; + if (model.bonus) { + for (const e of model.bonus) { + if (e.lv > nextLv) continue; + bonusCrt += e.attrs.crt ?? 0; + bonusFrz += e.attrs.frz ?? 0; + bonusStun += e.attrs.stun ?? 0; + bonusPara += e.attrs.para ?? 0; + } } // lv12 职业属性专项:达到 12 级时按职业对单项基础属性 +50%(基数为 lv11 有效值) if (nextLv >= HERO_LV12_SPECIAL_LV && model.role !== undefined) { @@ -2199,10 +2209,11 @@ export class MissionCardComp extends CCComp { else if (attr === "ap") model.ap += Math.round(lv11.ap * 0.5); } model.defense = base_def; - model.critical = 0; + model.critical = bonusCrt; model.critical_damage = 0; - model.stun_chance = 0; - model.freeze_chance = 0; + model.stun_chance = bonusStun; + model.freeze_chance = bonusFrz; + model.paralyze_chance = bonusPara; // 风怒机制已停用: 升级重算不再重置风怒 // model.wfuny = 0; model.puncture_chance = 0; diff --git a/put/server/data/heroes.json编辑指引.md b/put/server/data/heroes.json编辑指引.md index a49b4813..12395f63 100644 --- a/put/server/data/heroes.json编辑指引.md +++ b/put/server/data/heroes.json编辑指引.md @@ -44,7 +44,7 @@ | `skills` | array | **恰好 2 个**:`[普攻, 大招]`,结构见 §3 | | `atking`/`atked`/`fstart`/`dead` | object | 触发组,结构见 §4 | | `field` | array | 驻场光环档位,结构见 §5 | -| `ap_bonus`/`hp_bonus`/`def_bonus` | array | 一次性属性奖励 `[{lv,value}]`,运行时累加 ≤英雄等级的档;常规英雄留空 | +| `bonus` | array | 额外属性加成 `[{lv,attrs:{...}}]`,运行时累加所有 lv ≤ 英雄等级 的档位;`hp`/`ap`/`def` 为百分比(基数取"基础×品质+成长"快照,多档加法叠加),`crt`/`frz`/`stun`/`para` 为固定百分点;常规英雄留空 | | `info` | string | 描述文案,可空 | | `revive` | — | **已废弃**:复活改在 `dead` 组 overrides 里配 `revive_hp`/`revive_count`,不要写独立字段 | diff --git a/put/server/index.js b/put/server/index.js index 09b418e8..9824fc36 100644 --- a/put/server/index.js +++ b/put/server/index.js @@ -202,11 +202,13 @@ function renderSingleHero(h) { // 复活已纳入 dead 触发组,通过 dead 编辑器的 overrides 配置 revive_hp / revive_count // 不再导出独立的 revive 字段 - // bonuses - for (const bk of ['ap_bonus', 'hp_bonus', 'def_bonus']) { - if (!h[bk]?.length) continue - push(` ${bk}: [`) - for (const b of h[bk]) push(` { lv: ${b.lv}, value: ${b.value} },`) + // bonus(统一额外属性加成:hp/ap/def 百分比 + crt/frz/stun/para 百分点;跳过空 attrs 档位) + const bonusList = (h.bonus || []).filter((b) => b.attrs && Object.keys(b.attrs).length) + if (bonusList.length) { + push(` bonus: [`) + for (const b of bonusList) { + push(` { lv: ${b.lv}, attrs: ${JSON.stringify(b.attrs)} },`) + } push(` ],`) } @@ -325,6 +327,18 @@ function csvStatLine(h, key) { return `${role}${base > 0 ? '+' : ''}${base}` } +/** bonus 描述行:lv9 ap+20% hp+15%;lv12 crt+10 ap+30% */ +function csvBonus(bonus) { + if (!bonus?.length) return '无' + return bonus + .filter((b) => b.attrs && Object.keys(b.attrs).length) + .map((b) => { + const parts = Object.entries(b.attrs).map(([k, v]) => (k === 'hp' || k === 'ap' || k === 'def') ? `${k}+${v}%` : `${k}+${v}`) + return `lv${b.lv} ${parts.join(' ')}` + }) + .join(';') || '无' +} + /** 生成英雄设计表 CSV 内容 */ function renderHeroCsv(heroes) { const sorted = [...heroes].sort((a, b) => a.uuid - b.uuid) @@ -350,6 +364,7 @@ function renderHeroCsv(heroes) { rows.push(['fstart[0]_lv4', ...sorted.map((h) => csvTrigger(h.fstart))]) rows.push(['dead[0]_lv4', ...sorted.map((h) => csvTrigger(h.dead))]) rows.push(['field[0]_lv4', ...sorted.map((h) => csvField(h.field))]) + rows.push(['bonus', ...sorted.map((h) => csvBonus(h.bonus))]) return rows.map((r) => r.map(csvCell).join(',')).join('\n') + '\n' } diff --git a/put/web/src/App.vue b/put/web/src/App.vue index 54e766b9..6cccb03a 100644 --- a/put/web/src/App.vue +++ b/put/web/src/App.vue @@ -173,12 +173,8 @@
-

额外属性加成

- - - - - +

额外属性加成(hp/ap/def 百分比,crt/frz/stun/para 百分点)

+
@@ -363,9 +359,9 @@ function fieldSummary(h) { return `lv${f.lv} · ${auras}` }) } -// 额外加成条目总数(ap/hp/def_bonus 之和) +// 额外加成档位总数(bonus 数组长度) function bonusCount(h) { - return (h.ap_bonus?.length || 0) + (h.hp_bonus?.length || 0) + (h.def_bonus?.length || 0) + return h.bonus?.length || 0 } async function loadMeta() { @@ -439,6 +435,11 @@ async function loadHeroes() { try { const r = await api.fetchHeroes() if (Array.isArray(r.heroes) && r.heroes.length) { + // 兼容旧数据:ap_bonus/hp_bonus/def_bonus 已废弃,统一迁移为空 bonus + for (const h of r.heroes) { + if (!Array.isArray(h.bonus)) h.bonus = [] + delete h.ap_bonus; delete h.hp_bonus; delete h.def_bonus + } heroes.value = r.heroes } } catch (e) { diff --git a/put/web/src/components/BonusEditor.vue b/put/web/src/components/BonusEditor.vue index f25c48a4..253bf12c 100644 --- a/put/web/src/components/BonusEditor.vue +++ b/put/web/src/components/BonusEditor.vue @@ -1,14 +1,61 @@ + + diff --git a/put/web/src/constants.js b/put/web/src/constants.js index db5f0be0..9aaaac6f 100644 --- a/put/web/src/constants.js +++ b/put/web/src/constants.js @@ -76,9 +76,7 @@ export function defaultHero() { dead: {}, field: [], // [{lv,uuids:[{uuid,value,name}],desc}] // revive 已纳入 dead 触发组,通过 overrides.revive_hp / revive_count 配置 - ap_bonus: [], - hp_bonus: [], - def_bonus: [], + bonus: [], // [{lv, attrs:{hp?,ap?,def?,crt?,frz?,stun?,para?}}] hp/ap/def 为百分比,概率类为固定百分点 info: '', } } @@ -143,6 +141,17 @@ export const OVERRIDE_KEYS = [ { key: 'icon', label: 'icon 英雄语境图标(覆写 SkillSet.icon)', type: 'string' }, ] +// bonus attrs 可用键:hp/ap/def 按"基础+成长"快照百分比加成;crt/frz/stun/para 为固定百分点累加 +export const BONUS_ATTR_KEYS = [ + { key: 'hp', label: 'hp 生命%' }, + { key: 'ap', label: 'ap 攻击%' }, + { key: 'def', label: 'def 防御%' }, + { key: 'crt', label: 'crt 暴击率(百分点)' }, + { key: 'frz', label: 'frz 冰冻(百分点)' }, + { key: 'stun', label: 'stun 击晕(百分点)' }, + { key: 'para', label: 'para 麻痹(百分点)' }, +] + // icon 覆写常用候选(SkillSet 各底座图标 + 通用图标),可在输入框直接键入其他帧名 export const SKILL_ICON_OPTIONS = [ 'Stat_Attack_01', 'Stat_RangedAttack', 'Stat_Tripleshot',