diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5b9f3d..7afbc4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ - 被淘汰后观战(以及观战席)会一直跟着上次查看的玩家,每回合开始不再跳回自己的空棋盘;没查看过时跟随第一位仍在场的玩家,人机也算(社区反馈;思路来自 PR #189,感谢 @2321Robin)。 - 观战领袖回合的休整期时,看到的是领袖战场和站在那里的领袖,不再是没有领袖的普通棋盘(社区反馈)。 - 最终攻势和隐秘核心的休整期,像作战时一样把同组两名玩家放在同一个战场上(队友的一半只能看),从回合开始到作战结束,左侧队伍栏用官方的绿框标出同组两人(社区反馈;参考了 PR #191,感谢 @2321Robin)。 +- 被淘汰后(以及观战席)每回合开始的横幅改为显示「观战中」,不再显示「资金已到账」:回合资金只发给仍在场的玩家(GitHub #236;思路来自 PR #237,感谢 @2321Robin)。 ### 人机 diff --git a/docs/history/0.2.0.md b/docs/history/0.2.0.md index 58e95a4b..0db943f6 100644 --- a/docs/history/0.2.0.md +++ b/docs/history/0.2.0.md @@ -1856,3 +1856,20 @@ are. intents too (+3 layers, ATK, block). Two NORMAL copies of one item merge (the official rule), so two 空弦 need a normal and an 进阶 set; and the pair counts once toward the 3 members. - Pinned: `test/content/feedback5-cb1-pins.test.js`. + +#### 25.22.2 GitHub #236 「被淘汰后每回合开始横幅仍显示「资金已到账」(观战席同理)」 (@2321Robin, with PR #237): the round-start banner reads 观战中 to an eliminated player and a spectator seat — `ui/gameLogic/phases.js phaseBanner`, `screens/game.js` + +- **Seen** (a real Match with fake battles: three humans and a spectator seat, p_0 out of LP at round 1's settlement): + at the round-2 start p_0 (funds 0, pending 0) and the spectator seat (no seat, no funds) got 「第 2 回合 · 资金已到账」, + the same as p_1 (funds 5). The sub had been fixed text since 0.1.0. +- **Official**: no rule for the banner's text. The money: round income goes only to the players still in. + `startRound` runs `ps.startRound` (income + pending coins) for `alivePlayers()` only, and `eliminate()` zeroes + `funds` / `pendingFunds` (server/match/match/phases.js, player/round.js). An eliminated player keeps watching + (research 09: KEEP_WATCH). +- **Now**: `phaseBanner(phase, pub, { alive, spectator })`. The match screen passes its `alive` and `spectator`. + `alive` is false for an eliminated player and for a spectator seat. A spectator seat has no row in m.public, so `pub` + alone cannot tell. For those viewers ROUND_START's sub is 「观战中」 (en "Spectating", the combat HUD's spectator + caption). A player still in keeps 「资金已到账」. The title and the other phases do not change. PR #237 was read for the + idea only: its fourth `alive` argument sits on top of the #189 / #191 stack and Match.js changes, so it was not merged. +- Golden: unchanged (client text only). Tests: `test/ui/gameLogic.test.js` +1 (a player still in, an eliminated + player, a spectator seat, the screen's call). It fails on the parent: 「资金已到账」 where 「观战中」 is expected. diff --git a/public/js/screens/game.js b/public/js/screens/game.js index 3065e4ef..520493f0 100644 --- a/public/js/screens/game.js +++ b/public/js/screens/game.js @@ -576,7 +576,7 @@ function MatchScreen() { const prev = prevPhase.current; prevPhase.current = phase; if (prev === phase) return; - const b = phaseBanner(phase, pub); + const b = phaseBanner(phase, pub, { alive, spectator }); if (b) setBanner({ ...b, key: phaseKey }); if (phase === PHASE.ROUND_START) audio.sfx('roundStart'); else if (phase === PHASE.PREP) audio.sfx('rest', { volume: 0.7 }); diff --git a/public/js/ui/gameLogic/phases.js b/public/js/ui/gameLogic/phases.js index f7afe13d..ddedfaab 100644 --- a/public/js/ui/gameLogic/phases.js +++ b/public/js/ui/gameLogic/phases.js @@ -40,12 +40,21 @@ export const isCombatPhase = (phase) => COMBAT_PHASES.has(phase); export const showDeadPill = (alive, phase) => !alive && !COMBAT_PHASES.has(phase) && phase !== PHASE.SETTLE; export const isBossPhase = (phase) => phase === PHASE.FINAL_ASSAULT || phase === PHASE.HIDDEN_CORE; -/** Banner shown when a phase starts: { title, sub?, tone } or null. */ -export function phaseBanner(phase, pub) { +/** + * Banner shown when a phase starts: { title, sub?, tone } or null. + * @param {string|null|undefined} phase + * @param {any} pub m.public + * @param {{ alive?: boolean, spectator?: boolean }} [viewer] the local viewer (screens/game.js): `alive` false for an + * eliminated player, `spectator` for a spectator seat — it has no row in m.public, so `pub` cannot tell + */ +export function phaseBanner(phase, pub, { alive = true, spectator = false } = {}) { const r = int(pub?.round, 0); switch (phase) { case PHASE.BATTLE_CHECK: return { title: t('协议启动'), micro: 'PROTOCOL START', tone: 'mint', sub: t('模拟即将开始'), duration: 2600 }; - case PHASE.ROUND_START: return { title: t('第 {r} 回合', { r }), micro: `ROUND ${String(r).padStart(2, '0')}`, tone: 'mint', sub: t('资金已到账') }; + // round income goes to the seats still in only (server/match/match/phases.js startRound; eliminate() zeroes funds + // and pendingFunds): an eliminated player or a spectator seat reads 观战中, never 资金已到账 (GitHub #236, PR #237) + case PHASE.ROUND_START: return { title: t('第 {r} 回合', { r }), micro: `ROUND ${String(r).padStart(2, '0')}`, tone: 'mint', + sub: spectator || alive === false ? t('观战中') : t('资金已到账') }; case PHASE.SP_DRAFT: return { title: t('机变阶段'), micro: 'CONTINGENCY', tone: 'gold', sub: t('依次选择机变') }; case PHASE.PREP: return { title: t('休整期'), micro: `ROUND ${String(r).padStart(2, '0')} // REST`, tone: 'mint', sub: t('部署干员,准备迎敌') }; case PHASE.COMBAT: return { title: t('作战开始'), micro: 'COMBAT', tone: 'orange', sub: t('各自行动阶段') }; diff --git a/test/ui/gameLogic.test.js b/test/ui/gameLogic.test.js index 3d65c8ad..997ace61 100644 --- a/test/ui/gameLogic.test.js +++ b/test/ui/gameLogic.test.js @@ -72,6 +72,29 @@ describe('phases', () => { assert.equal(phaseBanner(PHASE.SETTLE, {}), null); assert.equal(prepCapsuleLabel(PHASE.PREP), '休息一下'); }); + test('the round-start banner says 资金已到账 to a player still in only — 观战中 to an eliminated player and a spectator seat (GitHub #236)', () => { + // round 2 after p_0 ran out of LP: p_1 is still in; the spectator seat s_spec has no row in m.public + const pub = { round: 2, phase: PHASE.ROUND_START, players: [ + { playerId: 'p_0', seat: 0, alive: false }, { playerId: 'p_1', seat: 1, alive: true }, + ] }; + const round = (viewer) => phaseBanner(PHASE.ROUND_START, pub, viewer); + // a player still in (the server paid the round's income) + assert.equal(round({ alive: true, spectator: false }).sub, '资金已到账'); + assert.equal(round().sub, '资金已到账', 'no viewer given: the copy of a player still in'); + // an eliminated player (eliminate() zeroed funds and pendingFunds; startRound pays the seats still in only) + assert.equal(round({ alive: false, spectator: false }).sub, '观战中'); + // a spectator seat (no seat, no funds), also when only `spectator` is given + assert.equal(round({ alive: false, spectator: true }).sub, '观战中'); + assert.equal(round({ spectator: true }).sub, '观战中'); + // the title and the other phases' copy are the same for every viewer + for (const v of [{ alive: true }, { alive: false }, { alive: false, spectator: true }]) { + assert.equal(round(v).title, '第 2 回合'); + assert.equal(phaseBanner(PHASE.PREP, pub, v).sub, phaseBanner(PHASE.PREP, pub).sub); + } + // the match screen hands the banner its viewer (screens/game.js `alive` folds a spectator seat in) + const src = readFileSync(path.join(ROOT, 'public/js/screens/game.js'), 'utf8'); + assert.match(src, /phaseBanner\(phase, pub, \{ alive, spectator \}\)/); + }); }); describe('countdown', () => {