1 parent
584cedc1b4
commit
4980ee786f
13 files changed
+153
-30
No files matched your search
@@ -195,6 +195,8 @@ test('薄绿 (3_08) 聚能涡旋 pushes the target towards her centre whatever h
|
||||
fill(u);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 5));
|
||||
h.run(3);
|
||||
assert.ok(h.eventsOf('fx').some((f) => f[1] === 'displace' && f[4]?.keepFacing === true),
|
||||
`${dir}: Mint S2's inward push inherits the common facing metadata`);
|
||||
return h.enemy('enemy_d');
|
||||
};
|
||||
for (const dir of ['UP', 'RIGHT']) {
|
||||
|
||||
@@ -533,6 +533,8 @@ test('歌蕾蒂娅 S1 缺水的大洋裂断: charges; next attack atk_scale (×1
|
||||
assert.equal(sk.length, 1);
|
||||
approx(sk[0].amount, u.s.atk * bb.atk_scale * t1.atk_scale, 1e-6, `${id} hit`);
|
||||
assert.ok(e.x < x0 - 0.5, `${id}: pulled towards her (${x0} → ${e.x})`);
|
||||
assert.ok(h.eventsOf('fx').some((f) => f[1] === 'displace' && f[4]?.keepFacing === true),
|
||||
`${id}: S1 pull carries the common facing metadata`);
|
||||
// 中力 vs weight 1 (受力等级 0): "必定拉至身前" — to the 急停 radius 0.6708 around her centre (PRTS 推与拉), never past it
|
||||
approx(e.x, u.x + 0.6708, 1e-6, `${id}: stops at her front`);
|
||||
}
|
||||
@@ -555,6 +557,8 @@ test('歌蕾蒂娅 S2 缺水的掌握怒海: BAT +0.5 s, wider range, 2 targets
|
||||
assert.equal(hits.length, bb['attack@max_target']);
|
||||
for (const c of hits) approx(c.amount, u.s.atk * bb['attack@atk_scale'] * t1.atk_scale, 1e-6);
|
||||
for (const e of atk.targets) assert.ok(e.x < 6 - 0.4, `pulled (${e.x})`);
|
||||
assert.ok(h.eventsOf('fx').some((f) => f[1] === 'displace' && f[4]?.keepFacing === true),
|
||||
`${id}: S2 pull carries the common facing metadata`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -609,7 +609,8 @@ test('1_19 野鬃: 夹枪冲锋 wider range, ATK +atk, pushes targets away; elit
|
||||
approx(u.s.atk, u.base.atk * (1 + bb.atk));
|
||||
const e = h.enemies()[0];
|
||||
assert.ok(e.x >= 6.5, `pushed to x=${e.x}`);
|
||||
assert.ok(h.eventsOf('fx').some((f) => f[1] === 'displace'));
|
||||
assert.ok(h.eventsOf('fx').some((f) => f[1] === 'displace' && f[4]?.keepFacing === true),
|
||||
'Wild Mane S2 inherits the common push facing rule');
|
||||
done(h);
|
||||
|
||||
const idb = 'chess_char_1_19_b', t = tal(idb);
|
||||
|
||||
@@ -121,6 +121,8 @@ test('莫斯提马 S3: ripple hits every enemy in range, ATK +90 %, knock-back;
|
||||
assert.ok(es.every((e) => hits.some((c) => c.target === e)), 'all three enemies hit by one ripple');
|
||||
approx(hits[0].amount, u.s.atk, 1e-6, 'full damage (res 0)');
|
||||
assert.ok(es.some((e, i) => e.x > x0[i] + 0.2), 'knocked back');
|
||||
assert.ok(h.eventsOf('fx').some((f) => f[1] === 'displace' && f[4]?.keepFacing === true),
|
||||
'Mostima S3 inherits the common push facing rule');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
|
||||
@@ -567,19 +567,50 @@ describe('a push / pull slide (推拉: the official impulse under friction)', ()
|
||||
assert.equal(v.x, 7.7, 'and it still lands');
|
||||
});
|
||||
|
||||
test('a pushed enemy faces the way it is pushed, not the route it resumes', async () => {
|
||||
// The official turns a displaced unit towards the force (_dontChangeFaceByDirection is an opt-in flag); our facing
|
||||
// comes from the snapshot's vx, which after a displacement points back down the route the enemy walks again —
|
||||
// i.e. against the push (player report: 被推开敌人的方向反过来了).
|
||||
test('an unmarked raw displacement still faces its travel direction', async () => {
|
||||
const v = await view(10);
|
||||
v.sync({ x: 5, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 0, vx: -1.2 });
|
||||
assert.equal(v.visFacing, -1, 'walking left before the push');
|
||||
v.slideTo(7.7, 12); // pushed to the right
|
||||
assert.equal(v.visFacing, 1, 'faces the push at once');
|
||||
assert.equal(v.visFacing, -1, 'walking left before the pull');
|
||||
v.slideTo(7.7, 12); // unmarked raw move to the right
|
||||
assert.equal(v.visFacing, 1, 'faces the movement at once');
|
||||
for (let i = 0; i < 20; i++) v.update(v.slide.dur / 40, cam(), 0);
|
||||
assert.equal(v.visFacing, 1, 'still facing the push in flight');
|
||||
v.slideTo(5.2, 12); // now pulled back to the left
|
||||
assert.equal(v.visFacing, -1, 'and the other way for a pull');
|
||||
assert.equal(v.visFacing, 1, 'still facing the movement in flight');
|
||||
v.slideTo(5.2, 12); // another unmarked raw move to the left
|
||||
assert.equal(v.visFacing, -1, 'and the other way for a raw move');
|
||||
});
|
||||
|
||||
test('a flagged push or pull keeps the enemy facing left while sliding right, then resumes snapshot facing', async () => {
|
||||
const v = await view(14);
|
||||
v.sync({ x: 5, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 1, vx: -1 });
|
||||
assert.equal(v.visFacing, -1, 'facing left before the push');
|
||||
v.slideTo(6.7, 12, { at: 3, dur: 0.8, keepFacing: true });
|
||||
assert.equal(v.visFacing, -1, 'unchanged when the push begins');
|
||||
v.sync({ x: 6.7, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 1, vx: 20 }, 2.95);
|
||||
v.update(1 / 60, cam(), 0);
|
||||
assert.equal(v.visFacing, -1, 'a positive interpolated velocity cannot flip it before the destination snapshot');
|
||||
v.sync({ x: 6.7, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 1, vx: -1 }, 3);
|
||||
const dur = v.slide.dur;
|
||||
step(v, dur / 2);
|
||||
assert.ok(v.x > 5 && v.x < 6.7, `still slides right (${v.x})`);
|
||||
assert.equal(v.visFacing, -1, 'unchanged in flight');
|
||||
step(v, dur / 2);
|
||||
assert.equal(v.slide, null, 'slide ends normally');
|
||||
assert.equal(v.x, 6.7);
|
||||
v.sync({ x: 6.6, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 1, vx: 1 });
|
||||
assert.equal(v.visFacing, 1, 'normal snapshot facing resumes after the slide');
|
||||
});
|
||||
|
||||
test('a buffered earlier attack sets the facing retained by a prestarted slide in either field direction', async () => {
|
||||
for (const sign of [-1, 1]) {
|
||||
const v = await view(30 + sign);
|
||||
v.sync({ x: 5, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 1, vx: -sign });
|
||||
v.slideTo(5 - sign, 12, { at: 3, dur: 0.8, keepFacing: true });
|
||||
v.onAttack({ x: 5 + sign, y: 12 }, 2.9, 'melee');
|
||||
assert.equal(v.visFacing, sign, 'the earlier attack supplies the real pre-push facing');
|
||||
v.sync({ x: 5 - sign, y: 12, hp: 100, maxHp: 100, sp: 0, spMax: 0, flags: 0, anim: 1, vx: -sign * 20 }, 3);
|
||||
step(v, v.slide.dur / 2);
|
||||
assert.equal(v.visFacing, sign, 'the slide and sampled velocity retain that facing');
|
||||
}
|
||||
});
|
||||
|
||||
test('a snapshot in flight does not teleport it (the slide owns the position until it lands)', async () => {
|
||||
|
||||
@@ -31,3 +31,32 @@ test('a displacement puts a `displace` fx into the event stream the client recei
|
||||
assert.equal(disp[4].y, undefined, 'e[4] carries no y either');
|
||||
});
|
||||
|
||||
test('directional and radial pushes and hook pulls preserve facing; explicit overrides and raw moves do not', () => {
|
||||
for (const directional of [false, true]) {
|
||||
const h = makeBattle({
|
||||
defs: { enemies: { enemy_walker: walker({ speed: 0.01 }) } },
|
||||
enemies: [{ key: 'enemy_walker', pos: [10, 6] }],
|
||||
content: 'none', autoFinish: false,
|
||||
});
|
||||
h.step();
|
||||
const e = h.enemy('enemy_walker');
|
||||
h.b.drainEvents();
|
||||
const from = { x: 5, y: 10 };
|
||||
const opts = directional ? { from, dir: { x: 1, y: 0 } } : { from };
|
||||
assert.ok(h.b.push(e, 0, opts) > 0);
|
||||
const fx = () => h.b.drainEvents().find((ev) => ev[0] === 'fx' && ev[1] === 'displace');
|
||||
assert.equal(fx()?.[4]?.keepFacing, true, 'both official push direction classes preserve the prior facing');
|
||||
|
||||
assert.ok(h.b.pull(e, 0, { to: { x: 5, y: 10 } }) > 0);
|
||||
assert.equal(fx()?.[4]?.keepFacing, true, 'hook pull preserves the pre-hit facing by default');
|
||||
|
||||
assert.ok(h.b.pull(e, 0, { to: { x: 9, y: 10 }, keepFacing: false }) > 0);
|
||||
assert.equal(fx()?.[4]?.keepFacing, undefined, 'a pull source can opt out');
|
||||
|
||||
assert.ok(h.b.push(e, 0, { ...opts, keepFacing: false }) > 0);
|
||||
assert.equal(fx()?.[4]?.keepFacing, undefined, 'a push source can opt out');
|
||||
|
||||
assert.ok(h.b.displace(e, { x: -1, y: 0 }, 0.2) > 0);
|
||||
assert.equal(fx()?.[4]?.keepFacing, undefined, 'raw displace remains unmarked');
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user