feedback5 R19C: the official 「作战结束」 result box and the three battle-over sounds at settlement (GitHub #235; PR #112 by @Convey123, approved and never merged, ported by hand onto the 0.2.0 modules) — the box says only the viewer's own LP, 「全员无伤!」 or 「生命值减少 −N」; after a 联防 the number is the server's own per-player charge (m.public.uniteResult in the SETTLE view) and 「全员无伤!」 only when nothing got through; no box for spectator seats, eliminated players or the boss rounds; golden: unchanged

This commit is contained in:
sganggs committed 2026-10-07 11:21:24 +08:00
1 parent 0b306baa49
commit f661a47773
21 files changed
+706 -14

No files matched your search

+2
View File
@@ -368,6 +368,8 @@ export class Match {
this.watchPref = new Map();
this.lastResults = new Map();
this.unitePlan = null;
/** 联防 outcome for the SETTLE view: { through, helpers, leakers, losses } (settle()), null when no 联防 resolved */
this.uniteResultView = null;
/** server-run 联防: the leakers' counts last published (_uniteTick) */
this._uniteLeftKey = null;
/** 联防: { plan, bounds } — per leaker the most survivors settlement can bill (_uniteLeft's clamp) */
+1
View File
@@ -229,6 +229,7 @@ export class MatchPhases {
this.fields = [];
this.watchers.clear();
this.unitePlan = null;
this.uniteResultView = null;
this.sp = null;
this.wave = null;
this.bossWaves = null;
+15 -2
View File
@@ -1,6 +1,6 @@
// server/match/match/settle.js — Match methods: SETTLE (the LP loss — a leaker's 联防 survivors —, stats, bounty coins,
// the IN_BATTLE layer gains clamped by layerGainRoom, CHAR_DAMAGE tickers, eliminations) and RESULT (finish: m.result
// to every human still here, onEnd).
// the IN_BATTLE layer gains clamped by layerGainRoom, CHAR_DAMAGE tickers, eliminations, the 联防 outcome the SETTLE
// view carries) and RESULT (finish: m.result to every human still here, onEnd).
// Installed on Match.prototype by server/match/Match.js (a method container: never instantiated; `this` is the match).
import { PHASE, layerGainRoom } from '../../../shared/constants.js';
@@ -20,11 +20,24 @@ export class MatchSettle {
// a 联防 battle that could not run at all (synthetic result) must not wipe the leakers' losses: charge their own leaks
const uniteRan = !!(plan && uniteResult && !uniteResult.synthetic);
const survivors = uniteRan ? uniteSurvivors(plan, uniteResult) : null;
// The 联防's outcome as data for the SETTLE view (m.public.uniteResult, views.js; GitHub #235, PR #112 by @Convey123):
// each client pops the official result box from it (ui/gameLogic/phases.js uniteResultBox) — `through` = the leakers'
// enemies that still got through (uncapped; only decides whether 「全员无伤!」 is true), `losses` = every alive
// player's own LP charge of this round, the same `loss` deducted below, so the box and the LP bar never disagree. A
// leaker's own battle leaks are not its charge in a 联防 round, which is why the client cannot work the number out
// itself. No ticker line: the official reports the outcome in the one dialog. null when no 联防 resolved.
this.uniteResultView = uniteRan && plan.leakers.length ? {
through: plan.leakers.reduce((n, lk) => n + Math.max(0, survivors.get(lk.playerId) || 0), 0),
helpers: plan.helpers.map((p) => p.playerId),
leakers: plan.leakers.map((p) => p.playerId),
losses: {},
} : null;
const alive = this.alivePlayers();
for (const ps of alive) {
const r = this.lastResults.get(ps.playerId) || { leaked: [], perfect: true, coins: 0, layerGains: {}, killed: 0, damageDealt: 0 };
const counted = (r.leaked || []).filter((l) => l && l.counted !== false).length;
const loss = uniteRan && plan.leakers.includes(ps) ? Math.min(cap, survivors.get(ps.playerId) || 0) : Math.min(cap, counted);
if (this.uniteResultView) this.uniteResultView.losses[ps.playerId] = loss;
ps.lp -= loss;
ps.stats.lpLost += loss;
ps.stats.leaks += counted;
+10 -3
View File
@@ -1,7 +1,7 @@
// server/match/match/views.js — Match methods: the state builders — m.public (publicView with statusOf / fieldOf, the
// fields' progress and the teammates' live pendingLp / uniteLeft), the nextEnemies preview of m.private and the prep
// scout's m.field (prepFieldMeta: board, hand and temp as units, the scouted player's effects and coming enemies; in a
// boss round's prep on the player's half of the boss field).
// fields' progress, the teammates' live pendingLp / uniteLeft, the SETTLE uniteResult), the nextEnemies preview of
// m.private and the prep scout's m.field (prepFieldMeta: board, hand and temp as units, the scouted player's effects and
// coming enemies; in a boss round's prep on the player's half of the boss field).
// Installed on Match.prototype by server/match/Match.js (a method container: never instantiated; `this` is the match).
import { PHASE, GEO } from '../../../shared/constants.js';
@@ -117,6 +117,13 @@ export class MatchViews {
};
}
if (this.phase === PHASE.UNITE && this.unitePlan) v.unite = { helpers: this.unitePlan.helpers.map((p) => p.playerId), leakers: this.unitePlan.leakers.map((p) => p.playerId) };
// SETTLE after a 联防: its outcome as data (settle.js uniteResultView; GitHub #235, PR #112) — { through, helpers,
// leakers, losses: { playerId: the LP settlement charged this round } } — the client's result box reads the viewer's
// own charge from it; absent when no 联防 resolved (the client then shows the round's own battle result)
if (this.phase === PHASE.SETTLE && this.uniteResultView) {
const ur = this.uniteResultView;
v.uniteResult = { through: ur.through, helpers: ur.helpers.slice(), leakers: ur.leakers.slice(), losses: { ...ur.losses } };
}
return v;
}