feedback5: merge W5E — a full hand refuses buys and reward picks, temp pieces fill freed slots (#82); 联防 plays on the official escaped maps (#41)
# Conflicts: # test/golden/fields.json # test/golden/matches.json
This commit is contained in:
commit
ece75b6853
37 files changed
+732
-265
No files matched your search
@@ -2,9 +2,12 @@
|
||||
//
|
||||
// Handlers validate → mutate → recompute bonds → mark the private view dirty. They never throw on bad input; they
|
||||
// return `{ ok: true }` or `{ error: ERR.*, detail? }`. Rules (research 00-INDEX §3–§4, 01 A1, 04 §2):
|
||||
// * Hand (整备区) 10 slots filled right→left, temp (临时整备区) 5 slots. A full hand refuses buys / withdrawals,
|
||||
// except a purchase that completes a merge and a withdrawal whose own summon stack frees a slot. Passive gains (merge
|
||||
// results, grants, returned equipment) overflow into temp; temp blocks Ready ("直到溢出情况排除才可开始进行作战").
|
||||
// * Hand (整备区) 10 slots filled right→left, temp (临时整备区) 5 slots. A full hand refuses buys and reward picks —
|
||||
// also one that would complete a merge at once (PRTS 卫戍协议/帮助 §手牌区 "例如招募/购入等通常情况下会增加手牌的操作";
|
||||
// GitHub #82) — and withdrawals, except one whose own summon stack frees a slot. Passive gains (merge results,
|
||||
// grants, returned equipment) overflow into temp, and a free hand slot pulls them back in at once, right→left
|
||||
// (_fillHandFromTemp at every recompute: "常规手牌区出现空位时自动移入"); temp blocks Ready ("直到溢出情况排除才可开始
|
||||
// 进行作战").
|
||||
// A temp piece is resolved (chess sold back to the pool, items destroyed, summon stacks removed — they come back at
|
||||
// the next round start, grantTokensFor) at the deadline of the
|
||||
// first prep in which the player could act on it (tempDue): a piece that overflowed during a prep before Ready
|
||||
|
||||
+26
-15
@@ -1432,6 +1432,11 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
|
||||
// shed is pre-emptive (one slot is still free), so with only fresh singles it waits for the next prep.
|
||||
const used = ps.hand.filter(Boolean).length;
|
||||
if (used >= gd.benchSize - 1) sellWeakestHand(m, ps, { keepFresh: true });
|
||||
// a full hand refuses every purchase, the third copy of a held pair too (PlayerState.buy, PRTS 卫戍协议/帮助 §手牌区):
|
||||
// an affordable one sheds the weakest hand single first (like the shed above, never one of this prep)
|
||||
if (freeSlot(ps.hand) < 0 && ps.shop.slots.some((s) => s && !s.sold && s.kind === 'chess' && ps.priceOf(s) <= ps.funds && ps.completesChessMerge(s.id))) {
|
||||
sellWeakestHand(m, ps, { handOnly: true, keepFresh: true });
|
||||
}
|
||||
let best = -1;
|
||||
let bestS = 0;
|
||||
let bestMerges = false;
|
||||
@@ -1443,7 +1448,7 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
|
||||
let sc;
|
||||
if (s.kind === 'chess') {
|
||||
const merges = ps.completesChessMerge(s.id);
|
||||
if ((freeSlot(ps.hand) < 0 || mergesOnly) && !merges) return;
|
||||
if (freeSlot(ps.hand) < 0 || (mergesOnly && !merges)) return;
|
||||
const base = gd.baseIdOf(s.id);
|
||||
if (boardFull && !merges && ps.funds - price < reserve) return; // banked (坎诺特)
|
||||
sc = buyScore(m, ps, s.id, ctx) - price;
|
||||
@@ -1466,7 +1471,7 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
|
||||
} else {
|
||||
if (fillOnly) return;
|
||||
if (!canUseItem(m, ps, { id: s.id })) return;
|
||||
if (freeSlot(ps.hand) < 0 && !ps.completesItemMerge(s.id)) return;
|
||||
if (freeSlot(ps.hand) < 0) return;
|
||||
if (ps.funds - price < reserve && !ps.completesItemMerge(s.id)) return;
|
||||
const carriers = [...ps.board.values()].filter((p) => p.kind === 'chess' && (p.items || []).length < gd.equipPerChess).length;
|
||||
if (!carriers) return;
|
||||
@@ -1805,34 +1810,40 @@ function equipItems(m, ps) {
|
||||
/**
|
||||
* Free a hand slot for an item worth keeping (突变细胞): sell the weakest single hand chess, else (on a bot's own seat
|
||||
* only — a human's items under AI 托管 are never destroyed to make room, like freeHandSlot) destroy the cheapest other
|
||||
* hand item, else sell the weakest hand chess even of a pair. false: the hand stays full (summon cards only).
|
||||
* hand item, else sell the weakest hand chess even of a pair. `piece` (in temp): done once the freed slot pulled it in
|
||||
* (PlayerState._fillHandFromTemp — resolveTemp asks for the rightmost temp piece, the next one a slot pulls in). false:
|
||||
* the hand stays full (summon cards only).
|
||||
*/
|
||||
function makeHandRoom(m, ps) {
|
||||
if (freeSlot(ps.hand) >= 0) return true;
|
||||
if (sellWeakestHand(m, ps, { handOnly: true }) && freeSlot(ps.hand) >= 0) return true;
|
||||
function makeHandRoom(m, ps, piece = null) {
|
||||
const room = () => freeSlot(ps.hand) >= 0 || (piece != null && !ps.temp.includes(piece));
|
||||
if (room()) return true;
|
||||
if (sellWeakestHand(m, ps, { handOnly: true }) && room()) return true;
|
||||
const gd = m.gd;
|
||||
const junk = ps.isBot && !ps.autoplay ? ps.hand.filter((p) => p && p.kind === 'item' && !isMutationCell(gd, p.id))
|
||||
.sort((a, b) => ((gd.item(a.id) || {}).price || 0) - ((gd.item(b.id) || {}).price || 0) || a.uid - b.uid)[0] : null;
|
||||
if (junk && tryDo(() => ps.destroy(junk.uid)) && freeSlot(ps.hand) >= 0) return true;
|
||||
return sellWeakestHand(m, ps, { handOnly: true, keepPairs: false }) && freeSlot(ps.hand) >= 0;
|
||||
if (junk && tryDo(() => ps.destroy(junk.uid)) && room()) return true;
|
||||
return sellWeakestHand(m, ps, { handOnly: true, keepPairs: false }) && room();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the temp row before Ready. A freed hand slot pulls the temp pieces in by itself, rightmost first
|
||||
* (PlayerState._fillHandFromTemp), so the pieces are decided right → left: the one at hand is always the next a freed
|
||||
* slot takes. A chess: the weakest bench single is sold to make room (it may be the temp chess itself), else the temp
|
||||
* chess is sold; an item: see below, else destroyed.
|
||||
*/
|
||||
function resolveTemp(m, ps) {
|
||||
for (let i = 0; i < ps.temp.length; i++) {
|
||||
for (let i = ps.temp.length - 1; i >= 0; i--) {
|
||||
const p = ps.temp[i];
|
||||
if (!p) continue;
|
||||
const idx = freeSlot(ps.hand);
|
||||
const idx = freeSlot(ps.hand); // (a recompute pulls it in; a state set up without one)
|
||||
if (idx >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx }))) continue;
|
||||
if (p.kind === 'chess') {
|
||||
if (!sellWeakestHand(m, ps)) tryDo(() => ps.sell(p.uid));
|
||||
else {
|
||||
const j = freeSlot(ps.hand);
|
||||
if (j >= 0) tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }));
|
||||
}
|
||||
} else if (p.kind === 'item') {
|
||||
// 突变细胞 comes back after every transformation (昆图斯's strategy item), and a human's item under AI 托管 is theirs:
|
||||
// make room in the hand (sell a bench operator) rather than lose it — only a hand of nothing but items still drops it
|
||||
if ((isMutationCell(m.gd, p.id) || ps.autoplay) && makeHandRoom(m, ps)) {
|
||||
if ((isMutationCell(m.gd, p.id) || ps.autoplay) && makeHandRoom(m, ps, p)) {
|
||||
if (!ps.temp.includes(p)) continue;
|
||||
const j = freeSlot(ps.hand);
|
||||
if (j >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }))) continue;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
// collectViolations(m) → string[] (empty when every invariant holds):
|
||||
// pool 0 ≤ left ≤ cap and left + Σ copies held by pieces == cap per base chess; non-pool chess hold 0 copies
|
||||
// economy funds / pendingFunds non-negative integers, LP finite, shop level in range, prices ≥ 0
|
||||
// pieces unique uids; hand 10 / temp 5 slots; chess carry ≤ equipPerChess known items; a normal piece holds ≤ 1
|
||||
// copy, an elite ≤ goldenCopies; merges are immediate (never `mergeCount` normal copies of one chess, never
|
||||
// two copies of a mergeable normal item); every token's owner chess is deployed
|
||||
// pieces unique uids; hand 10 / temp 5 slots; temp holds pieces only while the hand is full (a free hand slot pulls
|
||||
// a temp piece in, PlayerState._fillHandFromTemp); chess carry ≤ equipPerChess known items; a normal piece
|
||||
// holds ≤ 1 copy, an elite ≤ goldenCopies; merges are immediate (never `mergeCount` normal copies of one
|
||||
// chess, never two copies of a mergeable normal item); every token's owner chess is deployed
|
||||
// board tiles inside the own region and legal for the piece (a range-bound summon inside its owner's attack
|
||||
// range); no items on the board; chess count ≤ deploy cap
|
||||
// (where a merge's elite goes — a consumed deployed copy's tile, else the hand — needs the state before the
|
||||
@@ -52,6 +53,8 @@ export function collectViolations(m, { limit = 25 } = {}) {
|
||||
if (ps.alive && m.phase !== PHASE.SETTLE && ps.lp <= 0 && m.teamLp == null && m.round > 0 && ps.bandId) fail(`${id}: alive with lp ${ps.lp}`);
|
||||
if (ps.hand.length !== gd.benchSize) fail(`${id}: hand has ${ps.hand.length} slots`);
|
||||
if (ps.temp.length !== gd.tempSize) fail(`${id}: temp has ${ps.temp.length} slots`);
|
||||
// 临时整备区 = overflow only (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入", GitHub #82)
|
||||
if (ps.temp.some(Boolean) && ps.hand.some((x) => x == null)) fail(`${id}: a temp piece waits while a hand slot is free`);
|
||||
if (!(ps.shop.level >= 1 && ps.shop.level <= gd.maxShopLevel)) fail(`${id}: shop level ${ps.shop.level}`);
|
||||
if (!(ps.shop.upgradePrice >= 0)) fail(`${id}: upgradePrice ${ps.shop.upgradePrice}`);
|
||||
if (!Number.isInteger(ps.shop.freeRefreshes) || ps.shop.freeRefreshes < 0) fail(`${id}: freeRefreshes ${ps.shop.freeRefreshes}`);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { PHASE, GEO } from '../../../shared/constants.js';
|
||||
import { deriveSeed } from '../../sim/rng.js';
|
||||
import { uniteBattleOpts, uniteSurvivors } from '../unite.js';
|
||||
import { uniteBattleOpts, uniteSurvivors, uniteStageId } from '../unite.js';
|
||||
import { FieldRunner, timelineAt, uniteBillBounds } from '../fields.js';
|
||||
import { uniteLeft } from '../../sim/spec.js';
|
||||
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
|
||||
@@ -38,7 +38,11 @@ export class MatchUnite {
|
||||
this.runner.start();
|
||||
}
|
||||
|
||||
/** Battle options of the 联防 field (helpers' carried end state, the leakers' enemies). */
|
||||
/**
|
||||
* Battle options of the 联防 field (helpers' carried end state, the leakers' enemies) on its own map — the escaped
|
||||
* template's (unite.js uniteStageId; GitHub #41), the round's stage only when the data lacks it. The field meta and the
|
||||
* client-run spec carry that stageId, so every viewer draws the 联防 map.
|
||||
*/
|
||||
_uniteOpts(plan, limit) {
|
||||
const { wave, players } = uniteBattleOpts(this, plan, limit);
|
||||
return {
|
||||
@@ -46,7 +50,7 @@ export class MatchUnite {
|
||||
kind: 'unite',
|
||||
modeId: this.modeId,
|
||||
round: this.round,
|
||||
stageId: this.stageId,
|
||||
stageId: uniteStageId(this.gd, plan.helpers.length) ?? this.stageId,
|
||||
rect: { ...GEO.UNITE_RECT },
|
||||
timeLimit: limit,
|
||||
players,
|
||||
|
||||
@@ -97,6 +97,12 @@ export class PlayerEconomy {
|
||||
this.dirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* g.buy {slot}. A full regular hand refuses every purchase, also one whose copy would complete a merge at once (PRTS
|
||||
* 卫戍协议/帮助 §手牌区 "当常规手牌区全满无空位时,玩家将无法执行使手牌溢出的操作" — "例如招募/购入等通常情况下会增加手牌的
|
||||
* 操作"; BWIKI 盟约 "当整备区达到上限后将无法进行涉及到整备区的操作(购买、撤回等)"; GitHub #82: the reporter's first-hand
|
||||
* check in the official game, "哪怕是已经有了两个相同干员情况"). Until 0.1.3 such a purchase was let through.
|
||||
*/
|
||||
buy(slotIdx) {
|
||||
const g = this._gate(); if (g) return g;
|
||||
if (!Number.isInteger(slotIdx) || slotIdx < 0 || slotIdx >= this.shop.slots.length) return fail(ERR.BAD_TARGET);
|
||||
@@ -113,13 +119,13 @@ export class PlayerEconomy {
|
||||
const base = this.gd.baseIdOf(slot.id);
|
||||
const need = rec.isGolden ? this.gd.goldenCopies : 1;
|
||||
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
|
||||
if (handFull && !this.completesChessMerge(slot.id)) return fail(ERR.HAND_FULL);
|
||||
if (handFull) return fail(ERR.HAND_FULL);
|
||||
this.spend(price);
|
||||
slot.sold = true;
|
||||
piece = this.acquireChess(slot.id, { source: 'buy' });
|
||||
} else {
|
||||
if (!this.gd.item(slot.id)) return fail(ERR.BAD_TARGET);
|
||||
if (handFull && !this.completesItemMerge(slot.id)) return fail(ERR.HAND_FULL);
|
||||
if (handFull) return fail(ERR.HAND_FULL);
|
||||
this.spend(price);
|
||||
slot.sold = true;
|
||||
piece = this.acquireItem(slot.id, { source: 'buy' });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// server/match/player/pieces.js — PlayerState methods: piece bookkeeping — new pieces, lookup by uid (board / hand /
|
||||
// temp / equipped), the chess locations, detach and stow (hand first, overflow temp), the per-piece round counters
|
||||
// (拉普兰德), pool copies back to the shared pool, and summon stacks (removal, lifting, counts, the stack granted for an
|
||||
// owner).
|
||||
// temp / equipped), the chess locations, detach and stow (hand first, overflow temp; a free hand slot pulls a temp piece
|
||||
// in, _fillHandFromTemp), the per-piece round counters (拉普兰德), pool copies back to the shared pool, and summon stacks
|
||||
// (removal, lifting, counts, the stack granted for an owner).
|
||||
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
||||
// the player state).
|
||||
|
||||
@@ -68,7 +68,8 @@ export class PlayerPieces {
|
||||
|
||||
/**
|
||||
* Put a piece into the hand (right→left) or, when `allowTemp`, the temp slots (due at the deadline of the first prep
|
||||
* in which the player can act on it, _tempDueNow). Returns 'hand' | 'temp' | null.
|
||||
* in which the player can act on it, _tempDueNow). Returns 'hand' | 'temp' | null. A piece put into temp while a hand
|
||||
* slot is free (`toTemp`) moves into the hand at the next recompute (_fillHandFromTemp).
|
||||
*/
|
||||
stow(piece, { allowTemp = true, toTemp = false, preferIdx = null } = {}) {
|
||||
if (!toTemp) {
|
||||
@@ -85,6 +86,31 @@ export class PlayerPieces {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* PRTS 卫戍协议/帮助 §手牌区 "溢出单位会自动进入临时手牌区,常规手牌区出现空位时自动移入" (GitHub #82; until 0.1.3 the
|
||||
* player had to drag them back): every free regular hand slot takes a temp piece at once. Order [ASSUMED] (PRTS names
|
||||
* none): the temp row empties in the order it fills — right→left, the way stow fills it (freeSlot), so the piece that
|
||||
* overflowed first moves first — and each piece takes the hand's next free slot right→left ("被发送至手牌区的物资优先
|
||||
* 从右到左填充空位"). recompute() runs it, so it follows every change that frees a slot: a sale, a deployment from the
|
||||
* hand, a merge that consumed hand copies, an item equipped / destroyed / used from the hand, a summon stack removed
|
||||
* with its owner, an effect's destroyPiece… A piece that leaves temp is no longer due (tempDue). Returns the number
|
||||
* moved.
|
||||
* @returns {number}
|
||||
*/
|
||||
_fillHandFromTemp() {
|
||||
let moved = 0;
|
||||
for (let j = this.temp.length - 1; j >= 0; j--) {
|
||||
const p = this.temp[j];
|
||||
if (!p) continue;
|
||||
const i = freeSlot(this.hand);
|
||||
if (i < 0) break;
|
||||
this._detach({ piece: p, area: 'temp', idx: j });
|
||||
this.hand[i] = p;
|
||||
moved++;
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-piece counter of the current round (`piece.meta.round` = { r, n: { key: count } }): 0 for a key not counted yet
|
||||
* this round. The counters belong to the operator: a move keeps them, a new piece (bought, granted, transformed)
|
||||
|
||||
@@ -16,6 +16,11 @@ export class PlayerPrep {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* g.reward {idx}: the pick of the first queued offer (promotion reward, a strategy's special refresh, an item offer).
|
||||
* A pick is a 招募 like a purchase: a full regular hand refuses it, also when it would complete a merge (PRTS
|
||||
* 卫戍协议/帮助 §手牌区, see buy; GitHub #82).
|
||||
*/
|
||||
pickReward(idx) {
|
||||
const g = this._gate(); if (g) return g;
|
||||
const offer = this.offers[0];
|
||||
@@ -26,14 +31,14 @@ export class PlayerPrep {
|
||||
const handFull = freeSlot(this.hand) < 0;
|
||||
if (slot.kind === 'item') {
|
||||
if (!this.gd.item(slot.id)) return fail(ERR.BAD_TARGET);
|
||||
if (handFull && !this.completesItemMerge(slot.id)) return fail(ERR.HAND_FULL);
|
||||
if (handFull) return fail(ERR.HAND_FULL);
|
||||
} else {
|
||||
const rec = this.gd.chess(slot.id);
|
||||
if (!rec) return fail(ERR.BAD_TARGET);
|
||||
const base = this.gd.baseIdOf(slot.id);
|
||||
const need = rec.isGolden ? this.gd.goldenCopies : 1;
|
||||
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
|
||||
if (handFull && !this.completesChessMerge(slot.id)) return fail(ERR.HAND_FULL);
|
||||
if (handFull) return fail(ERR.HAND_FULL);
|
||||
}
|
||||
const price = Number.isFinite(slot.price) && slot.price > 0 ? Math.trunc(slot.price) : 0;
|
||||
if (price > this.funds) return fail(ERR.NO_FUNDS);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// server/match/player/round.js — PlayerState methods: the round lifecycle the match calls — startRound (income, pending
|
||||
// coins, the shop, summon stacks topped up), endPrep, eliminate (every copy back to the pool), recompute (legality,
|
||||
// out-of-range summons, bonds), the bond views — and battleInput (the player's PlayerBattleInput: board units with
|
||||
// their loadout, carried 联防 state, the reached layers).
|
||||
// out-of-range summons, temp pieces into free hand slots, bonds), the bond views — and battleInput (the player's
|
||||
// PlayerBattleInput: board units with their loadout, carried 联防 state, the reached layers).
|
||||
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
||||
// the player state).
|
||||
|
||||
@@ -74,6 +74,8 @@ export class PlayerRound {
|
||||
this.deployMap(); // a change of the deploy field (a boss round's prep) marks the legality stale
|
||||
if (this._legalityStale) this._evictIllegal();
|
||||
this._liftOutOfRange();
|
||||
// a free regular hand slot pulls a temp piece in (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入")
|
||||
this._fillHandFromTemp();
|
||||
this.bonds = computeBonds(this.gd, this);
|
||||
this.dirty();
|
||||
}
|
||||
|
||||
+21
-1
@@ -5,7 +5,11 @@
|
||||
// players chosen by most units on the field (downed included) > has an active bond (存疑) > most undowned units, then
|
||||
// seat; with 2 helpers the one ranked first by most units > active bond > Σ active bond layers (存疑) > most undowned
|
||||
// units "率先迎敌" on the RIGHT-hand field (colOffset +8, where the escaped_multi routes enter), the other keeps the
|
||||
// left half (colOffset 0); a lone helper plays escaped_single on its own field. Their operators keep the HP ratio and
|
||||
// left half (colOffset 0); a lone helper plays escaped_single on its own field. The field is the template's own map
|
||||
// (uniteStageId: data/stages.json act1autochess_escaped_single / _multi, kind 'unite' — two road halves joined at col 10,
|
||||
// the right half's gates at col 18; GitHub #41: until 0.1.3 it was the round's stage, its water, crates and devices
|
||||
// included), every helper's pieces on their prep tiles ("按休整期位置部署在场"), the right-hand one shifted 8 columns
|
||||
// (the official maps' halves, = the stage config's player_map_lr_offset 8). Their operators keep the HP ratio and
|
||||
// the SP (技力, stored charges included) from the end of their own combat, nothing else — a skill still running then
|
||||
// enters switched off (BattleResult.unitsEnd → PlayerBattleInput.units[].carryState `{ hpPct, sp }`, "阵地以其当前状态";
|
||||
// community report #34 / GitHub #82: it used to restart for free). An operator knocked out at the end of its own combat (alive false) is fielded with
|
||||
@@ -115,6 +119,22 @@ export function helperOrder(m, perfects, results) {
|
||||
return select.sort((a, b) => S(b).units - S(a).units || (S(b).active - S(a).active) || S(b).layers - S(a).layers || S(b).standing - S(a).standing || a.seat - b.seat);
|
||||
}
|
||||
|
||||
/**
|
||||
* The map of the 联防 field (GitHub #41): act2autochess constData escapedBattleTemplateMapSinglePlayer (1 helper) /
|
||||
* MultiPlayer (2 helpers) name one level for both the 联防 wave and its map — config.unite.templates[n], built into
|
||||
* data/stages.json as the stage of the same id (kind 'unite'). null when the data has no such stage (degraded data:
|
||||
* the caller keeps the round's stage).
|
||||
* @param {import('./gamedata.js').GameData} gd
|
||||
* @param {number} helperCount
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function uniteStageId(gd, helperCount) {
|
||||
const templates = gd.unite.templates || {};
|
||||
const id = templates[String(helperCount)] ?? templates[helperCount] ?? null;
|
||||
const st = id ? gd.stage(id) : null;
|
||||
return st && st.kind === 'unite' ? id : null;
|
||||
}
|
||||
|
||||
/** Battle options for the unite field (without data/logger, added by the match). */
|
||||
export function uniteBattleOpts(m, plan, timeLimit) {
|
||||
const wave = buildUniteWave(m.gd, plan.leaked, plan.helpers.length, timeLimit);
|
||||
|
||||
Reference in new issue
Block a user