feedback7 R22-OSIM: 望's placed 棋子 drop their 铸子 跟子 at the battle start (undeployable ground first, 上 > 右 > 下 > 左); a deployment on a 跟子's tile removes it; S3's overflow follows the same order; golden: unchanged
Community report relayed by the owner: 望的天赋以及3技能增强的天赋提供的额外棋子是可以出现(官方好像有出现的前后左右先后
顺序的规则)在不可部署地块的. The placement rule was already PRTS 望 铸子 备注's ("有敌人的>没有敌人的;不可部署地块>可部
署地面地块>可部署高台地块…相同优先级时按棋子上>右>下>左"), but only a 棋子's return dropped a 跟子: the battle-start
deployment of the pieces the player placed — their 手动部署 in this mode (PRTS 卫戍协议/帮助: a placement in 休整 fires
no deployment effect; the piece deploys when the combat starts) — dropped none, so no extra piece appeared, a lone 棋子
never connected and never set off. Now:
- the stone kit's deploy hook drops one 跟子 when `ctx.initial` (a return still drops its own in scheduleReturn);
- "跟子所在格进行部署(或进行类似部署的行为)时,该跟子会随之消失": any ally deploying on a 跟子's tile removes it (the
battle start deploys one piece after another, so a 跟子 may first land on a piece still to come);
- S3's overflow (备注 "采用相同的位置选择优先顺序"): enemy tiles first, then the tile class, then her target order /
distance / tile [ASSUMED ties] — an enemy on undeployable floor now comes before one on the road.
Her stock rule (the owner's decision of 2026-10-06) is unchanged; a 跟子 takes no stock.
Tests: test/content/op_wang.test.js — the four tests that encoded "the battle start drops no 跟子" updated, a removal
case and an S3 order test added (10; 5 fail on the parent).
This commit is contained in:
1 parent
977215e14d
commit
5a045731f1
2 files changed
+88
-41
No files matched your search
@@ -34,11 +34,14 @@
|
||||
// that touch on a side (上下 / 左右) activate each other and stay active once activated [ASSUMED from the S2 备注]; an
|
||||
// enemy (air units too) on an active one's tile sets it off: its effect, then it is used up (a 棋子 leaves the field; a
|
||||
// 跟子 is gone) [ASSUMED: a trap is spent by its trigger]. A spent 棋子 comes back on its tile ("原地再部署") after its
|
||||
// redeploy time (2 s), paying its cost and one of her stock, while 望 is on the field. That return is the mode's
|
||||
// deployment of her card: it counts as a 手动部署 for 铸子 —
|
||||
// one 跟子 on a tile next to it (the PRTS order above), at most attack@max_spawn_cnt on the field [ASSUMED: the battle-start
|
||||
// deployment does not count]. When 望 leaves the field her 棋子 and 跟子 vanish (PRTS 分支特性信息); the 棋子 come back
|
||||
// with her.
|
||||
// redeploy time (2 s), paying its cost and one of her stock, while 望 is on the field. 铸子 "手动部署棋子时,望在相邻位置额外
|
||||
// 部署一枚棋子": the 棋子 the player placed deploy at the battle start (PRTS 卫戍协议/帮助: a placement in 休整 fires no
|
||||
// deployment effect, the piece deploys when the combat starts) — that deployment drops one 跟子 on a tile next to it (the
|
||||
// PRTS order above; community report, 0.2.2 — until 0.2.1 the battle start dropped none, so a lone 棋子 never set off), at
|
||||
// most attack@max_spawn_cnt on the field; each later return counts as one too [ASSUMED]. The battle start deploys one
|
||||
// piece after another (Battle.start), so a 跟子 may land on the tile of a piece still to come: "跟子所在格进行部署(或进行类似
|
||||
// 部署的行为)时,该跟子会随之消失" (PRTS 铸子 备注) — any deployment on its tile removes it. When 望 leaves the field her 棋子
|
||||
// and 跟子 vanish (PRTS 分支特性信息); the 棋子 come back with her.
|
||||
// - T2 料敌机先 (attack@per_atk_scale / attack@per_magic_resist_penetrate_fixed / attack@max_trigger_cnt): an active 棋子 / 跟子
|
||||
// gets one stack per piece of the unbroken line it is part of (itself included; the longer of its lines; ≤ the cap),
|
||||
// kept once reached [ASSUMED]; its damage ×(1 + n × per) and n × pen RES ignored. TRP-X stage 3: 15 % / 13.
|
||||
@@ -52,8 +55,9 @@
|
||||
// attack@duration s, each one on its own timer, added up (speed ≥ 0.1). Active as S1.
|
||||
// - S3 天下劫 (MANUAL, data ACTIVE_RANGE on its 4-12; ammo trigger_time): passive — the pieces' trigger and damage area is the
|
||||
// token skill's range (x-6 in the data; PRTS writes x-1), atk_scale × her ATK arts on every enemy there. Active — no
|
||||
// attacks, range 4-12; +cnt stock, the part over the cap placed at once as 跟子 on the enemies of her range first (her
|
||||
// target order), then on other tiles of it by the 铸子 order [ASSUMED for the ties: nearest to her, then by tile]; a 棋子
|
||||
// attacks, range 4-12; +cnt stock, the part over the cap placed at once as 跟子 on the tiles of her range by the 铸子 order
|
||||
// (S3 备注 "采用相同的位置选择优先顺序"): enemy tiles first, then 不可部署 > 可部署地面 > 高台 [ASSUMED for the ties: her target
|
||||
// order among enemy tiles, then nearest to her, then by tile]; a 棋子
|
||||
// coming back inside her range during it drops up to 3 跟子, one bullet each ("第一天赋额外至多部署3枚棋子并消耗等量弹药");
|
||||
// it ends when the bullets are spent ("棋子耗尽" [ASSUMED]); bullets left at another end return to the stock.
|
||||
|
||||
@@ -231,6 +235,9 @@ export default {
|
||||
const w = owner.mem.wang;
|
||||
unit.mem.wangNode = { seq: ++w.seq, act: false, h: false, v: false, stacks: 0 };
|
||||
w.stock = Math.max(0, w.stock - 1); // it occupies her stock (the owner's decision of 2026-10-06)
|
||||
// 铸子: the battle-start deployment of a placed 棋子 is its 手动部署 — one 跟子 next to it (a return drops its own in
|
||||
// scheduleReturn)
|
||||
if (ctx.initial && up(owner)) dropFollowers(battle, owner, unit.tileR, unit.tileC, 1);
|
||||
}, { owner: unit });
|
||||
battle.on('death', (ctx) => {
|
||||
if (ctx.unit !== unit || battle.finished) return;
|
||||
@@ -264,28 +271,22 @@ export default {
|
||||
const total = w.stock + Math.max(0, Math.floor(num(b3.cnt)));
|
||||
const over = Math.max(0, total - stockCap);
|
||||
w.stock = Math.min(stockCap, total);
|
||||
// "将超出上限的棋子优先部署在范围内敌人所在位置": enemies of her (4-12) range first, then other tiles of it
|
||||
let n = Math.min(over, followCap - w.followers.length);
|
||||
// "将超出上限的棋子优先部署在范围内敌人所在位置" and the 备注's "采用相同的位置选择优先顺序" (铸子): the free tiles of her
|
||||
// (4-12) range, those holding an enemy she can target first, then 不可部署 > 可部署地面 > 高台; [ASSUMED] ties: her
|
||||
// target order among enemy tiles, then nearest to her, then by tile
|
||||
const n = Math.min(over, followCap - w.followers.length);
|
||||
if (!(n > 0)) return;
|
||||
const taken = new Set(piecesOf(battle, unit).map((p) => keyOf(p.r, p.c)));
|
||||
const foes = sortEnemyTargets(battle, unit, battle.enemiesInKeys(unit.rangeKeys, unit, ANY), unit.profile?.priority ?? null);
|
||||
for (const e of foes) {
|
||||
if (n <= 0) break;
|
||||
const r = Math.round(e.y), c = Math.round(e.x);
|
||||
if (!followerFree(battle, unit, r, c, taken)) continue;
|
||||
taken.add(keyOf(r, c));
|
||||
addFollower(battle, unit, r, c);
|
||||
n--;
|
||||
}
|
||||
if (n <= 0) return;
|
||||
const rest = [];
|
||||
const cands = [];
|
||||
for (const k of unit.rangeKeys ?? []) {
|
||||
const r = Math.floor(k / COLS), c = k % COLS;
|
||||
if (!followerFree(battle, unit, r, c, taken)) continue;
|
||||
rest.push({ r, c, k, cls: tileClass(battle, r, c), d: Math.max(Math.abs(r - unit.tileR), Math.abs(c - unit.tileC)) });
|
||||
const fi = foes.findIndex((e) => e.alive && bodyOnTile(e, r, c));
|
||||
cands.push({ r, c, k, foe: fi >= 0 ? 0 : 1, fi: fi >= 0 ? fi : 0, cls: tileClass(battle, r, c), d: Math.max(Math.abs(r - unit.tileR), Math.abs(c - unit.tileC)) });
|
||||
}
|
||||
rest.sort((a, b) => a.cls - b.cls || a.d - b.d || a.k - b.k);
|
||||
for (const t of rest.slice(0, n)) addFollower(battle, unit, t.r, t.c);
|
||||
cands.sort((a, b) => a.foe - b.foe || a.cls - b.cls || a.fi - b.fi || a.d - b.d || a.k - b.k);
|
||||
for (const t of cands.slice(0, n)) addFollower(battle, unit, t.r, t.c);
|
||||
},
|
||||
onEnd({ unit, reason }) {
|
||||
const w = unit.mem.wang;
|
||||
@@ -360,6 +361,15 @@ export default {
|
||||
const w = unit.mem.wang;
|
||||
w.followers = w.followers.filter((f) => f !== n);
|
||||
};
|
||||
// "跟子所在格进行部署(或进行类似部署的行为)时,该跟子会随之消失" (PRTS 铸子 备注): any ally deploying on a 跟子's tile
|
||||
// (priority 50: before that piece's own 铸子 drop)
|
||||
battle.on('deploy', (ctx) => {
|
||||
const a = ctx.unit, w = unit.mem.wang;
|
||||
if (!a || a.side !== 'ally' || !w || !w.followers.length) return;
|
||||
const n0 = w.followers.length;
|
||||
w.followers = w.followers.filter((f) => f.r !== a.tileR || f.c !== a.tileC);
|
||||
if (w.followers.length !== n0) battle.fx('disappear', { x: a.tileC, y: a.tileR, id: unit.id });
|
||||
}, { owner: unit, priority: 50 });
|
||||
battle.on('tick', () => {
|
||||
if (!up(unit) || battle.finished) return;
|
||||
const nodes = piecesOf(battle, unit);
|
||||
|
||||
@@ -101,12 +101,23 @@ test('望 in every 自选 form: her kit, stats + module attributes, 3-3, blocks
|
||||
assert.equal(validateDiyPicks({ [SLOT[6]]: { charId: WANG, skillIndex: 2, uniEquipId: X } }, { data, kitted: KITTED_CHARS }).ok, true);
|
||||
});
|
||||
|
||||
test('铸子: a lone 棋子 never sets off; two side by side activate each other — an enemy (air too) on one sets it off and it is used up; it stays active once its partner is gone', () => {
|
||||
{ // lone
|
||||
const { h, st } = field({ tier: 6, elite: true, skill: 1, stones: [[9, 6]] });
|
||||
test('铸子: the battle-start deployment of a placed 棋子 is its 手动部署 — one 跟子 next to it, so a lone 棋子 is live; with no free neighbour it never sets off; two side by side activate each other — an enemy (air too) on one sets it off and it is used up; it stays active once its partner is gone', () => {
|
||||
{ // lone: its 跟子 上 of it (its neighbours are road, 下 is off the field) — they connect
|
||||
const { h, u, st } = field({ tier: 6, elite: true, skill: 1, stones: [[9, 6]] });
|
||||
assert.deepEqual(placed(h), [[10, 6]], 'the battle start drops one 跟子, 上 first among equal tiles');
|
||||
assert.equal(u.mem.wang.stock, 6, 'the 棋子 takes one of her 7; the 跟子 none');
|
||||
h.spawn('enemy_dummy', { pos: [9, 6] });
|
||||
h.step();
|
||||
assert.ok(!st[0].alive && stoneHits(h).length > 0, 'a lone 棋子 with its 跟子 sets off');
|
||||
done(h);
|
||||
}
|
||||
{ // no free neighbour (operators on 上 / 右 / 左, 下 off the field): no 跟子, never sets off
|
||||
const others = [[10, 6], [9, 7], [9, 5]].map(([row, col], i) => ({ uid: 10 + i, chessId: 'chess_char_1_01_a', row, col }));
|
||||
const { h, st } = field({ tier: 6, elite: true, skill: 1, stones: [[9, 6]], others });
|
||||
assert.equal(placed(h).length, 0, 'nowhere to put it');
|
||||
h.spawn('enemy_dummy', { pos: [9, 6] });
|
||||
h.run(3);
|
||||
assert.equal(stoneHits(h).length, 0, 'a lone 棋子');
|
||||
assert.equal(stoneHits(h).length, 0, 'a 棋子 with no partner');
|
||||
assert.ok(st[0].alive);
|
||||
done(h);
|
||||
}
|
||||
@@ -115,7 +126,7 @@ test('铸子: a lone 棋子 never sets off; two side by side activate each other
|
||||
h.step();
|
||||
const a = at(st, 9, 6), b = at(st, 9, 7);
|
||||
assert.ok(a.mem.wangNode.act && b.mem.wangNode.act, 'connected ⇒ active');
|
||||
assert.equal(placed(h).length, 0, 'the battle-start deployment drops no 跟子');
|
||||
assert.deepEqual(placed(h), [[10, 6], [10, 7]], 'the battle start drops one 跟子 上 of each');
|
||||
const fl = h.spawn('enemy_fly', { pos: [9, 7] });
|
||||
h.step();
|
||||
assert.ok(!b.alive && a.alive, 'the flyer set off its 棋子, which is used up');
|
||||
@@ -142,11 +153,10 @@ test('铸子: a spent 棋子 comes back on its tile 2 s later for its cost (TRP-
|
||||
const dp0 = h.b.players[0].dp;
|
||||
assert.ok(h.runUntil(() => b.alive, 1), 'back');
|
||||
approx(h.b.players[0].dp, dp0 - (mod === X ? 2 : 3), `${mod ?? 'none'}: its cost`);
|
||||
// the 跟子: 上 of (10,7) is (11,7) — deployable ground; no enemy anywhere ⇒ undeployable first: (10,7)'s neighbours on
|
||||
// the flat stage are all deployable road but 右 (10,8) … the order 上 > 右 > 下 > 左 among equals
|
||||
const f = u.mem.wang.followers;
|
||||
assert.equal(f.length, 1, 'one 跟子');
|
||||
assert.deepEqual([f[0].r, f[0].c], [11, 7], '上 first among equal tiles');
|
||||
// the battle start dropped one 跟子 上 of each (equal road tiles: 上 > 右 > 下 > 左); the return of (10,7) finds its 上
|
||||
// (11,7) taken by its own and (10,6) by the other 棋子, so 右 (10,8)
|
||||
assert.deepEqual(placed(h), [[11, 6], [11, 7], [10, 8]], `${mod ?? 'none'}: 上 at the battle start, then 右`);
|
||||
assert.equal(u.mem.wang.followers.length, 3);
|
||||
done(h);
|
||||
}
|
||||
{ // an enemy tile first; undeployable ground before deployable ground
|
||||
@@ -156,23 +166,36 @@ test('铸子: a spent 棋子 comes back on its tile 2 s later for its cost (TRP-
|
||||
h.step();
|
||||
assert.ok(!b.alive);
|
||||
h.b.kill(h.b.enemies.find((x) => x.alive), null);
|
||||
// the battle start: (11,9) deploys first (a column top to bottom) — its 右 (11,10) is undeployable floor, before its
|
||||
// road 上; then (10,9) takes its floor 右 (10,10)
|
||||
assert.deepEqual(placed(h), [[11, 10], [10, 10]], '不可部署地块 > 可部署地面地块 (before 上)');
|
||||
const fl = h.spawn('enemy_fly', { pos: [9, 9] }); // 下 of (10,9) holds an enemy (a flyer: no block on the trap rule)
|
||||
assert.ok(h.runUntil(() => b.alive, 3), 'back (a flyer does not stop it)');
|
||||
assert.deepEqual(placed(h), [[9, 9]], 'the enemy tile first (before the undeployable 右 (10,10))');
|
||||
assert.deepEqual(placed(h)[2], [9, 9], 'the return: the enemy tile first');
|
||||
h.step();
|
||||
assert.ok(h.hooksOf('damaged').some((c) => c.target === fl && c.source === u && c.dmg.tags.includes('wang:stone')), 'the 跟子 sets off at once — its damage is 望\'s');
|
||||
assert.equal(u.mem.wang.followers.length, 0);
|
||||
assert.equal(u.mem.wang.followers.length, 2, 'the two on the floor stay; the one on the flyer is spent');
|
||||
done(h);
|
||||
}
|
||||
{ // no enemy around: undeployable ground (10,10) before the road
|
||||
{ // a return with no enemy around: undeployable ground (10,10) before the road
|
||||
const { h, u, st } = field({ tier: 6, elite: true, skill: 1, stones: [[10, 9], [11, 9]], dp: 50 });
|
||||
const b = at(st, 10, 9);
|
||||
const e = h.spawn('enemy_dummy', { pos: [10, 9] });
|
||||
h.step();
|
||||
h.b.kill(e, null);
|
||||
u.mem.wang.followers.length = 0; // (the battle start's 跟子 gone: the floor beside it is free again)
|
||||
const n0 = placed(h).length;
|
||||
assert.ok(h.runUntil(() => b.alive, 3));
|
||||
assert.deepEqual(placed(h), [[10, 10]], '不可部署地块 > 可部署地面地块');
|
||||
void u;
|
||||
assert.deepEqual(placed(h).slice(n0), [[10, 10]], '不可部署地块 > 可部署地面地块');
|
||||
done(h);
|
||||
}
|
||||
{ // a 跟子 on the tile of a piece still to come at the battle start goes when that piece deploys there ("跟子所在格进行部署…
|
||||
// 时,该跟子会随之消失"): (10,3)'s 上 holds an operator, so its 跟子 takes 右 (10,4) — the next 棋子's tile —, which
|
||||
// deploys there and drops its own 上 (11,4)
|
||||
const { h, u } = field({ tier: 6, elite: true, skill: 1, stones: [[10, 3], [10, 4]], others: [{ uid: 10, chessId: 'chess_char_1_01_a', row: 11, col: 3 }] });
|
||||
assert.deepEqual(placed(h), [[10, 4], [11, 4]]);
|
||||
assert.deepEqual(u.mem.wang.followers.map((f) => [f.r, f.c]), [[11, 4]], 'the one on (10,4) is gone');
|
||||
assert.ok(h.eventsOf('fx').some((e) => e[1] === 'disappear' && e[2] === 4 && e[3] === 10), 'it vanishes');
|
||||
done(h);
|
||||
}
|
||||
{ // 望 away ⇒ her 棋子 leave and wait for her; her 跟子 are gone
|
||||
@@ -306,13 +329,14 @@ test('S2 连星 (AUTO, SP_FULL): a triggered piece hits every enemy on its conne
|
||||
const { h, u } = field({ tier, elite, skill: 1, stones: [[10, 6], [10, 7]] });
|
||||
const onLine = h.spawn('enemy_fly', { pos: [10, 9] }); // 3 tiles right of (10,6)…(10,7)+2: on the horizontal line
|
||||
const beyond = h.spawn('enemy_dummy', { pos: [10, 11] }); // 4 tiles past (10,7)
|
||||
const off = h.spawn('enemy_dummy', { pos: [11, 7] }); // off the line (vertical never activated)
|
||||
const vertical = h.spawn('enemy_dummy', { pos: [12, 7] }); // its battle-start 跟子 (11,7) is 上 of it: the vertical line is on
|
||||
const off = h.spawn('enemy_dummy', { pos: [9, 4] }); // on neither line
|
||||
const trig = h.spawn('enemy_runner', { pos: [10, 7] });
|
||||
h.b.applyStatus(trig, 'stun', { duration: 99, force: true }); // it stays on the tile (the slow is read off its speed stat)
|
||||
h.step();
|
||||
const hit = stoneHits(h);
|
||||
const victims = new Set(hit.map((c) => c.target));
|
||||
assert.ok(victims.has(trig) && victims.has(onLine), `T${tier}: the line, air included`);
|
||||
assert.ok(victims.has(trig) && victims.has(onLine) && victims.has(vertical), `T${tier}: the lines, air included`);
|
||||
assert.ok(!victims.has(beyond) && !victims.has(off), `T${tier}: not beyond ± 3 / off the line`);
|
||||
for (const c of hit.filter((x) => x.target === trig)) approx(c.amount, u.s.atk * sk.bb['attack@atk_scale'] * 1.24, `T${tier}: 2 stacks`, 1e-4);
|
||||
const slow = trig.findBuff('wang:slow');
|
||||
@@ -359,8 +383,9 @@ test('S3 天下劫 (MANUAL, ACTIVE_RANGE 4-12, 20 bullets): passive — the piec
|
||||
assert.equal(u.skill.attackOverride()?.noAttack, true, `T${tier}: 停止攻击`);
|
||||
assert.equal(u.mem.wang.stock, 8, `T${tier}: +8, capped`);
|
||||
const fl = placed(h).map(([r, c]) => `${r},${c}`);
|
||||
assert.equal(fl.length, 5, `T${tier}: (7 − 2 placed) + 8 − 8 over the cap`);
|
||||
assert.deepEqual(fl.slice(0, 2).sort(), ['10,7', '9,5'], `T${tier}: the enemies' tiles first (${fl})`);
|
||||
assert.deepEqual(fl.slice(0, 2), ['12,6', '12,7'], `T${tier}: the battle start's 跟子, 上 of each (${fl})`);
|
||||
assert.equal(fl.length, 7, `T${tier}: then (7 − 2 placed) + 8 − 8 over the cap`);
|
||||
assert.deepEqual(fl.slice(2, 4).sort(), ['10,7', '9,5'], `T${tier}: the overflow on the enemies' tiles first (${fl})`);
|
||||
assert.ok(h.hooksOf('damaged').some((c) => c.target === a && c.source === u && c.dmg.tags.includes('wang:stone')), `T${tier}: they set off`);
|
||||
// inside her range a 棋子 comes back onto an enemy ("可部署至敌人所在位置") — and sets off again
|
||||
h.b.kill(a, null); h.b.kill(b, null);
|
||||
@@ -390,6 +415,18 @@ test('S3 天下劫 (MANUAL, ACTIVE_RANGE 4-12, 20 bullets): passive — the piec
|
||||
}
|
||||
});
|
||||
|
||||
test('S3 天下劫: the overflow 跟子 take her range by the 铸子 order (S3 备注 "采用相同的位置选择优先顺序") — enemy tiles first, an enemy on undeployable floor before one on the road her target order puts first, then the floor', () => {
|
||||
const { h, u } = field({ tier: 6, elite: true, skill: 2, row: 10, col: 6, stones: [[11, 4], [11, 5]], dp: 300 });
|
||||
assert.deepEqual(placed(h), [[12, 4], [12, 5]], 'the battle start');
|
||||
u.skill.gainSp(999);
|
||||
h.spawn('enemy_dummy', { pos: [10, 10] }); // on tile_floor
|
||||
h.spawn('enemy_dummy', { pos: [10, 8] }); // on the road, nearer the goal
|
||||
assert.ok(h.runUntil(() => u.skill.active, 2));
|
||||
const fl = placed(h).slice(2).map(([r, c]) => `${r},${c}`);
|
||||
assert.deepEqual(fl, ['10,10', '10,8', '9,10', '11,10', '12,10'], `(7 − 2) + 8 − 8 = 5: the enemies' tiles by class, then the undeployable tiles (${fl})`);
|
||||
done(h);
|
||||
});
|
||||
|
||||
test('the hand: deploying her in prep brings 7 棋子 (TRP-X "可同时部署的陷阱数量提升": 8) — the match\'s data view reads the module variant\'s deploy limit', () => {
|
||||
const gd = new GameData(getData({ log: { warn() {}, error() {}, info() {} } }), 'mode_multi_hard');
|
||||
const data = { chess: CHESS, backups: BACKUPS };
|
||||
|
||||
Reference in new issue
Block a user