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.
This commit is contained in:
1 parent
bc50cac01f
commit
221e6b8dd2
134 files changed
+4590
-1201
No files matched your search
@@ -6,7 +6,7 @@
|
||||
// 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';
|
||||
import { deriveSeed } from '../../sim/rng.js';
|
||||
import { createRng, deriveSeed } from '../../sim/rng.js';
|
||||
import { buildBossWave, bountySpawns } from '../waves.js';
|
||||
import { pairPlayers, bossPoolHp, SharedBossPool, CreditPool, hiddenEligible, BOSS_HIT_STEPS } from '../finalAssault.js';
|
||||
import { FieldRunner, HeadlessPacer, syntheticResult, BOSS_SILENCE_MS, CATCHUP_TICKS_PER_INTERVAL } from '../fields.js';
|
||||
@@ -66,7 +66,8 @@ export class MatchBoss {
|
||||
},
|
||||
});
|
||||
this.bossPool = pool;
|
||||
const groups = pairPlayers(alive);
|
||||
const groups = pairPlayers(alive, createRng(deriveSeed(this.seed, 'boss-pairs')), this.bossPairs);
|
||||
this.bossPairs = groups.map(g => g.map(p => p.playerId));
|
||||
const reuse = this.bossWaves && this.bossWaves.length === groups.length && this.bossWaves.every((w, i) => w.players.join() === groups[i].map((p) => p.playerId).join());
|
||||
this.fields = groups.map((g, i) => {
|
||||
const solo = this.isSolo || g.length === 1;
|
||||
|
||||
@@ -88,8 +88,11 @@ export class MatchInfra {
|
||||
}
|
||||
humans() { return this.order.filter((p) => !p.isBot && !p.left); }
|
||||
|
||||
/** [ASSUMED] Touch's initial, match-long team grant survives its holder's elimination/quit; focus alone grants none. */
|
||||
teamMedicGranted() { return this.order.some(p => p.bandId === 'band_amedic'); }
|
||||
|
||||
/**
|
||||
* The boss-round group of a player (`bossWaves`: the seat pairs of finalAssault.js pairPlayers, planned in startRound
|
||||
* The boss-round group of a player (`bossWaves`: the per-match random pairs, planned in startRound
|
||||
* before the players' round start) and its side — 'L', or 'R' for the second player of a pair (the mirrored right
|
||||
* half) — or null outside a boss round / for a player without a field.
|
||||
* @returns {{ wave: object, players: string[], side: 'L'|'R' } | null}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { PHASE, ERR } from '../../../shared/constants.js';
|
||||
import { buildNormalWave, buildBossWave } from '../waves.js';
|
||||
import { pairPlayers } from '../finalAssault.js';
|
||||
import { createRng, deriveSeed } from '../../sim/rng.js';
|
||||
import { botPickBand } from '../bot.js';
|
||||
import { OK, fail, DELAYS, BAND_TURN_SECONDS } from './common.js';
|
||||
|
||||
@@ -288,11 +289,14 @@ export class MatchPhases {
|
||||
for (const ps of this._viewers()) this._followScout(ps);
|
||||
}
|
||||
|
||||
/** The boss round's fields (seat pairs of the alive players) and their templates, generated for the prep preview. */
|
||||
/** Stable per-match pairs and their templates, generated before the prep preview / board validation. */
|
||||
_planBossWaves() {
|
||||
const r = this.round;
|
||||
const bossId = r === this.gd.hiddenRound && r !== this.gd.bossRound ? this.hiddenBossId : this.bossId;
|
||||
this.bossWaves = pairPlayers(this.alivePlayers()).map((g) => ({
|
||||
// Pairing uses an independent seeded draw, consuming no shop, wave, draft or battle randomness.
|
||||
const groups = pairPlayers(this.alivePlayers(), createRng(deriveSeed(this.seed, 'boss-pairs')), this.bossPairs);
|
||||
this.bossPairs = groups.map(g => g.map(p => p.playerId));
|
||||
this.bossWaves = groups.map((g) => ({
|
||||
players: g.map((p) => p.playerId),
|
||||
wave: buildBossWave(this.gd, this.rngWaves, this.factions, r, { bossId, solo: this.isSolo || g.length === 1 }),
|
||||
}));
|
||||
|
||||
@@ -86,6 +86,7 @@ export class MatchViews {
|
||||
hiddenBossId: this.hiddenBossId,
|
||||
bossRound: this.gd.bossRound,
|
||||
hiddenRound: this.gd.hiddenRound,
|
||||
bossPairs: this.bossWaves ? this.bossWaves.map(w => w.players.slice()) : this.bossPairs ? this.bossPairs.map(ids => ids.slice()) : null,
|
||||
spRound: this.gd.spRounds().includes(this.round),
|
||||
// DESIGN §14: 'client' = battles are simulated by the browsers (b.start specs), 'server' = legacy streaming
|
||||
combatMode: this.clientCombat ? 'client' : 'server',
|
||||
@@ -248,7 +249,7 @@ export class MatchViews {
|
||||
* battleStart, so the prep view always uses the reserve medic, regardless of the current elite count. The stage's
|
||||
* normal-board slot is transformed to the correct half of a boss field by _onBossHalf / the client prep camera. */
|
||||
prepMapChars(ps) {
|
||||
if (!ps?.alive || this.phase !== PHASE.PREP || !this.order.some((p) => p.alive && p.bandId === 'band_amedic')) return [];
|
||||
if (!ps?.alive || this.phase !== PHASE.PREP || !this.teamMedicGranted()) return [];
|
||||
const id = 'char_605_cmedic';
|
||||
const slot = this.stage?.mapChars?.find((m) => m?.key === id && Array.isArray(m.pos) && m.pos[0] >= 9 && m.pos[0] <= 12
|
||||
&& m.pos[1] >= 0 && m.pos[1] <= 10 && !/multi_only/.test(String(m.alias)));
|
||||
|
||||
Reference in new issue
Block a user