feedback5: merge R19C — GitHub #235: the official 「作战结束」 result box (全员无伤! / 生命值减少 −N) and the three battle-over sounds at settlement, PR #112 by @Convey123 ported onto the 0.2.0 layout (test/ui/gameLogic.test.js: both streams' tests kept); golden unchanged
This commit is contained in:
commit
93a45f733f
21 files changed
+705
-14
No files matched your search
@@ -9,6 +9,7 @@ import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
phaseMode, phaseBanner, isCombatPhase, isBossPhase, countdownState, phaseTotalSeconds, sortBonds, bondTier, nextThreshold,
|
||||
battleOverSfx, ownRoundLoss, uniteResultBox, battleResultBox, roundResultBox, RESULT_BOX_MS,
|
||||
bondMembers, memberHeadCount, bannedPerBond, priceTone, mergeProgress, shopBlockReason, deploySets, indexPieces, placementContext, canPlace,
|
||||
boardTargets, dropIntent, normalizeDraft, normalizeSp, groupEnemies, factionTypes, snapHud, bossFrac, attackInterval, fmtNum,
|
||||
rangeGridBox, shortcutFor, sanitizeSettings, DEFAULT_SETTINGS, normalizeResult, cycleField, fieldLabel, homeFieldId,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
} from '../../public/js/ui/gameLogic.js';
|
||||
import { pairPlayers } from '../../server/match/finalAssault.js';
|
||||
import { PHASE, GEO } from '../../shared/constants.js';
|
||||
import { setLang, setMessages } from '../../shared/i18n.js';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const load = (f) => JSON.parse(readFileSync(path.join(ROOT, 'data', f), 'utf8'));
|
||||
@@ -95,6 +97,106 @@ describe('phases', () => {
|
||||
const src = readFileSync(path.join(ROOT, 'public/js/screens/game.js'), 'utf8');
|
||||
assert.match(src, /phaseBanner\(phase, pub, \{ alive, spectator \}\)/);
|
||||
});
|
||||
// GitHub #235 (PR #112 by @Convey123): the official round result dialog and its 战斗结束 sound at SETTLE
|
||||
test('战斗结束 sound: the official BATTLEOVER_* variant for the round', () => {
|
||||
// 掉血 → _REDUCE;联防里自己没被扣 → _NOREDUCE;普通回合没扣 → _NORMAL;本回合没打过(不在本回合 / 重连落在结算)→ 不响
|
||||
// `cost` is what screens/game.js roundLossRef keeps — { round, leaks, cap, unite }; it has no `pending` (a field the
|
||||
// object never carried is how the Reduce variant was once dead code, review on PR #112)
|
||||
const cost = (o) => ({ round: 3, leaks: 0, cap: 10, unite: false, ...o });
|
||||
assert.equal(battleOverSfx(cost({ leaks: 3 })), 'battleOverReduce');
|
||||
assert.equal(battleOverSfx(cost({ leaks: 1, unite: true })), 'battleOverReduce');
|
||||
assert.equal(battleOverSfx(cost({ unite: true })), 'battleOverNoReduce');
|
||||
assert.equal(battleOverSfx(cost({})), 'battleOverNormal');
|
||||
// the cap bounds it like settlement's charge (14 leaks with cap 10 is still a loss)
|
||||
assert.equal(battleOverSfx(cost({ leaks: 14, cap: 10 })), 'battleOverReduce');
|
||||
assert.equal(battleOverSfx(cost({ leaks: 14, cap: undefined })), 'battleOverReduce', 'no cap known → the official 10');
|
||||
// a 联防 round: the authority's charge decides, not the own battle's leaks — a leaker whose enemies the helpers
|
||||
// stopped paid 0 and hears NoReduce
|
||||
assert.equal(battleOverSfx(cost({ leaks: 3, unite: true }), 0), 'battleOverNoReduce');
|
||||
assert.equal(battleOverSfx(cost({ leaks: 3, unite: true }), 3), 'battleOverReduce');
|
||||
assert.equal(battleOverSfx(cost({ leaks: 0, unite: true }), 2), 'battleOverReduce');
|
||||
assert.equal(battleOverSfx(cost({ leaks: 2, unite: true }), null), 'battleOverReduce', 'no 联防 figure → the own battle decides');
|
||||
assert.equal(battleOverSfx(cost({ leaks: 2, unite: true }), undefined), 'battleOverReduce');
|
||||
// this round's battle never seen (a spectator seat, an eliminated player, a reconnect landing on SETTLE)
|
||||
assert.equal(battleOverSfx(null), null);
|
||||
assert.equal(battleOverSfx(undefined, 0), null);
|
||||
});
|
||||
test('ownRoundLoss: min(cap, leaks) — what settlement charges outside a 联防', () => {
|
||||
assert.equal(ownRoundLoss({ leaks: 3, cap: 10 }), 3);
|
||||
assert.equal(ownRoundLoss({ leaks: 14, cap: 10 }), 10);
|
||||
assert.equal(ownRoundLoss({ leaks: 14 }), 10);
|
||||
assert.equal(ownRoundLoss({ leaks: 2.9, cap: 10 }), 2);
|
||||
assert.equal(ownRoundLoss({ leaks: 0 }), 0);
|
||||
assert.equal(ownRoundLoss(null), null);
|
||||
assert.equal(ownRoundLoss({ cap: 10 }), null, 'no leaks seen → no figure at all');
|
||||
});
|
||||
test('round result box: the official words — 作战结束 + 全员无伤! (mint) / 生命值减少 −N (red)', () => {
|
||||
assert.equal(RESULT_BOX_MS, 2800, 'inside SETTLE\'s 3 s (server DELAYS.SETTLE)');
|
||||
assert.deepEqual(roundResultBox(0), { title: '作战结束', micro: 'BATTLE OVER', tone: 'mint', sub: '全员无伤!', duration: RESULT_BOX_MS });
|
||||
assert.deepEqual(roundResultBox(4), { title: '作战结束', micro: 'BATTLE OVER', tone: 'red', sub: '生命值减少 −4', duration: RESULT_BOX_MS });
|
||||
assert.equal(roundResultBox(undefined).sub, '全员无伤!', 'an unknown loss reads as no loss');
|
||||
assert.equal(roundResultBox(-2).sub, '全员无伤!');
|
||||
assert.equal(roundResultBox(2.7).sub, '生命值减少 −2');
|
||||
// through t(): the English pack (public/i18n/en.json — no official EN wording in the data, a plain translation)
|
||||
try {
|
||||
setMessages('en', JSON.parse(readFileSync(path.join(ROOT, 'public/i18n/en.json'), 'utf8')));
|
||||
setLang('en');
|
||||
assert.deepEqual([roundResultBox(0).title, roundResultBox(0).sub, roundResultBox(3).sub], ['Battle over', 'All unharmed!', 'LP reduced by 3']);
|
||||
} finally { setLang('zh'); setMessages('en', {}); }
|
||||
});
|
||||
test('联防 result box: the viewer\'s own charge from the authority, the official words only when true', () => {
|
||||
const res = { through: 3, helpers: ['p_1', 'p_2'], leakers: ['p_0'], losses: { p_0: 3, p_1: 0, p_2: 0 } };
|
||||
// the leaker reads the LP settlement actually charged — word for word an ordinary round's box
|
||||
assert.deepEqual(uniteResultBox(res, 'p_0'), roundResultBox(3));
|
||||
assert.equal(uniteResultBox(res, 'p_0').sub, '生命值减少 −3');
|
||||
// a helper was spared, but the 联防 leaked and a teammate paid: 全员无伤! would claim that teammate was unharmed, and
|
||||
// the official dialog has no other line (the remake's 「联防失败:还有 N 只突破防线」 was dropped in the owner's review
|
||||
// of PR #112) — the official title alone, orange (player report 2026-10-06 on PR #112)
|
||||
const helper = uniteResultBox(res, 'p_1');
|
||||
assert.equal(helper.title, '作战结束');
|
||||
assert.equal(helper.tone, 'orange');
|
||||
assert.equal(helper.sub, '', 'no 全员无伤! while a teammate was charged');
|
||||
assert.ok(!/联防成功|联防失败|突破防线/.test(JSON.stringify(helper)), 'no 联防 verdict line');
|
||||
// nothing got through: nobody paid, so 全员无伤! is true for leaker and helper alike — the only 联防 case that says it
|
||||
const cleared = { through: 0, helpers: ['p_1', 'p_2'], leakers: ['p_0'], losses: { p_0: 0, p_1: 0, p_2: 0 } };
|
||||
assert.deepEqual(uniteResultBox(cleared, 'p_0'), roundResultBox(0));
|
||||
assert.deepEqual(uniteResultBox(cleared, 'p_1'), roundResultBox(0));
|
||||
// two leakers, one spared by the 联防, one charged: only the charged one reads 生命值减少
|
||||
const spared = { through: 3, helpers: ['p_1'], leakers: ['p_0', 'p_2'], losses: { p_0: 0, p_2: 2, p_1: 0 } };
|
||||
assert.equal(uniteResultBox(spared, 'p_0').sub, '', 'spared leaker, but the 联防 leaked: no claim');
|
||||
assert.equal(uniteResultBox(spared, 'p_2').sub, '生命值减少 −2');
|
||||
assert.equal(uniteResultBox(spared, 'p_1').sub, '', 'the helper, whose teammate paid');
|
||||
// not in the round — a spectator seat, a player eliminated before it (settle lists alive players only): no box,
|
||||
// as in a round without 联防 where a viewer without a battle gets none (and no sound) [ASSUMED]
|
||||
assert.equal(uniteResultBox(res, 'nobody'), null);
|
||||
assert.equal(uniteResultBox(cleared, 'nobody'), null);
|
||||
assert.equal(uniteResultBox(res, undefined), null);
|
||||
assert.equal(uniteResultBox(res, null), null);
|
||||
// no 联防 this round: no box from here (the own battle's takes over)
|
||||
assert.equal(uniteResultBox(null, 'p_0'), null);
|
||||
assert.equal(uniteResultBox(undefined, 'p_0'), null);
|
||||
assert.equal(uniteResultBox({}, 'p_0'), null);
|
||||
assert.equal(uniteResultBox({ through: -1 }, 'p_0'), null);
|
||||
// a view without `losses` makes no LP claim — and never falls back to the own battle's leaks, which a 联防 removed
|
||||
const noLosses = uniteResultBox({ through: 3, helpers: ['p_1'], leakers: ['p_0'] }, 'p_0');
|
||||
assert.equal(noLosses.title, '作战结束');
|
||||
assert.equal(noLosses.sub, '', 'no LP known → no LP claim');
|
||||
assert.equal(noLosses.tone, 'orange', 'no LP known → not read as a loss');
|
||||
assert.equal(uniteResultBox({ through: 0, helpers: ['p_1'], leakers: ['p_0'] }, 'p_1').sub, '');
|
||||
});
|
||||
test('作战 result box (no 联防): 全员无伤! without a leak, min(cap, leaks) otherwise, nothing without a battle', () => {
|
||||
assert.deepEqual(battleResultBox({ leaks: 0, cap: 10 }), roundResultBox(0));
|
||||
const bad = battleResultBox({ leaks: 3, cap: 10 });
|
||||
assert.equal(bad.title, '作战结束');
|
||||
assert.equal(bad.tone, 'red');
|
||||
assert.equal(bad.sub, '生命值减少 −3');
|
||||
// the per-round LP cap (config.lpCapPerRound): the loss shown is min(cap, leaks), never the leaked count
|
||||
assert.equal(battleResultBox({ leaks: 14, cap: 10 }).sub, '生命值减少 −10');
|
||||
assert.equal(battleResultBox({ leaks: 14 }).sub, '生命值减少 −10', 'no cap known → the official 10');
|
||||
assert.equal(battleResultBox({ leaks: 2, cap: 5 }).sub, '生命值减少 −2');
|
||||
assert.equal(battleResultBox(null), null);
|
||||
assert.equal(battleResultBox({}), null, 'no battle seen this round (a spectator, a reconnect) → no box');
|
||||
});
|
||||
});
|
||||
|
||||
describe('countdown', () => {
|
||||
|
||||
Reference in new issue
Block a user