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:
1 parent
620f88c0d8
commit
82c64086d5
21 files changed
+2087
-57
No files matched your search
+12
-1
@@ -812,7 +812,12 @@ export class Match {
|
||||
shopLevel: ps.shop.level,
|
||||
boardCount: ps.deployCount,
|
||||
ready: this.phase === PHASE.INFO_CHECK ? ps.infoReady : ps.ready,
|
||||
bonds: bondList(this.gd, ps.bonds).filter((b) => b.count > 0 || b.active),
|
||||
// the strip of a teammate watching this player (DESIGN §20.15): every bond with members, layers or an active tier
|
||||
// (= the player's own m.private list without thresholds / countsHand — the client reads those from bonds.json),
|
||||
// this round's in-battle gains included once the COMBAT phase ended (PlayerState.bondsView); [] once eliminated —
|
||||
// nobody can watch an eliminated player (g.watch refuses them, they have no field) and the result screen reads
|
||||
// m.result's own bonds, so their layers would only cost every m.public bytes for the rest of the match
|
||||
bonds: ps.alive ? bondList(this.gd, ps.bondsView()) : [],
|
||||
fieldId: this.fieldOf(ps),
|
||||
status: this.statusOf(ps),
|
||||
autoplay: ps.autoplay,
|
||||
@@ -1804,6 +1809,10 @@ export class Match {
|
||||
for (const pid of f.players) {
|
||||
const pp = res.perPlayer && res.perPlayer[pid];
|
||||
this.lastResults.set(pid, pp || { killed: 0, total: 0, leaked: [], perfect: true, layerGains: {}, coins: 0, damageDealt: 0, unitsEnd: [], unitStats: [] });
|
||||
// the views show the layers the battle reached until settle() makes them persistent (DESIGN §20.15)
|
||||
const ps = this.players.get(pid);
|
||||
const gains = pp && pp.layerGains && typeof pp.layerGains === 'object' ? pp.layerGains : null;
|
||||
if (ps && gains && Object.keys(gains).length) { ps.pendingLayerGains = { ...gains }; ps.dirty(); }
|
||||
}
|
||||
}
|
||||
for (const f of this.fields) f.live = false;
|
||||
@@ -2685,6 +2694,8 @@ export class Match {
|
||||
this.phase = PHASE.SETTLE;
|
||||
this.runner = null;
|
||||
this._stopClientCombat();
|
||||
// the pending in-battle gains the views showed become persistent below (alive players) or lapse (DESIGN §20.15)
|
||||
for (const ps of this.order) if (ps.pendingLayerGains) { ps.pendingLayerGains = null; ps.dirty(); }
|
||||
const cap = this.gd.lpCapPerRound;
|
||||
// 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);
|
||||
|
||||
@@ -58,7 +58,7 @@ import { ERR, GEO, PHASE, layerGainRoom } from '../../shared/constants.js';
|
||||
import { checkLoadout, resolveLoadout } from '../../shared/protocol.js';
|
||||
import { FIELD, tileKey, parseKey, inField, canPlace, positionClass, boardOrder, freeSlot, pieceDir, parseDir, mergeTile } from './board.js';
|
||||
import { offsetTile } from '../sim/dir.js';
|
||||
import { computeBonds, bondList, bondSnapshot, activatedLayers } from './bondsMeta.js';
|
||||
import { computeBonds, bondList, bondSnapshot, activatedLayers, bondsWithGains } from './bondsMeta.js';
|
||||
import { itemKey } from './gamedata.js';
|
||||
import { bountyText } from './choices.js';
|
||||
|
||||
@@ -112,6 +112,11 @@ export class PlayerState {
|
||||
this.layers = {};
|
||||
/** computed bond states */
|
||||
this.bonds = {};
|
||||
/**
|
||||
* this round's IN_BATTLE layer gains of the finished normal battle ({ [bondId]: n }, Match._finishCombat) until
|
||||
* settle() makes them persistent — the views add them (bondsView, DESIGN §20.15); null otherwise
|
||||
*/
|
||||
this.pendingLayerGains = null;
|
||||
/** optional per-bond count bonus written by effects */
|
||||
this.bondCountBonus = {};
|
||||
/** EffectRef list: { id, key, name, desc, iconKind, iconId, counter?, battle, params, data } */
|
||||
@@ -1301,6 +1306,7 @@ export class PlayerState {
|
||||
|
||||
startRound(r) {
|
||||
this.round = { refreshes: 0, buys: 0, sells: 0, spent: 0, gainedChess: 0, arts: 0 };
|
||||
this.pendingLayerGains = null; // settled (or lapsed) at the last SETTLE
|
||||
if (r > 1) this.shop.upgradePrice = Math.max(0, this.shop.upgradePrice - 1);
|
||||
// onIncome handlers may rewrite ev.income / ev.pending (e.g. 老鲤 withholds R1–R2 income until R3)
|
||||
const ev = { round: r, income: this.gd.income(r), pending: this.pendingFunds };
|
||||
@@ -1366,6 +1372,12 @@ export class PlayerState {
|
||||
|
||||
activatedLayers() { return activatedLayers(this.bonds); }
|
||||
|
||||
/**
|
||||
* The bond states the views show (m.private bonds, m.public players[].bonds): the computed states plus the pending
|
||||
* in-battle gains of this round's finished normal battle (bondsMeta.bondsWithGains). Never used by rules.
|
||||
*/
|
||||
bondsView() { return bondsWithGains(this.bonds, this.pendingLayerGains); }
|
||||
|
||||
// =================================================================================================
|
||||
// battle input
|
||||
|
||||
@@ -1476,7 +1488,7 @@ export class PlayerState {
|
||||
board,
|
||||
deployCap: this.deployCap,
|
||||
deployCount: this.deployCount,
|
||||
bonds: bondList(this.gd, this.bonds, { full: true }),
|
||||
bonds: bondList(this.gd, this.bondsView(), { full: true }),
|
||||
effects: this.effectsView(),
|
||||
nextEnemies: this.m.nextEnemiesFor(this),
|
||||
// DESIGN §16: the effective operator loadout ({ [baseChessId]: { skill, module } }; chess not listed use defaults)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// `tier` = number of thresholds reached (downward: 1 when active); `active = tier ≥ 1`.
|
||||
// Layers (`ps.layers[bondId]`) persist the whole match; they are reported for every bond but only matter while active.
|
||||
|
||||
import { layerGainRoom } from '../../shared/constants.js';
|
||||
|
||||
export const HARMONY_BOND = 'maniShip';
|
||||
export const DEPUTY_BOND = 'deputShip';
|
||||
|
||||
@@ -164,6 +166,29 @@ export function bondList(gd, bonds, { full = false } = {}) {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bond states the views show (m.private / m.public players[].bonds, DESIGN §20.15): `bonds` with this round's
|
||||
* IN_BATTLE gains of a finished normal battle (`gains` = the result's layerGains, PlayerState.pendingLayerGains, set
|
||||
* when the COMBAT phase ends) added the way settle() will add them — at most up to BOND_LAYER_CAP — so the strip of a
|
||||
* player (and of a teammate watching him) keeps the layers his battle reached through the 联防 until the settlement
|
||||
* makes them persistent. `bonds` itself when there is nothing to add; the persistent state is never touched.
|
||||
* @param {ReturnType<typeof computeBonds>} bonds
|
||||
* @param {Record<string, number>|null|undefined} gains
|
||||
*/
|
||||
export function bondsWithGains(bonds, gains) {
|
||||
if (!gains || typeof gains !== 'object') return bonds;
|
||||
let out = null;
|
||||
for (const [id, n] of Object.entries(gains)) {
|
||||
const b = bonds && bonds[id];
|
||||
if (!b) continue;
|
||||
const add = layerGainRoom(b.layers, Math.floor(Number(n) || 0));
|
||||
if (!(add > 0)) continue;
|
||||
if (!out) out = { ...bonds };
|
||||
out[id] = { ...b, layers: (b.layers || 0) + add };
|
||||
}
|
||||
return out || bonds;
|
||||
}
|
||||
|
||||
/** Snapshot for PlayerBattleInput.bonds: { [bondId]: { count, active, tier, layers } } (plain copy). */
|
||||
export function bondSnapshot(bonds) {
|
||||
const o = {};
|
||||
|
||||
Reference in new issue
Block a user