feedback5 WI: server toasts and ticker lines carry msgid + params so the client shows them in the player's language
messaging.js: a toast / CUSTOM ticker line is a string (its own msgid) or a shared/i18n.js msg(); frames keep
the Chinese `text` (older clients) and add `msgid` / `params`; config.broadcasts lines carry their `args`
(the client fills the localized broadcast template; the player name {0} is never translated). Templated call
sites send msg(): the grant toast 「X:获得Y」, the summon-range and terrain withdrawals, the 联防 line,
eliminated / left lines, the 远见 / 奇迹 stack payouts and the 维多利亚 hammers; game-data names go as dn().
The protocol stays backward compatible (new fields optional). Tests that captured m.toast's argument render
it with renderMessage. No gameplay change: golden unchanged.
This commit is contained in:
1 parent
a12a539e4d
commit
5398a0cfc5
12 files changed
+166
-18
No files matched your search
@@ -1,9 +1,13 @@
|
||||
// server/match/match/messaging.js — Match methods: messaging — unicast, broadcast, toasts, the ticker lines
|
||||
// (config.broadcasts by type; the remake's CUSTOM lines), the dirty marks and flush: m.private per player only when it
|
||||
// changed (plus the prep scouts of that player's board), m.public throttled (DELAYS.PUBLIC_THROTTLE) and deduplicated.
|
||||
// i18n (docs/I18N.md): a toast / CUSTOM line is a string (its own msgid) or a shared/i18n.js msg(msgid, params); the frame
|
||||
// carries `text` (the Chinese rendering, what older clients show) plus `msgid` / `params`; a broadcast line carries its
|
||||
// `args` so the client can fill the localized config.broadcasts template.
|
||||
// Installed on Match.prototype by server/match/Match.js (a method container: never instantiated; `this` is the match).
|
||||
|
||||
import { DELAYS } from './common.js';
|
||||
import { wireMessage } from '../../../shared/i18n.js';
|
||||
|
||||
export class MatchMessaging {
|
||||
sendTo(playerId, msg) {
|
||||
@@ -18,9 +22,10 @@ export class MatchMessaging {
|
||||
try { this.broadcastFn(msg); } catch (e) { this.reportError('broadcast', e); }
|
||||
}
|
||||
|
||||
/** @param {string|{ msgid: string, params?: object }} text a string or msg() (shared/i18n.js) */
|
||||
toast(ps, kind, text) {
|
||||
if (!ps || ps.isBot || ps.left || !ps.connected) return;
|
||||
this.sendTo(ps.playerId, { t: 'm.toast', kind, text });
|
||||
this.sendTo(ps.playerId, { t: 'm.toast', kind, ...wireMessage(text) });
|
||||
}
|
||||
|
||||
/** Broadcast ticker from config.broadcasts by type; `param` picks the variant (SHOP_LEVEL level, BOSS_HIT share…). */
|
||||
@@ -31,7 +36,7 @@ export class MatchMessaging {
|
||||
const tpl = b && typeof b.text === 'string' ? b.text : null;
|
||||
if (!tpl) return;
|
||||
const text = tpl.replace(/\{(\d)\}/g, (_, i) => (args[Number(i)] != null ? String(args[Number(i)]) : ''));
|
||||
const msg = { t: 'm.ticker', text, id: b.id, type, priority: Number(b.priority) || 0, playerId };
|
||||
const msg = { t: 'm.ticker', text, id: b.id, type, priority: Number(b.priority) || 0, playerId, args: args.map((a) => String(a ?? '')) };
|
||||
if (to) this.sendTo(to, msg);
|
||||
else this.broadcast(msg);
|
||||
}
|
||||
@@ -42,7 +47,8 @@ export class MatchMessaging {
|
||||
*/
|
||||
tickerText(text, priority = 0) {
|
||||
if (!text) return;
|
||||
this.broadcast({ t: 'm.ticker', text: String(text).slice(0, 200), id: null, type: 'CUSTOM', priority: Number(priority) || 0, playerId: null });
|
||||
const w = wireMessage(text);
|
||||
this.broadcast({ t: 'm.ticker', ...w, text: w.text.slice(0, 200), id: null, type: 'CUSTOM', priority: Number(priority) || 0, playerId: null });
|
||||
}
|
||||
|
||||
markPublic() { this._pubDirty = true; }
|
||||
|
||||
@@ -8,6 +8,7 @@ import { C2S } from '../../../shared/protocol.js';
|
||||
import { PHASE, ERR } from '../../../shared/constants.js';
|
||||
import { syntheticResult } from '../fields.js';
|
||||
import { FLOW_TICKER_PRIORITY, OK, fail } from './common.js';
|
||||
import { msg } from '../../../shared/i18n.js';
|
||||
|
||||
const GAME_TYPES = new Set(Object.keys(C2S).filter((t) => Object.hasOwn(C2S, t) && (t.startsWith('g.') || t.startsWith('b.'))));
|
||||
|
||||
@@ -171,7 +172,7 @@ export class MatchPlatform {
|
||||
}
|
||||
ps.lp = 0;
|
||||
ps.eliminate(passedRound);
|
||||
this.tickerText(`${ps.name}博士中途退出了模拟`, FLOW_TICKER_PRIORITY);
|
||||
this.tickerText(msg('{name}博士中途退出了模拟', { name: ps.name }), FLOW_TICKER_PRIORITY);
|
||||
if (this.bossWaves && (phase === PHASE.ROUND_START || phase === PHASE.SP_DRAFT || phase === PHASE.PREP)) {
|
||||
// before the boss fight: pair the players left again (the prep preview shows the new partner / template); a
|
||||
// player moved to the other half re-checks its board there at once (recompute → deployMap, marks it private)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { PHASE, layerGainRoom } from '../../../shared/constants.js';
|
||||
import { uniteSurvivors } from '../unite.js';
|
||||
import { buildResult } from '../results.js';
|
||||
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
|
||||
import { msg } from '../../../shared/i18n.js';
|
||||
|
||||
export class MatchSettle {
|
||||
settle(plan, uniteResult) {
|
||||
@@ -63,7 +64,7 @@ export class MatchSettle {
|
||||
ps.lp = 0;
|
||||
ps.eliminate(this.round);
|
||||
this.toast(ps, 'error', '你的目标生命值耗尽,已被淘汰');
|
||||
this.tickerText(`${ps.name}博士的目标生命值已耗尽`, FLOW_TICKER_PRIORITY);
|
||||
this.tickerText(msg('{name}博士的目标生命值已耗尽', { name: ps.name }), FLOW_TICKER_PRIORITY);
|
||||
}
|
||||
}
|
||||
this.fields = [];
|
||||
|
||||
@@ -9,6 +9,7 @@ import { uniteBattleOpts, uniteSurvivors, uniteStageId } from '../unite.js';
|
||||
import { FieldRunner, timelineAt, uniteBillBounds } from '../fields.js';
|
||||
import { uniteLeft } from '../../sim/spec.js';
|
||||
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
|
||||
import { msg } from '../../../shared/i18n.js';
|
||||
|
||||
export class MatchUnite {
|
||||
startUnite(plan) {
|
||||
@@ -21,7 +22,7 @@ export class MatchUnite {
|
||||
this.deadline = this.sched.instant ? 0 : this.sched.now() + Math.round((limit / this.gameSpeed) * 1000);
|
||||
this._defaultWatch();
|
||||
this.markPublic();
|
||||
this.tickerText(`联防阶段:${plan.helpers.map((p) => p.name).join('、')} 迎战突破防线的敌人`, FLOW_TICKER_PRIORITY);
|
||||
this.tickerText(msg('联防阶段:{names} 迎战突破防线的敌人', { names: plan.helpers.map((p) => p.name) }), FLOW_TICKER_PRIORITY);
|
||||
this._uniteLeftKey = null;
|
||||
this.runner = new FieldRunner(this, this.fields, {
|
||||
onTick: (runner) => this._uniteTick(runner),
|
||||
@@ -79,7 +80,7 @@ export class MatchUnite {
|
||||
this._sendStart(ps.playerId, f, { watch: !f.players.includes(ps.playerId) });
|
||||
}
|
||||
this.markPublic();
|
||||
this.tickerText(`联防阶段:${plan.helpers.map((p) => p.name).join('、')} 迎战突破防线的敌人`, FLOW_TICKER_PRIORITY);
|
||||
this.tickerText(msg('联防阶段:{names} 迎战突破防线的敌人', { names: plan.helpers.map((p) => p.name) }), FLOW_TICKER_PRIORITY);
|
||||
}
|
||||
|
||||
_finishUniteClient() {
|
||||
|
||||
Reference in new issue
Block a user