fix: 补回驭械术师普通攻击的本体伤害
This commit is contained in:
1 parent
bc50cac01f
commit
823e3d0af4
21 files changed
+1011
-710
No files matched your search
@@ -661,7 +661,7 @@ test('modules: removing the elite module changes the trait / talent behaviour th
|
||||
const h = run({ defs: { enemies: { e: dummy('e') } }, units: [{ chessId: id, row: 10, col: 4, carryState: IDLE, ...mod(m) }], enemies: [{ key: 'e', pos: [10, 6] }] });
|
||||
h.runUntil(() => dealt(h, h.unit(id)).length > 0, 5);
|
||||
const u = h.unit(id);
|
||||
const r = dealt(h, u)[0].amount / u.s.atk;
|
||||
const r = dealt(h, u, (c) => c.dmg.tags.includes('funnel'))[0].amount / u.s.atk;
|
||||
done(h);
|
||||
return r;
|
||||
});
|
||||
|
||||
@@ -1070,7 +1070,7 @@ test('6_17 耀骑士临光 S2 逐夜烁光: on deploy ATK + and 3 护盾 layers
|
||||
// =================================================================================================================
|
||||
// 6_18 荒芜拉普兰德
|
||||
|
||||
test('6_18 荒芜拉普兰德 S1 慵怠者悲鸣: passive 浮游单元+1 (2 hits per attack); toggled on: ATK + and the drones lock still enemies anywhere', () => {
|
||||
test('6_18 荒芜拉普兰德 S1 慵怠者悲鸣: passive 浮游单元+1 (one body plus two additions); toggled on: ATK + and the drones lock still enemies anywhere', () => {
|
||||
for (const id of both('chess_char_6_18')) {
|
||||
const sid = 'skchr_whitw2_1', bb = bbOf(id, sid);
|
||||
const idle = run({ defs: { enemies: { e: dummy('e') } }, units: [U(id, sid, 10, 4, { carryState: IDLE })], enemies: [{ key: 'e', pos: [10, 6] }] });
|
||||
@@ -1079,7 +1079,9 @@ test('6_18 荒芜拉普兰德 S1 慵怠者悲鸣: passive 浮游单元+1 (2 hits
|
||||
const a0 = idle.hooksOf('attack').find((c) => c.attacker === iu);
|
||||
assert.ok(a0);
|
||||
idle.run(1);
|
||||
assert.equal(dealt(idle, iu, (c) => c.dmg.attackId === dealt(idle, iu)[0].dmg.attackId).length, 2, 'drone +1');
|
||||
const volley = dealt(idle, iu, (c) => c.dmg.attackId === dealt(idle, iu)[0].dmg.attackId);
|
||||
assert.equal(volley.filter((c) => c.dmg.isAttack).length, 1, 'one body hit');
|
||||
assert.equal(volley.filter((c) => c.dmg.tags.includes('funnel')).length, 2, 'drone +1');
|
||||
done(idle);
|
||||
|
||||
const h = run({
|
||||
|
||||
@@ -488,13 +488,13 @@ test('3_13 至简: 神工意匠 charges: ATK% arts ×2 per cast; 忽有所悟 cr
|
||||
const u = h.unit(id);
|
||||
assert.equal(u.skill.maxCharges, bb.ct);
|
||||
h.run(60);
|
||||
const normal = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isAttack && !c.dmg.isSkill).map((c) => c.amount / u.s.atk);
|
||||
const normal = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.tags.includes('funnel') && !c.dmg.isSkill).map((c) => c.amount / u.s.atk);
|
||||
const ramp = (v) => [tb.max_atk_scale, tb.max_atk_scale * t0.atk_scale].some((x) => Math.abs(v - x) < 1e-6);
|
||||
assert.ok(normal.slice(10).every(ramp), 'ramped drone hits: max or max × crit');
|
||||
assert.ok(normal.some((v) => Math.abs(v - tb.max_atk_scale * t0.atk_scale) < 1e-6), 'some crits');
|
||||
assert.ok(normal.some((v) => Math.abs(v - tb.max_atk_scale) < 1e-6), 'some plain hits');
|
||||
const skillAtks = h.hooksOf('attack').filter((c) => c.attacker === u && c.isSkill).length;
|
||||
const sk = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isSkill).map((c) => c.amount / u.s.atk);
|
||||
const sk = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isSkill && c.dmg.isAttack).map((c) => c.amount / u.s.atk);
|
||||
assert.ok(skillAtks >= 2);
|
||||
assert.equal(sk.length, skillAtks * 2, 'two hits per cast');
|
||||
for (const v of sk) assert.ok([bb.atk_scale, bb.atk_scale * t0.atk_scale].some((x) => Math.abs(v - x) < 1e-6), `skill hit ${v}`);
|
||||
@@ -790,7 +790,7 @@ test('3_20 耶拉: 低眉 ATK (ground tiles in range); 心随意动 +1 drone, AT
|
||||
h.step();
|
||||
approx(u.s.atk, u.base.atk * (1 + t0['kjera_t_1[high].atk']));
|
||||
h.run(4);
|
||||
const first = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isAttack).map((c) => c.amount / u.s.atk);
|
||||
const first = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.tags.includes('funnel')).map((c) => c.amount / u.s.atk);
|
||||
assert.ok(first[1] > first[0], 'drone ramps on the same target');
|
||||
fill(u);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 5));
|
||||
@@ -1083,7 +1083,7 @@ test('3_20 耶拉: 心随意动 with a single enemy — both drones lock it and
|
||||
});
|
||||
const u = h.unit(id);
|
||||
h.run(12);
|
||||
const plain = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isAttack).map((c) => c.amount / u.s.atk);
|
||||
const plain = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.tags.includes('funnel')).map((c) => c.amount / u.s.atk);
|
||||
approx(plain[plain.length - 1], tb.max_atk_scale, 1e-6, 'module ramp cap');
|
||||
fill(u);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 5));
|
||||
@@ -1159,7 +1159,7 @@ test('精锐 numbers from the golden blackboards (module traits handled by the p
|
||||
const ml = m.unit(e13);
|
||||
assert.equal(ml.skill.maxCharges, BB(e13).ct);
|
||||
m.run(25);
|
||||
const ramps = m.hooksOf('damaged').filter((c) => c.source === ml && c.dmg?.isAttack && !c.dmg.isSkill).map((c) => c.amount / ml.s.atk);
|
||||
const ramps = m.hooksOf('damaged').filter((c) => c.source === ml && c.dmg?.tags.includes('funnel') && !c.dmg.isSkill).map((c) => c.amount / ml.s.atk);
|
||||
assert.ok(ramps.some((v) => Math.abs(v - TR(e13).max_atk_scale) < 1e-6), 'module cap 120 %');
|
||||
assert.ok(ramps.every((v) => v <= TR(e13).max_atk_scale * TB(e13, 0).atk_scale + 1e-6));
|
||||
done(m);
|
||||
|
||||
@@ -997,8 +997,9 @@ test('荒芜拉普兰德: 叙拉古的荣幸 +5 initial SP on every deployment;
|
||||
// (S3 may be running — its drones do not normal-attack): wait for a normal attack after stage 3
|
||||
assert.ok(h.runUntil(() => h.dmg.some((d) => d.src === u.id && d.attack && d.tgt === e.id && d.t > ts), 60));
|
||||
const last = h.dmg.filter((d) => d.src === u.id && d.attack && d.tgt === e.id).at(-1);
|
||||
const same = h.dmg.filter((d) => d.src === u.id && d.attack && d.t === last.t);
|
||||
assert.equal(same.length, 2, 'two drone hits per attack');
|
||||
const same = h.dmg.filter((d) => d.src === u.id && d.t === last.t);
|
||||
assert.equal(same.filter((d) => d.attack).length, 1, 'one body hit per attack');
|
||||
assert.equal(same.filter((d) => d.tags.includes('funnel')).length, 2, 'two drone additions per attack');
|
||||
});
|
||||
|
||||
test('缪尔赛思: 流形 copies stats/range/damage type but not the attack shape (锏 2-hit); a capped melee copy steals nothing more', () => {
|
||||
|
||||
@@ -80,18 +80,22 @@ test('a 自选 pick: 澄闪 is offered at tiers 5 and 6 and a roster with her pa
|
||||
{ ok: true, picks: { [SLOT[6]]: { charId: GD, skillIndex: 2, uniEquipId: FY } } });
|
||||
});
|
||||
|
||||
test('her normal attack (no skill): one drone hit per attack — init, +delta per hit on the same target, up to the cap (FUN-X from 0.35, FUN-Y to 1.2); 精准导流 lowers the RES it meets', () => {
|
||||
for (const f of [[5, false, null], [5, true, FX], [6, true, FY]]) {
|
||||
test('her normal attack (no skill): a body hit and one drone addition — init, +delta per hit on the same target, up to the cap (FUN-X from 0.35, FUN-Y to 1.2); 精准导流 lowers the RES of both', () => {
|
||||
for (const f of FORMS_ALL) {
|
||||
const [tier, elite, mod] = f;
|
||||
const { h, u } = field({ tier, elite, mod, skill: 2 });
|
||||
const e = h.spawn('enemy_res', { pos: [10, 6] });
|
||||
h.run(12);
|
||||
const hits = mine(h, u).filter((c) => c.target === e && c.dmg.isAttack);
|
||||
const drones = mine(h, u).filter((c) => c.target === e && c.dmg.tags.includes('funnel'));
|
||||
assert.ok(hits.length >= 8, `${label(f)}: ${hits.length} attacks`);
|
||||
assert.equal(drones.length, hits.length, `${label(f)}: one addition per body hit`);
|
||||
const tb = traitOf(tier, elite, mod).bb, pen = talentOf(tier, elite, mod, 1).bb.magic_resist_penetrate_fixed;
|
||||
hits.slice(0, 8).forEach((c, i) => {
|
||||
const ramp = Math.min(tb.max_atk_scale, tb.init_atk_scale + i * tb.delta_atk_scale);
|
||||
approx(c.amount, u.s.atk * ramp * (1 - (50 - pen) / 100), `${label(f)}: attack ${i + 1} at ${ramp}`);
|
||||
approx(c.amount, u.s.atk * (1 - (50 - pen) / 100), `${label(f)}: body attack ${i + 1}`);
|
||||
approx(drones[i].amount, c.amount * ramp, `${label(f)}: drone at ${ramp}`);
|
||||
assert.equal(drones[i].dmg.attackId, c.dmg.attackId, 'same attack action');
|
||||
assert.equal(c.type, 'arts');
|
||||
});
|
||||
done(h);
|
||||
|
||||
Reference in new issue
Block a user