Files
sganggs 221e6b8dd2 0.2.5-dev: community round 30 and follow-ups
- Stock: fixed trait gifts (缪尔赛思 / 变形者集群 变形同构体, 耶拉 不融冰, 蜜蜡 / 卡涅利安 迅捷作战粮, 诗怀雅 盟约之币) still occupy stock but are granted past the cap; an operator card already shown in the shop or a reward can be taken after the pool runs out and overdraws (GitHub #498, the owner's decision); item buys and item rewards keep the stock check. 洛洛 / 焰尾 sub-pools use the measured weights (PR #497 by @2321Robin; the occupancy idea of PR #496 by @2321Robin re-implemented differently).
- Boss pairs are drawn at random once per match and fixed to battle start; the Hidden Core reuses them; a leaver does not split a full pair (the owner's decision).
- 阿戈尔 in 联防 no longer re-applies the opening devour HP loss or a false revive; Touch's team medic stays for the match after its holder leaves (owner-approved, official lifetime unverified).
- Auto-skill ranges: 司霆惊蛰 S2, 斩业星熊 S3 against air, 予愿安洁莉娜 S3; 自在's post-rebirth invulnerability is also untargetable (#510); Horn / Clementia / catapult / 忍冬 first hit (PR #502 by @Xinglan233); 不融冰 / tear gas trigger on real damage.
- Bond layer-up sound in round start / draft / prep / battle (#482): one channel, a new cue replaces the old, no extra cooldown; a prep update that adds layers and activates a bond plays one cue.
- Client: multi-swing fix (#506), refresh single-flight (PR #507 by @robll-v1), LP tower keeps the settled value with a −N tick (PR #504 by @robll-v1), export with only 潜能 / 练度 changes (PR #503 by @Xinglan233), spectator rosters list DIY members (PR #505 by @Xinglan233), banned members beside bond tooltips (PR #494 by @YuriRestia), recovery action above the away overlay (PR #511 by @RainoutOvO), the /dev/sim-check.html consistency page (PR #492 by @Cloudnyco), Korean (PR #490 by @D-Roote) and English Victoria wording (PR #499 by @YuriRestia).
- golden: 45 of 287 scenarios move against 0.2.4 (roster 17, bonds 3, fields 6, matches 17, standins 2), each attributed in docs/history/0.2.5.md §30.9 / §30.16.
2026-10-12 14:07:08 +08:00

89 lines
4.4 KiB
JavaScript

// server/sim/content/support/meta.js — prep-side helpers shared by content wave B (docs/META.md registry API).
//
// registerSupportMeta(registry) is called once from bonds.js → registerMeta. It installs
// global:contentb_info onBattleStart: writes `ev.input.contentInfo` (read in battles with support.contentInfo()):
// { round, kind, shopLevel, funds,
// handUnits pieces in the 整备区 (hand, 10 slots; operators, items and summon stacks),
// handChess operators in the hand,
// roundStats { refreshes, buys, sells, spent, gainedChess, arts } (this round's prep, ps.round),
// matchBands band ids of every alive player (this player first), plus a retained Touch team grant,
// teammates [{ playerId, bandId }] of the other alive players }
// Global handlers run first in every dispatch, so band / bond / garrison / item handlers of the same onBattleStart can
// read or extend `ev.input.contentInfo`.
//
// Meta helpers (pure functions of a handler ctx):
// metaBonds(ctx, pieceView) bonds of a piece incl. 变形同构体 grants
// distinctTiers(ctx, pred) distinct chess tiers on the board among pieces matching pred
// frontPiece / behindPiece board neighbours ("身前一格" = one step along the piece's direction `dir`, default
// RIGHT = col + 1; board piece views carry `dir`, sim/dir.js)
import { frontOf } from '../../dir.js';
export function registerSupportMeta(registry) {
registry.global('contentb_info', {
onBattleStart(ctx, ev) {
if (!ev || !ev.input || typeof ev.input !== 'object') return;
const hand = ctx.hand();
const rs = ctx.roundStats();
const mates = ctx.teammates();
const matchBands = [ctx.bandId(), ...mates.map(t => t.bandId())].filter(Boolean);
// [ASSUMED] 外勤医疗 is a team grant for this match, not an aura revoked when its holder is eliminated.
// Other team strategies and the teammate contexts still require a living player.
if (ctx.teamMedicGranted?.() && !matchBands.includes('band_amedic')) matchBands.push('band_amedic');
ev.input.contentInfo = {
round: ctx.round,
kind: ev.kind ?? null,
shopLevel: ctx.shopLevel(),
funds: ctx.funds(),
handUnits: hand.filter(Boolean).length,
handChess: hand.filter((p) => p && p.kind === 'chess').length,
roundStats: {
refreshes: rs.refreshes | 0, buys: rs.buys | 0, sells: rs.sells | 0, spent: rs.spent | 0,
gainedChess: rs.gainedChess | 0, arts: rs.arts | 0,
},
matchBands,
teammates: mates.map((t) => ({ playerId: t.playerId, bandId: t.bandId() })),
};
},
});
}
/** Bonds of a piece view (own + 变形同构体 grants), as the match counts them. */
export function metaBonds(ctx, piece) {
if (!piece || piece.kind !== 'chess') return [];
const fresh = Number.isInteger(piece.uid) && piece.uid > 0 ? ctx.piece(piece.uid) : null;
const p = fresh && fresh.kind === 'chess' ? fresh : piece;
const rec = ctx.chessRecord(p.id);
return withGrants(ctx, p, rec && Array.isArray(rec.bonds) ? rec.bonds.slice() : []);
}
function withGrants(ctx, piece, bonds) {
const items = Array.isArray(piece.items) ? piece.items : [];
if (items.length < 2) return bonds;
const recs = items.map((it) => ctx.gd.item(it.id)).filter(Boolean);
if (!recs.some((r) => r.canGiveBond)) return bonds;
const out = bonds.slice();
for (const r of recs) if (!r.canGiveBond && typeof r.giveBondId === 'string' && ctx.gd.bond(r.giveBondId) && !out.includes(r.giveBondId)) out.push(r.giveBondId);
return out;
}
/** Distinct tiers of board chess matching `pred(pieceView)`. */
export function distinctTiers(ctx, pred = () => true) {
const tiers = new Set();
for (const p of ctx.board()) if (p && p.kind === 'chess' && pred(p)) tiers.add(p.tier ?? ctx.gd.tierOf(p.id));
return tiers;
}
/** Board piece at (row, col) or null. */
export function boardPieceAt(ctx, row, col) {
for (const p of ctx.board()) if (p.row === row && p.col === col) return p;
return null;
}
/** Board piece `k` tiles in front of a board piece, along its direction (`p.dir`, default RIGHT: col + k). */
export const frontPiece = (ctx, p, k = 1) => {
if (!p || !Number.isInteger(p.row) || !Number.isInteger(p.col)) return null;
const [r, c] = frontOf(p.row, p.col, p.dir, k);
return boardPieceAt(ctx, r, c);
};
export const behindPiece = (ctx, p, k = 1) => frontPiece(ctx, p, -k);