feedback1 WD: knock-out forms audit — leaders' 重生 clips, 守墓石像 modes
- reborn() tells the model its forms ('reborn' then 'form2' on the existing fx):
锏 and 扎罗 hold their revive clips and switch to their B_* set (both used to
stay on the A model and die on B_Die), “复仇者” plays Revive_Begin/Loop/End,
杰斯顿 its C1_Die then the C2_* killer form.
- 守墓石像 per its PRTS talent: melee only while blocked; the first defeat is an
instant 重生 into a 10 s unblockable, immobile, 失衡/浮空-immune statue (Sleep
clip), then a flyer with ranged arts attacks that skip flyers, without a
second HP refill.
- A form kind the skeleton has no clip set for (arts barrier, broken charge)
no longer resets a model's form.
This commit is contained in:
1 parent
92282878d3
commit
73e8bdcd87
7 files changed
+175
-23
No files matched your search
@@ -648,17 +648,33 @@ for (const key of ['enemy_1169_duphlx', 'enemy_1169_duphlx_2']) {
|
||||
}
|
||||
|
||||
for (const key of ['enemy_1172_dugago', 'enemy_1172_dugago_2']) {
|
||||
test(`${nm(key)}: first knock-out ⇒ statue (DEF +${tb(key, 'stone.def')}) for ${tb(key, 'stone.duration')} s, then reborn as a flyer`, () => {
|
||||
const h = arena({ units: [{ chessId: 't_wall', row: 10, col: 7 }] });
|
||||
test(`${nm(key)}: melee only while blocked; first knock-out ⇒ unblockable statue (DEF +${tb(key, 'stone.def')}, 失衡 / 浮空 immune) for ${tb(key, 'stone.duration')} s, then a flyer with ranged arts attacks that skip flyers (PRTS 天赋)`, () => {
|
||||
// 地面模式: an operator in its 1.6 radius but not blocking it is never attacked
|
||||
const h0 = arena({ units: [{ chessId: 't_gun', row: 11, col: 7 }] });
|
||||
h0.step();
|
||||
put(h0, key, [10, 7]);
|
||||
h0.run(6);
|
||||
assert.equal(h0.unit('t_gun').stats.taken, 0, 'no ranged attack in its ground mode');
|
||||
const h = arena({ units: [{ chessId: 't_wall', row: 10, col: 7 }, { chessId: 't_gun', row: 11, col: 7 }], kits: { t_gun: NOATK }, hooks: ['damaged'], captureNoisy: true });
|
||||
h.step();
|
||||
const e = put(h, key, [10, 7]);
|
||||
h.step();
|
||||
killed(h, e, null);
|
||||
assert.ok(e.alive);
|
||||
assert.equal(e.hp, e.s.maxHp, 'an instant 重生 to full HP');
|
||||
assert.equal(e.s.def, E[key].stats.def + tb(key, 'stone.def'));
|
||||
h.step();
|
||||
assert.ok(e.s.flags.unblockable && e.s.flags.noDisplace && !e.blockedBy, '转换模式: 无法被阻挡, 失衡免疫');
|
||||
assert.equal(h.b.applyStatus(e, 'levitate', { duration: 2 }), false, '免疫浮空');
|
||||
h.b.dealDamage(h.unit('t_gun'), e, { amount: e.s.maxHp * 0.3, type: 'true' });
|
||||
const hp = e.hp;
|
||||
h.run(tb(key, 'stone.duration') + 0.5);
|
||||
assert.equal(e.motion, 'FLY');
|
||||
assert.ok(!e.blockedBy);
|
||||
assert.equal(e.hp, hp, 'no second refill when it takes off');
|
||||
assert.ok(h.runUntil(() => e.stats.attacks > 0, 10), '飞行模式 attacks at range');
|
||||
assert.ok(h.unit('t_gun').stats.taken + h.unit('t_wall').stats.taken > 0, 'an operator in its radius');
|
||||
assert.ok(h.hooksOf('damaged').filter((c) => c.source === e && c.dmg.isAttack).every((c) => c.dmg.type === 'arts'), 'arts damage');
|
||||
killed(h, e, null);
|
||||
assert.ok(!e.alive, 'second knock-out is final');
|
||||
});
|
||||
|
||||
@@ -212,6 +212,67 @@ describe('逐火 embers and the 再生 puppet (user report after 0.1.0, #8)', ()
|
||||
});
|
||||
});
|
||||
|
||||
describe('the leaders\' 重生 and 守墓石像 (audit of the knock-out forms after report #5)', () => {
|
||||
const REBIRTH = {
|
||||
enemy_1525_blkswb: ['Revive1', 'Revive2', 'Revive3', 'B_Idle', 'B_Die'],
|
||||
enemy_1535_wlfmster: ['A_revive_1', 'A_revive_2', 'A_revive_3', 'B_Idle', 'B_Die'],
|
||||
enemy_1539_reid: ['Revive_Begin', 'Revive_Loop', 'Revive_End', 'Idle', 'Die'],
|
||||
};
|
||||
for (const [id, [begin, hold, end, idle2, die2]] of Object.entries(REBIRTH)) {
|
||||
test(`${id}: knock-out ⇒ ${begin}, ${hold} held through the 重生's stun, ${end}, then ${idle2}; dies on ${die2}`, async () => {
|
||||
const anims = assets.enemies[id].spine.animations;
|
||||
for (const name of [begin, hold, end, idle2, die2]) assert.ok(name in anims, name);
|
||||
const v = await enemy(id);
|
||||
v.sync(sample(UF.STUNNED, ANIM.STUN), 1);
|
||||
v.setForm('reborn');
|
||||
assert.equal(clip(v), begin);
|
||||
frames(v, Math.ceil(anims[begin] * 60) + 5);
|
||||
assert.equal(clip(v), hold, 'held (its stun role) while the sim keeps it stunned');
|
||||
v.sync(sample(0, ANIM.IDLE), 6);
|
||||
v.setForm('form2');
|
||||
assert.equal(clip(v), end);
|
||||
frames(v, Math.ceil(anims[end] * 60) + 5);
|
||||
assert.equal(clip(v), idle2);
|
||||
v.die();
|
||||
assert.equal(clip(v), die2);
|
||||
});
|
||||
}
|
||||
|
||||
test('enemy_1516_jakill 杰斯顿: C1_Die is its 4 s 重生, then the killer form C2_*', async () => {
|
||||
const v = await enemy('enemy_1516_jakill');
|
||||
v.setForm('reborn');
|
||||
assert.equal(clip(v), 'C1_Die');
|
||||
frames(v, 245);
|
||||
v.setForm('form2');
|
||||
v.sync(sample(0, ANIM.MOVE), 5);
|
||||
assert.equal(clip(v), 'C2_Move');
|
||||
v.onAttack(null, 5.5);
|
||||
assert.equal(clip(v), 'C2_Attack');
|
||||
});
|
||||
|
||||
test('enemy_1172_dugago 守墓石像: the statue on Sleep, the flyer on *_2', async () => {
|
||||
const v = await enemy('enemy_1172_dugago');
|
||||
v.setForm('stone');
|
||||
v.sync(sample(), 1);
|
||||
assert.equal(clip(v), 'Sleep');
|
||||
v.setForm('fly');
|
||||
v.sync(sample(UF.FLYING, ANIM.MOVE), 2);
|
||||
assert.equal(clip(v), 'Move_2');
|
||||
v.die();
|
||||
assert.equal(clip(v), 'Die_2');
|
||||
});
|
||||
|
||||
test('a kind without a clip set of that skeleton (an arts barrier, a broken charge) keeps the current form', async () => {
|
||||
const v = await enemy('enemy_10081_mpplai');
|
||||
v.setForm('translator_fuchou');
|
||||
frames(v, 130);
|
||||
v.setForm('artsBarrier');
|
||||
assert.equal(v.form, 'translator_fuchou');
|
||||
v.sync(sample(), 3);
|
||||
assert.equal(clip(v), 'B_Idle');
|
||||
});
|
||||
});
|
||||
|
||||
test('render/app.js hands the sim\'s fx \'phase\' kind — or the `form` of any fx — to the view and keeps the mode on the unit info', () => {
|
||||
const src = readFileSync(path.join(ROOT, 'public/js/render/app.js'), 'utf8');
|
||||
assert.match(src, /e\[1\] === 'phase' \? ex4\.kind : 'form' in ex4 \? ex4\.form[\s\S]{0,200}inf\.form = [\s\S]{0,120}setForm\?\.\(form\)/);
|
||||
|
||||
@@ -211,6 +211,45 @@ describe('#8 深池逐火: a knock-out is a walking 隐匿 ember that a blocker
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// audit: every other knock-out form of the mode ends in a targetable, killable unit and tells the model its clip set
|
||||
|
||||
describe('audit: the leaders\' 重生 and 守墓石像 — no vanishing, killable afterwards, the model follows', () => {
|
||||
const CASES = [
|
||||
['enemy_1525_blkswb', 'Reborn.duration', 'Reborn.invincible', 'reborn', 'form2'],
|
||||
['enemy_1535_wlfmster', 'Reborn.duration', 'Passive2.invincible_time', 'reborn', 'form2'],
|
||||
['enemy_1539_reid', 'Reborn.duration', 'Reborn.invincible', 'reborn', 'form2'],
|
||||
['enemy_1516_jakill', 'reborn.duration', null, 'reborn', 'form2'],
|
||||
['enemy_1517_xi', 'reborn.duration', 'reborn.invincible', 'reborn', 'form2'],
|
||||
['enemy_1512_mcmstr', 'reborn.duration', null, 'reborn', 'form2'],
|
||||
['enemy_10098_crhro', 'reborn.duration', 'reborn.invincible', 'reborn', 'form2'],
|
||||
['enemy_1172_dugago', 'stone.duration', null, 'stone', 'fly'],
|
||||
['enemy_1172_dugago_2', 'stone.duration', null, 'stone', 'fly'],
|
||||
];
|
||||
for (const [key, durKey, invKey, f1, f2] of CASES) {
|
||||
test(`${key} ${E[key].name}: knock-out ⇒ form '${f1}' (no die event, no kill), then '${f2}' — targetable and killed for good by the next knock-out`, () => {
|
||||
const h = arena({ units: [{ chessId: 't_gun', row: 10, col: 4 }], kits: QUIET_GUNS });
|
||||
h.step();
|
||||
const e = put(h, key, { pos: [10, 7], mods: { speedMul: 0 } });
|
||||
const gun = h.unit('t_gun');
|
||||
h.b.kill(e, gun);
|
||||
assert.ok(e.alive, 'not removed');
|
||||
const fxOf = (form) => h.eventsOf('fx').some((ev) => ev[4] && ev[4].id === e.id && ev[4].form === form);
|
||||
assert.ok(fxOf(f1), `fx form '${f1}' at the knock-out`);
|
||||
assert.equal(h.eventsOf('die').filter((ev) => ev[1] === e.id).length, 0, 'no die event');
|
||||
assert.equal(h.b.killed, 0, 'no kill');
|
||||
h.run((tb(key, durKey) ?? 0) + (invKey ? tb(key, invKey) ?? 0 : 0) + 0.3);
|
||||
assert.ok(e.alive && fxOf(f2), `fx form '${f2}' when it stands up`);
|
||||
assert.ok(!e.s.flags.untargetable && !e.s.flags.invulnerable, 'targetable and vulnerable again');
|
||||
if (!e.s.flags.stealth && !e.isFlying) assert.ok(canTargetEnemy(gun, e, gun.profile), 'a ranged operator can target it');
|
||||
h.b.kill(e, gun);
|
||||
assert.ok(!e.alive && e.removeReason === 'killed', 'the second knock-out is its death');
|
||||
assert.equal(h.b.killed, 1);
|
||||
assert.equal(h.eventsOf('die').filter((ev) => ev[1] === e.id).length, 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// #5 转译基底·α
|
||||
|
||||
|
||||
Reference in new issue
Block a user