feedback3 WJ: toast when a bond grants a chess, and flash the new card

歌蕾蒂娅:获得X, and the same line for 余 and every other silent grantChess. A new hand or temp card uses the existing deploy flash.
This commit is contained in:
sganggs committed 2026-10-04 20:41:46 +08:00
1 parent 2723a3daea
commit 5e4fe8ae60
3 files changed
+45 -1

No files matched your search

+2 -1
View File
@@ -12,7 +12,7 @@
// (left_prepare / *_boss_prepare) clear of the folded shop's band (public
// issue #5: folding the shop did not grow the board)
// view.setPrep(privateState, { editable, canPlace }) hand/temp/board pieces; editable enables drag & drop; a
// new board piece flashes (fx.deploy); a merge's elite — on the tile of
// new board piece flashes (fx.deploy), and so does a card that just arrived in the hand or temp; a merge's elite — on the tile of
// the deployed copy it replaced, or on its bench slot — gets the
// promotion cue instead (render/promote.js, fx.promote)
// view.enterBattle(fieldMeta) m.field { fieldId, kind, rect, stageId, units: [UnitInfo] } — each
@@ -975,6 +975,7 @@ export async function createFieldView(host, options = {}) {
promotions.push({ uid: e.uid, id: e.piece.id, area: e.area, row: e.row ?? null, col: e.col ?? null, idx: e.idx ?? null, copies: promoFrom.get(e.uid).length });
if (promotions.length > 20) promotions.shift();
} else if (e.area === 'board' && prevBoard.size && !prevBoard.has(e.uid)) { v.onDeploy?.(); fx.deploy(v); }
else if (before.length && (e.area === 'hand' || e.area === 'temp') && !before.some((g) => g.uid === e.uid)) fx.deploy(v);
} else {
const prevHome = v._home;
const moved = !prevHome || prevHome.x !== w.x || prevHome.y !== w.y || prevHome.z !== w.z;
+15
View File
@@ -406,6 +406,11 @@ export function offerLabel(gd, source) {
return null;
}
/** Who a grantChess toast names: the same speaker as an offer label, or the choice card's name. */
function grantSpeaker(gd, source) {
return offerLabel(gd, source) || (source && source.kind === 'choice' && source.card && source.card.name) || '';
}
/**
* @param {import('./Match.js').Match} m
* @param {import('./PlayerState.js').PlayerState} ps
@@ -507,6 +512,16 @@ export function makeCtx(m, ps, source, hook, ev = null) {
// "some effects fail when the cap is hit" (research 06 §7): by default a chess of the pool needs a free copy
if (opts.requirePool !== false && m.pool.has(base) && m.pool.left(base) < 1) return null;
const p = ps.acquireChess(id, { source: opts.source || source.key || 'effect', toTemp: !!opts.toTemp, fromPool: opts.fromPool !== false });
// 「歌蕾蒂娅:获得斯卡蒂」 — every silent grantChess (a 特质, 余 SERVER_MOST_BOND, a band, an item, a choice).
// opts.toast === false skips it. A caller that already says the same thing should pass that.
if (p && opts.toast !== false) {
const got = gd.chess(p.id);
const name = got && got.name;
if (name) {
const who = grantSpeaker(gd, source);
m.toast(ps, 'info', who ? `${who}:获得${name}` : `获得${name}`);
}
}
return p ? view(p) : null;
},
grantItem: (itemId, opts = {}) => {
+28
View File
@@ -696,6 +696,34 @@ test('pools: 凯瑟琳 127 odd rounds, 佩佩 94, 洛洛 91, 焰尾 149, 歌蕾
}
});
test('歌蕾蒂娅 / 余: granting a chess toasts 「名字:获得X」', () => {
const glady = setup(11);
const row = plain().slice(0, 2);
give(glady.m, glady.ps, 'chess_char_4_12_a', 'board', [10, 4]);
give(glady.m, glady.ps, row[0], 'board', [10, 6]);
give(glady.m, glady.ps, row[1], 'board', [10, 8]);
const n0 = glady.h.allTo('p_0', 'm.toast').length;
glady.roundStart();
const lines = glady.h.allTo('p_0', 'm.toast').slice(n0).map((t) => t.text);
const line = lines.find((t) => t.startsWith('歌蕾蒂娅:获得'));
assert.ok(line, lines.join(' | '));
const gained = line.slice('歌蕾蒂娅:获得'.length);
assert.ok(handChess(glady.ps).some((id) => CH(id).name === gained), `${line} not in ${handChess(glady.ps)}`);
const yu = setup(12);
give(yu.m, yu.ps, 'chess_char_6_03_a', 'board', [10, 4]);
yu.ps.bondCountBonus.egirShip = 9;
yu.ps.recompute();
const fillers = plain((c) => !c.bonds.includes('egirShip')).slice(0, 2);
give(yu.m, yu.ps, fillers[0], 'board', [10, 6]);
give(yu.m, yu.ps, fillers[1], 'board', [10, 8]);
const n1 = yu.h.allTo('p_0', 'm.toast').length;
yu.roundStart();
const yuLine = yu.h.allTo('p_0', 'm.toast').slice(n1).map((t) => t.text).find((t) => t.startsWith('余:获得'));
assert.ok(yuLine, yu.h.allTo('p_0', 'm.toast').slice(n1).map((t) => t.text).join(' | '));
const yuName = yuLine.slice('余:获得'.length);
assert.ok(handChess(yu.ps).some((id) => CH(id).name === yuName), yuLine);
});
test('余 37: a chess of the bond with the most members (normal: 3 in the row; 精锐: always)', () => {
for (const { gid, g, owners } of idsOf('SERVER_PREP_START', 'SERVER_MOST_BOND')) {
for (const owner of owners) {