feedback1e WR: 卡涅利安 charged S1 never attacks, S3 mark before the damage; review fixes
Review of the E3 fix (阵法术师 / 轰击术师 strike every enemy in range): - 卡涅利安 S1 沙暴守卫 charged: PRTS 备注 "应用蓄力时:应用技能未开启时的特性" keeps the whole skill-off trait in force, 不攻击 included. A kit trait canAttack now stops her attacks while a charged S1 runs; the guard stays. The E3 test casts S1 uncharged and a new test pins both casts. - 卡涅利安 S3 食噬之印 charged: the mark stacks before the attack's damage (PRTS 备注 "于攻击造成伤害前生效"): x1.2 on the first hit, x2.0 from the 5th (was one attack late). One mark per enemy: another 卡涅利安 only adds stacks, the bonus stays the setter's (PRTS 备注 popup). - rangeAoe is applied after every override (kit trait included) and sets allInRange itself; SUB.phalanx / blastcaster carry rangeAoe only. - 流形 copies (tokens.js and the 缪尔赛思 kit) turn a copied 'beam' into a plain bolt: the copy takes no attack shape. - fx.js: the dead blastcaster shell colour is gone (and its test). - SIM.md / comments cite PRTS 溅射半径一览 (no splash row for these branches), note that stealthed enemies are not struck unless revealed or blocked, and list the table's other radii as open deviations.
This commit is contained in:
1 parent
27181d88bd
commit
f3a73fec6b
9 files changed
+207
-57
No files matched your search
@@ -16,8 +16,9 @@
|
||||
// groupId `abyssal` (data/chess.json has no groupId: charIds from docs/research/03-operators.json).
|
||||
// - "友方干员" effects touch operators only (summons/devices excluded); SP gifts skip units whose timed skill runs
|
||||
// (AK: no SP gain during a skill — the engine's gainSp enforces it too; the kits skip such units when picking).
|
||||
// - On-hit statuses of 卡涅利安's attacks apply to every enemy the attack damages — the 阵法术师 trait strikes every
|
||||
// enemy on her range at once (professions.js `allInRange`, community report E3) — via a `damaged` hook.
|
||||
// - On-hit effects of 卡涅利安's attacks land on every enemy the attack strikes — the 阵法术师 trait strikes every enemy
|
||||
// on her range at once (professions.js `allInRange` / `rangeAoe`, community report E3): S2's 停顿 / 束缚 via a
|
||||
// `damaged` hook, the charged S3 mark via a `hit` hook (it applies before the damage, PRTS 备注).
|
||||
// - Dodge from a skill that adds to other dodge sources (焰尾 S3) is rolled independently in a `hit` hook, so the
|
||||
// sources combine as 1 − Π(1 − p) (the same rule the engine now applies to stacked dodge mods).
|
||||
// - Operator loadouts (DESIGN §16): every selectable non-default skill of the 22 visible chess is authored in the kit's
|
||||
@@ -39,6 +40,9 @@ const ABYSSAL = new Set(['char_143_ghost', 'char_263_skadi', 'char_474_glady', '
|
||||
const LINE = Object.freeze(Array.from({ length: COLS }, (_, i) => Object.freeze([0, i])));
|
||||
/** 卡涅利安 S3 食噬之印: the ATK bonus climbs in 1 s steps over 20 s (PRTS 备注; the blackboard has no ramp time). */
|
||||
const BILLRO_S3_RAMP = 20;
|
||||
/** 卡涅利安 S3 charged mark: one buff per enemy, shared by every 卡涅利安 (PRTS 备注), ≤ 5 stacks (skill text). */
|
||||
const BILLRO_MARK = 'billro:mark';
|
||||
const BILLRO_MARK_MAX = 5;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// helpers
|
||||
@@ -1716,10 +1720,15 @@ const kits = {
|
||||
|
||||
// ===== 卡涅利安 (phalanx) S2 沙缚镣锁 — faster attacks on every enemy in range (the trait's 群体法术伤害), 0.3 s
|
||||
// sluggish on each; charged: ATK +10 % and bind; talents
|
||||
// S1 沙暴守卫 (SEARCH: ATK/DEF up; charged — cast with every charge stored — the trait's DEF/RES guard stays on);
|
||||
// S3 食噬之印 (wider range, ATK +0 % → +140 %/+200 % in 1 s steps over 20 s — PRTS 备注; charged: every hit marks
|
||||
// its target, +20 % damage from her per mark, ≤ 5, until the skill ends). Talent 生命之餐 heals on every skill;
|
||||
// module PLX-X keeps part of the guard during any skill, PLX-Y (乡音): +3 % damage per enemy in range (≤ 5)
|
||||
// S1 沙暴守卫 (SEARCH: ATK/DEF up; charged — cast with every charge stored — "特性效果在技能期间继续生效", PRTS 备注
|
||||
// "应用蓄力时:应用技能未开启时的特性": the skill-off trait stays in force, the DEF/RES guard AND 不攻击 — she makes
|
||||
// no attack until the skill ends, a pure guard);
|
||||
// S3 食噬之印 (wider range, ATK +0 % → +140 %/+200 % in 1 s steps over 20 s — PRTS 备注; charged: each attack adds a
|
||||
// stack of the mark BEFORE its damage — PRTS 备注 "于攻击造成伤害前生效,多层效果之间加算叠加" — +20 % damage from
|
||||
// her per stack, ≤ 5, so ×1.2 on the first hit and ×2.0 from the 5th, until the skill ends; one mark per enemy:
|
||||
// another 卡涅利安 only adds stacks to it, the bonus is the setter's — PRTS 备注 popup). Talent 生命之餐 heals on
|
||||
// every skill; module PLX-X keeps part of the guard during any skill, PLX-Y (乡音): +3 % damage per enemy in range
|
||||
// (≤ 5)
|
||||
chess_char_4_24_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0), t1 = tbb(def, 1), mb = moduleBb(def);
|
||||
const tb = def.traitBb || {};
|
||||
@@ -1727,15 +1736,17 @@ const kits = {
|
||||
const S1 = isSel(def, 'skchr_billro_1'), S2 = isSel(def, 'skchr_billro_2'), S3 = isSel(def, 'skchr_billro_3');
|
||||
const g = grid(def.skill?.rangeGrid);
|
||||
const isCharged = (skill) => skill.maxCharges > 1 && skill.charges + 1 >= skill.maxCharges; // cast with every charge stored
|
||||
const markKey = (unit) => `billro:mark:${unit.id}`;
|
||||
return {
|
||||
// charged S1: the skill-off trait's 不攻击 stays in force while the skill lasts (PRTS S1 备注) — no attack at all
|
||||
trait: S1 ? { canAttack: (battle, u) => !(skillActive(u) && u.mem.billroCharged) } : null,
|
||||
skills: alt(def, {
|
||||
skchr_billro_1: () => ({
|
||||
kind: 'duration',
|
||||
mods: { atkPct: num(bb.atk), defPct: num(bb.def) },
|
||||
onStart({ battle, unit, skill }) {
|
||||
unit.mem.billroCharged = isCharged(skill);
|
||||
// 蓄力额外效果:特性效果在技能期间继续生效 (the profession removes its own guard buff when a skill starts)
|
||||
// 蓄力额外效果:特性效果在技能期间继续生效 (the profession removes its own guard buff when a skill starts; the
|
||||
// kit trait's canAttack keeps the trait's 不攻击)
|
||||
if (unit.mem.billroCharged) battle.addBuff(unit, { key: 'billro:s1guard', mods: { defPct: num(unit.profile?.guardDef, 2), resFlat: num(unit.profile?.guardRes, 20) } });
|
||||
battle.fx('shell', { x: unit.x, y: unit.y, id: unit.id });
|
||||
},
|
||||
@@ -1759,7 +1770,11 @@ const kits = {
|
||||
},
|
||||
onEnd({ battle, unit }) {
|
||||
battle.removeBuff(unit, 'billro:s3atk');
|
||||
for (const id of unit.mem.billroMarked || []) { const e = battle.unitById(id); if (e) battle.removeBuff(e, markKey(unit)); }
|
||||
// "持续至技能结束": the marks she set end with her skill (stacks another 卡涅利安 added to them included)
|
||||
for (const id of unit.mem.billroMarked || []) {
|
||||
const e = battle.unitById(id);
|
||||
if (e && e.findBuff(BILLRO_MARK)?.source === unit) battle.removeBuff(e, BILLRO_MARK);
|
||||
}
|
||||
unit.mem.billroMarked = null;
|
||||
unit.mem.billroCharged = false;
|
||||
},
|
||||
@@ -1807,17 +1822,22 @@ const kits = {
|
||||
}, { owner: unit });
|
||||
}
|
||||
if (S3) {
|
||||
// charged S3: each hit adds a mark (≤ 5) — +20 % damage taken from her per mark until the skill ends
|
||||
// charged S3 食噬之印: each attack adds a stack BEFORE its damage (PRTS 备注 "于攻击造成伤害前生效"); stacks add up,
|
||||
// +20 % damage taken from the mark's setter per stack. One mark per enemy (PRTS 备注 popup: "存在则为已有的BUFF叠加
|
||||
// 一次加成,不存在则给与敌人仅对此卡涅利安的伤害生效的一个食噬之印BUFF"): another 卡涅利安 stacks it without the bonus
|
||||
const per = num(bb['attack@damage_scale'], 0.2);
|
||||
battle.on('hit', (c) => {
|
||||
if (c.source !== unit) return;
|
||||
const m = c.target.findBuff?.(markKey(unit));
|
||||
if (m) c.dmg.mul *= 1 + num(bb['attack@damage_scale'], 0.2) * (m.stacks || 1);
|
||||
}, { owner: unit });
|
||||
battle.on('damaged', (c) => {
|
||||
const e = c.target;
|
||||
if (c.source !== unit || !c.dmg?.isAttack || e.side !== 'enemy' || !e.alive || !skillActive(unit) || !unit.mem.billroCharged) return;
|
||||
battle.addBuff(e, { key: markKey(unit), refresh: 'stack', maxStacks: 5, duration: Math.max(0.1, (unit.skill?.timeLeft ?? 0) + 0.1), source: unit, visible: true });
|
||||
unit.mem.billroMarked?.add(e.id);
|
||||
if (c.source !== unit || e.side !== 'enemy') return;
|
||||
let m = e.findBuff?.(BILLRO_MARK) || null;
|
||||
if (c.dmg.isAttack && skillActive(unit) && unit.mem.billroCharged) {
|
||||
if (m) battle.addBuff(e, { key: BILLRO_MARK, refresh: 'stack', maxStacks: BILLRO_MARK_MAX, duration: m.timeLeft });
|
||||
else {
|
||||
m = battle.addBuff(e, { key: BILLRO_MARK, refresh: 'stack', maxStacks: BILLRO_MARK_MAX, duration: Math.max(0.1, (unit.skill?.timeLeft ?? 0) + 0.1), source: unit, visible: true });
|
||||
if (m) unit.mem.billroMarked?.add(e.id);
|
||||
}
|
||||
}
|
||||
if (m && m.source === unit) c.dmg.mul *= 1 + per * (m.stacks || 1);
|
||||
}, { owner: unit });
|
||||
}
|
||||
if (!S2) return;
|
||||
|
||||
@@ -1749,7 +1749,8 @@ function copyInto(battle, t, src, scale, ranged) {
|
||||
const p = t.profile;
|
||||
if (p) {
|
||||
p.attack = ranged ? 'ranged' : 'melee';
|
||||
p.projectile = ranged ? (sp.projectile && sp.projectile !== 'none' && sp.projectile !== 'orb' ? sp.projectile : 'bolt') : 'none';
|
||||
// (a 阵法术师 / 轰击术师's instant 'beam' is their every-enemy-in-range shape: the copy fires a plain bolt)
|
||||
p.projectile = ranged ? (sp.projectile && sp.projectile !== 'none' && sp.projectile !== 'orb' && sp.projectile !== 'beam' ? sp.projectile : 'bolt') : 'none';
|
||||
p.canHitFly = ranged ? true : !!sp.canHitFly;
|
||||
if (!(sp.dmgType === 'heal' || sp.dmgType === 'none' || sp.noAttack || sp.noAttackUnlessSkill)) p.dmgType = sp.dmgType;
|
||||
p.heal = null;
|
||||
|
||||
@@ -728,7 +728,9 @@ function applyManifoldCopy(battle, unit, target, scale) {
|
||||
const p = unit.profile;
|
||||
if (p) {
|
||||
p.attack = ranged ? 'ranged' : 'melee';
|
||||
p.projectile = ranged ? (tp.projectile && tp.projectile !== 'none' && tp.projectile !== 'orb' ? tp.projectile : 'bolt') : 'none';
|
||||
// the talent's list of copied attributes (生命上限 … 伤害类型) names no attack shape: a 阵法术师 / 轰击术师's instant
|
||||
// 'beam' (rangeAoe: every enemy in range) becomes a plain single-target bolt, like a healer's orb
|
||||
p.projectile = ranged ? (tp.projectile && tp.projectile !== 'none' && tp.projectile !== 'orb' && tp.projectile !== 'beam' ? tp.projectile : 'bolt') : 'none';
|
||||
p.canHitFly = ranged ? true : !!tp.canHitFly;
|
||||
// 初始伤害类型(不攻击、治疗类型则不继承)
|
||||
if (!(tp.dmgType === 'heal' || tp.dmgType === 'none' || tp.noAttack || tp.noAttackUnlessSkill)) p.dmgType = tp.dmgType;
|
||||
|
||||
+16
-11
@@ -10,7 +10,9 @@
|
||||
// boomerang bool (回环射手: keeps 'boomerang')
|
||||
// canHitFly bool maxTargets n (≥1) hitAllBlocked bool (attack every blocked enemy)
|
||||
// allInRange bool (every enemy on the range at once) splashRadius tiles (around the struck target)
|
||||
// rangeAoe bool (a 锁定攻击范围 AoE without a projectile: allInRange, instant 'beam' hits)
|
||||
// rangeAoe bool (a 锁定攻击范围 AoE without a projectile — SUB table or kit trait, applied by resolveProfile after
|
||||
// every override: allInRange + instant 'beam' hits on a ranged profile; only selectable enemies are
|
||||
// struck — a stealthed one is not, unless revealed or blocked; PRTS 作战机制 §AOE伤害判定)
|
||||
// splashScale (× damage for splash victims)
|
||||
// splashOthersOnly bool groundOnly bool hits n (damage instances per attack)
|
||||
// chain {count, falloff, radius, sluggish} heal {mode:'single'|'multi'|'chain', count, falloff, farMul, elementHealRatio}
|
||||
@@ -300,12 +302,12 @@ export const SUB = Object.freeze({
|
||||
return front ? (unit.profile.frontScale ?? 1.5) : 1;
|
||||
} }),
|
||||
// --- CASTER
|
||||
// "群体法术伤害" names two shapes: the 扩散术师 splash 1.1 tiles around the struck target (Arknights Terra Wiki, Splash
|
||||
// Caster), while the 轰击术师 ("超远距离的群体法术伤害") and the 阵法术师 strike every enemy inside the attack range at
|
||||
// once, the same damage near and far (Terra Wiki, Blast / Phalanx Caster; PRTS 作战机制 §AOE伤害判定: 伊芙利特's 炎爆 is a
|
||||
// 锁定攻击范围 AoE) — community report E3 after 0.1.0
|
||||
// "群体法术伤害" names two shapes: the 扩散术师 splash 1.1 tiles around the struck target (PRTS 溅射半径一览 — which lists
|
||||
// no splash for the 阵法术师 / 轰击术师 — and Arknights Terra Wiki, Splash Caster), while the 轰击术师 ("超远距离的群体法术
|
||||
// 伤害") and the 阵法术师 strike every enemy inside the attack range at once, the same damage near and far (Terra Wiki,
|
||||
// Blast / Phalanx Caster; PRTS 作战机制 §AOE伤害判定: 伊芙利特's 炎爆 is a 锁定攻击范围 AoE) — community report E3 after 0.1.0
|
||||
splashcaster: P({ splashRadius: 1.1 }),
|
||||
blastcaster: P({ allInRange: true, rangeAoe: true }),
|
||||
blastcaster: P({ rangeAoe: true }),
|
||||
chain: P({ chain: { count: 3, falloff: 0.15, radius: 1.8, sluggish: 0.5 } }),
|
||||
funnel: P({ projectile: 'drone', install: installFunnel,
|
||||
dmgMul: (battle, unit, target) => {
|
||||
@@ -316,7 +318,7 @@ export const SUB = Object.freeze({
|
||||
} }),
|
||||
mystic: P({ install: installMystic,
|
||||
hitsFn: (battle, unit) => { const n = 1 + (unit.trait.stored ?? 0); unit.trait.stored = 0; return n; } }),
|
||||
phalanx: P({ noAttackUnlessSkill: true, allInRange: true, rangeAoe: true, install: installPhalanx }),
|
||||
phalanx: P({ noAttackUnlessSkill: true, rangeAoe: true, install: installPhalanx }),
|
||||
primcaster: P({}),
|
||||
corecaster: P({}),
|
||||
// --- MEDIC
|
||||
@@ -483,10 +485,6 @@ export function resolveProfile(def, kitTrait = null) {
|
||||
// a boomerang thrower (回环射手) always throws its boomerang: the data's generic ranged projectile ('arrow', a
|
||||
// build-data default) would turn the out-and-back flight into a plain shot
|
||||
if (p.boomerang && p.attack === 'ranged') p.projectile = 'boomerang';
|
||||
// a 锁定攻击范围 AoE (阵法术师, 轰击术师) has no projectile: every enemy on the range is struck at the same moment
|
||||
// ('beam' = instant hits, drawn as a line to each victim — PRTS 作战机制 "在攻击前摇结束时选取范围内的全体目标,同时
|
||||
// 造成伤害"); the data's generic ranged projectile ('bolt') would land them one by one
|
||||
if (p.rangeAoe && p.attack === 'ranged') p.projectile = 'beam';
|
||||
if (def.type === 'token') {
|
||||
if (p.dmgType === 'heal') p.heal = p.heal || { mode: 'single' };
|
||||
if (def.stats.atk <= 0) p.noAttack = true;
|
||||
@@ -495,6 +493,13 @@ export function resolveProfile(def, kitTrait = null) {
|
||||
if (p.dmgType !== 'heal' && p.heal) p.heal = null;
|
||||
if (p.dmgType === 'none') p.noAttack = true;
|
||||
if (kitTrait) Object.assign(p, kitTrait);
|
||||
// a 锁定攻击范围 AoE (阵法术师, 轰击术师) strikes every enemy on its range and has no projectile: they are struck at the
|
||||
// same moment ('beam' = instant hits, drawn as a line to each victim — PRTS 作战机制 "在攻击前摇结束时选取范围内的全体
|
||||
// 目标,同时造成伤害"); the data's generic ranged projectile ('bolt') would land them one by one
|
||||
if (p.rangeAoe) {
|
||||
p.allInRange = true;
|
||||
if (p.attack === 'ranged') p.projectile = 'beam';
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user