feedback1b WH: Mlynar own-attack kills count the damage his attack carries
The S2 / S3 kill tracker kept only the last target of his attack, so the true damage carried by that attack (卡西米尔 bond, 天马之枪) overwrote it and a kill landed by that damage was not counted: S2 lost its "trait not reset" in the 卡西米尔 lineup or with the 天马 set, and S3 never lowered the trait bonus with the 天马 set. Track every enemy the current attack (or the mark it set off) hit, cleared after the attack; reflect kills and marks set off by other operators stay excluded (PRTS 备注).
This commit is contained in:
1 parent
8ecaa46a55
commit
20b65a5ff0
2 files changed
+67
-8
No files matched your search
@@ -2190,18 +2190,19 @@ const KITS = {
|
||||
else battle.removeBuff(unit, 'mlynar:traitUp');
|
||||
};
|
||||
/**
|
||||
* "仅自身普通攻击(与该次攻击附带的伤害)击倒非角色类单位" (PRTS S2 / S3 备注): `onKill(victim)` for each enemy whose
|
||||
* last damage from him came from his attack or the mark that attack set off (tagged 'mlynarOwn') while his skill
|
||||
* runs; `onAttack()` after each of his attacks ("加成降低于当次攻击后统一结算").
|
||||
* "仅自身普通攻击(与该次攻击附带的伤害)击倒非角色类单位" (PRTS S2 / S3 备注): `onKill(victim)` for each enemy he
|
||||
* knocks out, while his skill runs, that his current attack hit (or the mark it set off, tagged 'mlynarOwn') — so a
|
||||
* kill by the damage that attack carries (天马之枪, the 卡西米尔 bond's true damage, the mark) counts too, in any
|
||||
* hook order; `onAttack()` after each of his attacks ("加成降低于当次攻击后统一结算"), which also closes the set.
|
||||
* 无动于衷's reflection and a mark another operator's attack set off happen outside his attack: never counted.
|
||||
*/
|
||||
const ownKills = (battle, unit, onKill, onAttack = null) => {
|
||||
let last = null;
|
||||
const hit = new Set();
|
||||
battle.on('damaged', (c) => {
|
||||
if (c.source !== unit) return;
|
||||
last = c.dmg?.isAttack || (c.dmg?.tags || []).includes('mlynarOwn') ? c.target : null;
|
||||
if (c.source === unit && (c.dmg?.isAttack || (c.dmg?.tags || []).includes('mlynarOwn'))) hit.add(c.target);
|
||||
}, { owner: unit, priority: 1000 });
|
||||
battle.on('kill', (c) => { if (c.killer === unit && c.victim === last && c.victim.side === 'enemy' && unit.skill?.active) onKill(c.victim); }, { owner: unit });
|
||||
battle.on('attack', (c) => { if (c.attacker !== unit) return; last = null; if (onAttack) onAttack(); }, { owner: unit });
|
||||
battle.on('kill', (c) => { if (c.killer === unit && hit.has(c.victim) && c.victim.side === 'enemy' && unit.skill?.active) onKill(c.victim); }, { owner: unit });
|
||||
battle.on('attack', (c) => { if (c.attacker !== unit) return; hit.clear(); if (onAttack) onAttack(); }, { owner: unit });
|
||||
};
|
||||
return {
|
||||
skills: lazySkills({
|
||||
|
||||
@@ -213,3 +213,61 @@ test('B4 玛恩纳 S3 per-kill rule (PRTS 备注): trait ATK bonus −10 % per k
|
||||
assert.deepEqual(h.b.errors.map((e) => `${e.label}: ${e.message}`), []);
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test('B4 玛恩纳: a kill by the damage his attack carries (卡西米尔 bond true damage, 天马之枪) is a kill of his own attack — S2 keeps the ramp, S3 lowers the bonus', () => {
|
||||
const weak = (key) => enemyRec({ key, hp: 1, def: 0, speed: 0, atk: 0 });
|
||||
const KAZ6 = { kazimierzShip: { count: 6, active: true, tier: 2, layers: 0 } };
|
||||
const PEGASUS = ['chess_item_5_09_e_a', 'chess_item_6_01_e_a']; // 天马之盔 + 天马之枪: every damage instance + 30 % ATK true
|
||||
for (const [label, items, bonds, carried] of [['卡西米尔 6', [], KAZ6, 'bond:kazimierz'], ['天马之盔 + 天马之枪', PEGASUS, {}, 'item:pegasus']]) {
|
||||
const setup = (skillIndex) => {
|
||||
const h = makeBattle({
|
||||
defs: { enemies: { enemy_weak: weak('enemy_weak') } },
|
||||
units: [{ chessId: MLYNAR, row: 10, col: 4, dir: 'RIGHT', items, skillIndex }], bonds, autoFinish: false, timeLimit: 200,
|
||||
});
|
||||
const u = h.unit(MLYNAR);
|
||||
// which damage instance of his knocked each victim out (the last one he dealt it before the kill)
|
||||
const lastTags = new Map(), killedBy = [];
|
||||
h.b.on('damaged', (c) => { if (c.source === u) lastTags.set(c.target, c.dmg?.tags || []); });
|
||||
h.b.on('kill', (c) => { if (c.killer === u) killedBy.push(lastTags.get(c.victim) || []); });
|
||||
return { h, u, killedBy };
|
||||
};
|
||||
// S2 未宽解的悲哀: "技能期间若击倒敌人,技能结束时特性效果不重置"
|
||||
{
|
||||
const { h, u, killedBy } = setup(1);
|
||||
assert.equal(u.def.skill.id, 'skchr_mlynar_2');
|
||||
h.run(20.5);
|
||||
const ramp = u.trait.ramp;
|
||||
approx(ramp, 1, 1e-9, `${label}: ramp after 20 s`);
|
||||
h.spawn('enemy_weak', { pos: [10, 5] });
|
||||
u.skill.gainSp(1000);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 2));
|
||||
assert.ok(h.runUntil(() => !u.skill.active, 40));
|
||||
h.step();
|
||||
assert.equal(killedBy.length, 1, `${label}: S2 knocked the enemy out`);
|
||||
assert.ok(killedBy[0].includes(carried), `${label}: the carried damage landed the kill (${killedBy[0]})`);
|
||||
approx(u.trait.ramp, ramp, 1e-9, `${label}: S2 kill of his attack keeps the ramp`);
|
||||
}
|
||||
// S3 未照耀的荣光: −10 % trait bonus per own kill
|
||||
{
|
||||
const { h, u, killedBy } = setup(2);
|
||||
assert.equal(u.def.skill.id, 'skchr_mlynar_3');
|
||||
const bb = u.def.skill.bb;
|
||||
h.run(10.5);
|
||||
const ramp = u.trait.ramp;
|
||||
for (const p of [[10, 5], [10, 6], [10, 7], [11, 5], [9, 5]]) h.spawn('enemy_weak', { pos: p });
|
||||
const bonus = () => u.s.atk / u.base.atk - 1;
|
||||
let atCast = null;
|
||||
h.b.on('skillStart', (c) => { if (c.unit === u) atCast = bonus(); }, { priority: -1000 });
|
||||
u.skill.gainSp(1000);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 2));
|
||||
assert.ok(h.runUntil(() => killedBy.length >= 5, 3), `${label}: S3 knocks the 5 enemies out`);
|
||||
h.step();
|
||||
// the carried damage (or the S3 mark his attack set off, whichever hook runs first) landed the kills
|
||||
assert.ok(killedBy.every((t) => t.includes(carried) || t.includes('mlynarOwn')), `${label}: carried damage landed the kills`);
|
||||
assert.ok(killedBy.some((t) => t.includes(carried)) || carried === 'bond:kazimierz', `${label}: some kill by ${carried}`);
|
||||
assert.equal(u.mem.mlyKills, 5, `${label}: 5 own kills`);
|
||||
approx(bonus() - atCast, 5 * bb.per_kill_reduce, 1e-9, `${label}: 5 kills: −50 points (ramp ${ramp})`);
|
||||
assert.deepEqual(h.b.errors.map((e) => `${e.label}: ${e.message}`), []);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user