Bond strip follows the watched teammate (v2.5.2): prep scouting, after the own battle, 联防, Final Assault, eliminated observers

- the strip, its counts / layers / tiers and the bond popups show the player whose field or half is on screen, with an
  amber "👁 name" tag; live in-battle layers (runner bondLayers, ≤ 999) and the finished battle's gains until SETTLE
- shared fields follow the ‹ › half's player; 全景 shows your own when you fight there, else the picked teammate [ASSUMED]
- a card's bond chips and popups belong to the unit's owner; a teammate's popup lists their operators on the field
- m.public players[].bonds for living players only; a reload while watching after the own battle returns to it
- DESIGN §20.15, META, PLAYING; tests (unit, UI logic, 4 browser scenarios)
This commit is contained in:
sganggs committed 2026-10-02 12:11:48 +08:00
1 parent 620f88c0d8
commit 82c64086d5
21 files changed
+2087 -57

No files matched your search

+25
View File
@@ -55,6 +55,31 @@ export function observeTarget(p, pub, myId, { observing = false, ownDone = false
return { fieldId: target.fieldId };
}
/**
* A screen reloaded (or a socket reconnected) while it watched a teammate's battle after the own one: the server
* resends the watched field (Match._resendBattle, b.start `watch: true`), but a fresh screen starts at home — so the
* observing pill, 返回战场 and the own row would be missing. Decided once per battle, at the first sight of its
* battleId (`seen` = the battleId already decided): adopt `fieldId` as the watched field when it is a teammate's normal
* battle in 各自行动 shown to a living player who watches nothing. Never for a battle first seen while watching (a
* 前往查看 under way — and its 返回战场 must not bring it back while the own b.start is on its way), the own field, a
* 联防 leaker's field or an eliminated player's auto-observed one (those keep their own rules). A loading battle (its
* state carries no `watch` yet) is decided once it runs, unless the screen already watches (then it is marked seen).
* @param {any} b battleRunner.state() @param {{ pub?: any, myId?: string, alive?: boolean, watching?: string|null,
* seen?: string|null }} [o]
* @returns {{ seen: string|null, fieldId: string|null }} seen: the new value to remember
*/
export function resumedWatch(b, { pub = null, myId = '', alive = true, watching = null, seen = null } = {}) {
const keep = { seen, fieldId: null };
if (!isObj(pub) || !isObj(b) || typeof b.battleId !== 'string' || !b.battleId || b.battleId === seen) return keep;
if (b.loading && !watching) return keep;
const done = { seen: b.battleId, fieldId: null };
if (b.loading || watching || !alive || !isClientCombat(pub) || pub.phase !== PHASE.COMBAT) return done;
if (!b.watch || b.kind !== 'normal' || typeof b.fieldId !== 'string' || !b.fieldId) return done;
const f = fields(pub).find((x) => x.fieldId === b.fieldId);
if (!f || f.kind !== 'normal' || !Array.isArray(f.players) || f.players.includes(myId)) return done;
return { seen: b.battleId, fieldId: b.fieldId };
}
/** Teammates' progress for the waiting pill: [{ playerId, name, killed, total, done, isBot }]. */
export function teammateProgress(pub, myId) {
const out = [];