feedback5 R2: Match.js → server/match/match/*.js, a pure move — the 167 class members (166 methods and the soloUntimed getter), verbatim with their comments, go to 16 per-concern method containers (platform, infra, messaging, views, watch, intents, pause, phases, spDraft, prep, combat, clientCombat, reports, unitePhase, bossRounds, settle) that Match.js installs on Match.prototype in a fixed order with class-method descriptors (a name defined twice throws); the constants the modules share go to match/common.js (FLOW_TICKER_PRIORITY, DELAYS, BAND_TURN_SECONDS re-exported by Match.js; OK, fail, BOSS_CLOCK_MS), GAME_TYPES to platform.js, the boss-round budgets / rates to bossRounds.js; Match.js keeps the header (+ a code-layout paragraph), the constructor-only constants and the class with its constructor. Section-header comments are replaced by the module headers. docs-consistency reads the method modules (matchText) and pins layerGainRoom in match/settle.js
This commit is contained in:
1 parent
8ab6d021da
commit
1ca4936f0a
19 files changed
+3105
-2923
No files matched your search
@@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
// Installed on Match.prototype by server/match/Match.js (a method container: never instantiated; `this` is the match).
|
||||
|
||||
import { DELAYS } from './common.js';
|
||||
|
||||
export class MatchMessaging {
|
||||
sendTo(playerId, msg) {
|
||||
if (this.disposed) return false;
|
||||
const ps = this.players.get(playerId) || this.spectators.get(playerId);
|
||||
if (!ps || ps.isBot || ps.left) return false;
|
||||
try { return !!this.sendFn(playerId, msg); } catch (e) { this.reportError('send', e); return false; }
|
||||
}
|
||||
|
||||
broadcast(msg) {
|
||||
if (this.disposed) return;
|
||||
try { this.broadcastFn(msg); } catch (e) { this.reportError('broadcast', e); }
|
||||
}
|
||||
|
||||
toast(ps, kind, text) {
|
||||
if (!ps || ps.isBot || ps.left || !ps.connected) return;
|
||||
this.sendTo(ps.playerId, { t: 'm.toast', kind, text });
|
||||
}
|
||||
|
||||
/** Broadcast ticker from config.broadcasts by type; `param` picks the variant (SHOP_LEVEL level, BOSS_HIT share…). */
|
||||
tickerFor(type, args = [], { playerId = null, to = null, param = null } = {}) {
|
||||
const list = Array.isArray(this.gd.config.broadcasts) ? this.gd.config.broadcasts.filter((b) => b && b.type === type) : [];
|
||||
let b = list[0];
|
||||
if (param != null) b = list.find((x) => Array.isArray(x.params) && x.params.includes(String(param))) || b;
|
||||
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 };
|
||||
if (to) this.sendTo(to, msg);
|
||||
else this.broadcast(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* A ticker line of the remake's own (type CUSTOM). `priority`: the match-flow notices (隐秘核心已解锁, 联防阶段, a player out
|
||||
* or gone) take FLOW_TICKER_PRIORITY so the strip does not hold them behind shop-level lines; other lines 0.
|
||||
*/
|
||||
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 });
|
||||
}
|
||||
|
||||
markPublic() { this._pubDirty = true; }
|
||||
/** A player's state changed: its m.private and (throttled, deduplicated) m.public (level, board, bonds…). */
|
||||
markPrivate(ps) { if (ps) { this._privDirty.add(ps); this._pubDirty = true; } }
|
||||
|
||||
/** Send pending m.private (per player, only when changed) and m.public (throttled ≤ 10/s). */
|
||||
flush(forcePublic = false) {
|
||||
if (this.disposed) return;
|
||||
if (this._privDirty.size) {
|
||||
const list = [...this._privDirty];
|
||||
this._privDirty.clear();
|
||||
for (const ps of list) {
|
||||
if (!(ps.isBot || ps.left || !ps.connected)) this._sendPrivate(ps, false);
|
||||
this._notifyPrepScouts(ps);
|
||||
}
|
||||
}
|
||||
if (this._pubDirty || forcePublic) this._maybeSendPublic(forcePublic);
|
||||
}
|
||||
|
||||
_sendPrivate(ps, force) {
|
||||
let view;
|
||||
try { view = ps.privateView(); } catch (e) { this.reportError('privateView', e); return; }
|
||||
const json = JSON.stringify(view);
|
||||
if (!force && json === ps._lastPriv) return;
|
||||
ps._lastPriv = json;
|
||||
this.sendTo(ps.playerId, view);
|
||||
}
|
||||
|
||||
_maybeSendPublic(force) {
|
||||
const now = this.sched.now();
|
||||
if (!force && now - this._lastPubAt < DELAYS.PUBLIC_THROTTLE) {
|
||||
if (!this._pubTimer) {
|
||||
const wait = Math.max(1, DELAYS.PUBLIC_THROTTLE - (now - this._lastPubAt));
|
||||
this._pubTimer = this.sched.setTimeout(() => {
|
||||
this._pubTimer = null;
|
||||
if (this.disposed) return;
|
||||
try { this._maybeSendPublic(false); } catch (e) { this.reportError('public', e); }
|
||||
}, wait);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._pubDirty = false;
|
||||
let view;
|
||||
try { view = this.publicView(); } catch (e) { this.reportError('publicView', e); return; }
|
||||
const { serverNow, ...rest } = view;
|
||||
const json = JSON.stringify(rest);
|
||||
if (!force && json === this._lastPubJson) return;
|
||||
this._lastPubJson = json;
|
||||
this._lastPubAt = now;
|
||||
this.broadcast(view);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user