feedback1 integrate: WA per-copy 拉普兰德 refresh counter; 突变细胞 reusable
This commit is contained in:
commit
a5e78ee73b
18 files changed
+638
-60
No files matched your search
@@ -30,6 +30,12 @@
|
||||
// the one that deploys first — board.js mergeTile, [ASSUMED]); it replaces a deployed copy, so the deploy count
|
||||
// never grows, and it gets its own summon stack (grantTokensFor). Otherwise to the hand, overflow temp — also
|
||||
// outside PREP (a SETTLE merge's elite waits in temp for the next prep).
|
||||
// * Per-piece round counters (pieceRoundCount, piece.meta.round): an operator's own counts of the current round
|
||||
// (拉普兰德: the manual refreshes she witnessed — player feedback after 0.1.0); a new piece starts at 0, an elite
|
||||
// merged this round keeps the highest of its copies' [ASSUMED].
|
||||
// * Transformations (transformChess, 突变细胞): the new chess keeps the tile when legal; `returnItems` sends the old
|
||||
// piece's equipment to the hand (overflow temp) like a destroyed operator's — before the new piece's summon stack,
|
||||
// as in a merge, so a summon card never pushes the returned 突变细胞 into temp.
|
||||
// * Items: equip max 2 (a 3rd replaces the equipped item the player picks — g.equip replaceUid, the oldest when
|
||||
// absent; equipped items are otherwise locked: g.destroy refuses them),
|
||||
// 2 identical normal items (hand/temp/equipped) merge into the golden item in the hand, items are never sold
|
||||
@@ -353,6 +359,27 @@ export class PlayerState {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* starts at 0, and an elite merged this round keeps the highest count of its copies (_mergeChess) — 拉普兰德's
|
||||
* "本回合首次主动刷新" is the first manual refresh she witnesses (player feedback after 0.1.0, garrisons/meta.js).
|
||||
*/
|
||||
pieceRoundCount(piece, key) {
|
||||
const rc = piece && piece.meta && piece.meta.round;
|
||||
return rc && rc.r === this.m.round && Number.isFinite(rc.n[key]) ? rc.n[key] : 0;
|
||||
}
|
||||
|
||||
/** Add `n` to a piece's counter of the current round (pieceRoundCount); returns the new count. */
|
||||
bumpPieceRoundCount(piece, key, n = 1) {
|
||||
if (!piece || typeof key !== 'string' || !Number.isFinite(n)) return 0;
|
||||
if (!piece.meta || typeof piece.meta !== 'object') piece.meta = {};
|
||||
const v = this.pieceRoundCount(piece, key) + n;
|
||||
if (!piece.meta.round || piece.meta.round.r !== this.m.round) piece.meta.round = { r: this.m.round, n: {} };
|
||||
piece.meta.round.n[key] = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** Return a piece's pool copies (and its equipped items are handled by the caller). */
|
||||
returnCopies(piece) {
|
||||
if (piece && piece.kind === 'chess' && piece.poolCopies > 0) {
|
||||
@@ -503,6 +530,12 @@ export class PlayerState {
|
||||
l.piece.items = [];
|
||||
}
|
||||
const elite = this.newPiece('chess', goldenId, { poolCopies: copies });
|
||||
// this round's per-piece counters: the highest of the copies' (an elite made from 拉普兰德 that already saw their
|
||||
// first refresh this round does not fire again this round — [ASSUMED] conservative, pieceRoundCount)
|
||||
for (const l of consumed) {
|
||||
const rc = l.piece.meta && l.piece.meta.round;
|
||||
if (rc && rc.r === this.m.round) for (const [k, v] of Object.entries(rc.n)) this.bumpPieceRoundCount(elite, k, Math.max(0, v - this.pieceRoundCount(elite, k)));
|
||||
}
|
||||
const deployed = consumed.filter((l) => l.key && !this.board.has(l.key)).map((l) => ({ key: l.key, dir: l.area === 'new' ? l.dir : pieceDir(l.piece) }));
|
||||
const toTile = (t) => { elite.dir = parseDir(t.dir) || 'RIGHT'; this.board.set(t.key, elite); return 'board'; };
|
||||
const tile = mergeTile(deployed, (r, c) => this._legal(elite, r, c));
|
||||
@@ -548,10 +581,17 @@ export class PlayerState {
|
||||
|
||||
/**
|
||||
* Replace a chess piece by another chess (突变细胞 and similar). The new piece keeps the tile when legal (else goes
|
||||
* to the hand/temp) and keeps the equipment; pool copies are swapped; completes a merge when possible — a deployed
|
||||
* piece's tile then counts as a consumed copy's for the elite (_mergeChess fromKey, when legal for it).
|
||||
* to the hand/temp) and keeps the equipment — or, with `returnItems`, the equipment returns to the hand like the
|
||||
* equipment of a destroyed / promoted operator (突变细胞: PRTS 备注 "生效时,原干员销毁,获得一名高一阶的随机初始干员"; the
|
||||
* cell itself comes back, player feedback after 0.1.0), overflowing into temp; with both full an item stays on the new
|
||||
* piece up to its equipPerChess slots (any further one is destroyed with a log warning, as in a merge). Pool copies are
|
||||
* swapped; completes a merge when possible — a deployed piece's tile then counts as a consumed copy's for the elite
|
||||
* (_mergeChess fromKey, when legal for it), and the merge returns the equipment itself. A new piece on the board gets
|
||||
* its summon stack only after the equipment came back (like _mergeChess: a summon card must not push the returned
|
||||
* equipment — 突变细胞 — into temp, where it would be lost at the deadline).
|
||||
* @param {{ returnItems?: boolean }} [opts]
|
||||
*/
|
||||
transformChess(piece, newId) {
|
||||
transformChess(piece, newId, { returnItems = false } = {}) {
|
||||
const loc = this.find(piece.uid);
|
||||
const rec = this.gd.chess(newId);
|
||||
if (!loc || loc.piece.kind !== 'chess' || !rec) return null;
|
||||
@@ -563,14 +603,26 @@ export class PlayerState {
|
||||
const base = this.gd.baseIdOf(newId);
|
||||
const taken = this.m.pool.take(base, rec.isGolden ? this.gd.goldenCopies : 1);
|
||||
let np = this.newPiece('chess', newId, { poolCopies: taken });
|
||||
np.items = items;
|
||||
if (!rec.isGolden && this.completesChessMerge(newId)) {
|
||||
const merging = !rec.isGolden && this.completesChessMerge(newId);
|
||||
// a merge returns the consumed copies' equipment (this one's too) before the elite's summons (_mergeChess)
|
||||
const pending = returnItems && !merging ? items : [];
|
||||
np.items = returnItems && !merging ? [] : items;
|
||||
let deployed = false;
|
||||
if (merging) {
|
||||
np = this._mergeChess(base, np, loc.area === 'board' ? { fromKey: loc.key, fromDir: pieceDir(piece) } : undefined);
|
||||
if (!np) { this.recompute(); return null; }
|
||||
} else if (loc.area === 'board') {
|
||||
const [r, c] = parseKey(loc.key);
|
||||
if (this._legal(np, r, c)) { np.dir = pieceDir(piece); this.board.set(loc.key, np); this.grantTokensFor(np); } else if (!this.stow(np, { allowTemp: true })) { this.returnCopies(np); np = null; }
|
||||
if (this._legal(np, r, c)) { np.dir = pieceDir(piece); this.board.set(loc.key, np); deployed = true; } else if (!this.stow(np, { allowTemp: true })) { this.returnCopies(np); np = null; }
|
||||
} else if (!this._putBack(loc, np)) { this.returnCopies(np); np = null; }
|
||||
// after the new piece took its place (a hand piece keeps its slot), its predecessor's equipment comes off
|
||||
for (const it of pending) {
|
||||
if (this.stow(it, { allowTemp: true })) continue;
|
||||
if (np && np.items.length < this.gd.equipPerChess) { np.items.push(it); continue; }
|
||||
this.m.log.warn?.(`[match ${this.m.roomCode}] ${this.playerId}: returned item ${it.id} destroyed (no space)`);
|
||||
}
|
||||
if (returnItems) this.checkItemMerges();
|
||||
// deployed in its predecessor's place: its manually deployable summons join the hand, after the returned equipment
|
||||
if (deployed) this.grantTokensFor(np);
|
||||
this.recompute();
|
||||
if (np) this.m.dispatch(this, 'onGain', { piece: np, kind: 'chess', source: 'transform' });
|
||||
return np;
|
||||
|
||||
+56
-5
@@ -35,8 +35,11 @@
|
||||
// order as the one-shot functions (runSteps), hence the same rng draws and decisions.
|
||||
// The summon cards of the placed operators (赫默's 医疗探机, 伺夜's 狼群 …; user playtest #6) are placed after
|
||||
// them on the best remaining tiles, 凯瑟琳's 支援装置 next to the best operator no device faces yet, facing it.
|
||||
// 5. equip items on the strongest deployed damage dealers (consume-on-equip items / Arts only with a handler)
|
||||
// 6. resolve the temp slots, keep one hand slot free, then Ready.
|
||||
// 5. equip items on the strongest deployed damage dealers (consume-on-equip items / Arts only with a handler);
|
||||
// 突变细胞 (it comes back after every transformation) on the least valuable single normal operator below 6阶 —
|
||||
// never an elite or one of a merge pair (cellTarget)
|
||||
// 6. resolve the temp slots (a 突变细胞 left there gets a hand slot made for it — makeHandRoom — instead of being
|
||||
// destroyed), keep one hand slot free, then Ready.
|
||||
// Placement quality (tools/matchrun sweeps, research-faithful waves): the planner beats random layouts by ≈ 8 points
|
||||
// of kill rate and rehearsal adds ≈ 5 more; see docs/META.md §1.5.
|
||||
|
||||
@@ -804,12 +807,15 @@ function context(m, ps) {
|
||||
return { owned, focus: focusBond(m, ps, owned), roles: roles(m, ps), fly: model.flyTotal, model };
|
||||
}
|
||||
|
||||
/** Sell the weakest bench chess that is not part of a merge pair (or anything when keepPairs is false). */
|
||||
function sellWeakestHand(m, ps, { keepPairs = true, below = Infinity } = {}) {
|
||||
/**
|
||||
* Sell the weakest bench chess (temp and hand; the hand only with `handOnly`) that is not part of a merge pair (or
|
||||
* anything when keepPairs is false).
|
||||
*/
|
||||
function sellWeakestHand(m, ps, { keepPairs = true, below = Infinity, handOnly = false } = {}) {
|
||||
const ctx = context(m, ps);
|
||||
let worst = null;
|
||||
let worstV = Infinity;
|
||||
for (const p of [...ps.temp, ...ps.hand]) {
|
||||
for (const p of handOnly ? ps.hand : [...ps.temp, ...ps.hand]) {
|
||||
if (!p || p.kind !== 'chess') continue;
|
||||
const base = m.gd.baseIdOf(p.id);
|
||||
if (keepPairs && !m.gd.isGolden(p.id) && ps.countCopies(base) >= 2) continue;
|
||||
@@ -1159,6 +1165,27 @@ function* placeTokensSteps(m, ps) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 突变细胞 (buff char_chess_transformation_equip): after the battle its carrier becomes a random operator one tier higher. */
|
||||
const isMutationCell = (gd, itemId) => { const rec = gd.item(itemId); return !!(rec && Array.isArray(rec.buffs) && rec.buffs.some((b) => b && b.key === 'char_chess_transformation_equip')); };
|
||||
|
||||
/**
|
||||
* Whom the bot injects with 突变细胞 (after the battle the carrier is replaced by a random NORMAL operator one tier
|
||||
* higher and the cell comes back — builtinMeta char_chess_transformation_equip): its least valuable normal operator
|
||||
* below 6阶 with a free equip slot — never an elite or one of a merge pair (both would be lost), nobody already carrying
|
||||
* a cell. null: the cell waits in the hand.
|
||||
*/
|
||||
export function cellTarget(m, ps, ctx = context(m, ps)) {
|
||||
const gd = m.gd;
|
||||
const cands = ps.allChess().filter((p) => !gd.isGolden(p.id) && gd.tierOf(p.id) < 6 && (p.items || []).length < gd.equipPerChess
|
||||
&& !(p.items || []).some((it) => isMutationCell(gd, it.id)) && ps.countCopies(gd.baseIdOf(p.id)) < 2);
|
||||
let best = null, bv = Infinity;
|
||||
for (const p of cands) {
|
||||
const v = pieceValue(m, ps, p, ctx);
|
||||
if (v < bv) { bv = v; best = p; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function equipItems(m, ps) {
|
||||
const gd = m.gd;
|
||||
const ctx = context(m, ps);
|
||||
@@ -1168,6 +1195,11 @@ function equipItems(m, ps) {
|
||||
const item = [...ps.hand, ...ps.temp].find((p) => p && p.kind === 'item' && canUseItem(m, ps, p) && !tried.has(p.uid));
|
||||
if (!item) break;
|
||||
tried.add(item.uid);
|
||||
if (isMutationCell(gd, item.id)) {
|
||||
const target = cellTarget(m, ps, ctx);
|
||||
if (target) tryDo(() => ps.equip(item.uid, target.uid));
|
||||
continue;
|
||||
}
|
||||
const rec = gd.item(item.id);
|
||||
if (rec && rec.itemType === 'MAGIC') {
|
||||
const board = [...ps.board.entries()].filter(([, p]) => p.kind === 'chess');
|
||||
@@ -1187,6 +1219,20 @@ function equipItems(m, ps) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a hand slot for an item worth keeping (突变细胞): sell the weakest single hand chess, else destroy the cheapest
|
||||
* other hand item, else sell the weakest hand chess even of a pair. 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;
|
||||
const gd = m.gd;
|
||||
const junk = 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];
|
||||
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;
|
||||
}
|
||||
|
||||
function resolveTemp(m, ps) {
|
||||
for (let i = 0; i < ps.temp.length; i++) {
|
||||
const p = ps.temp[i];
|
||||
@@ -1200,6 +1246,11 @@ function resolveTemp(m, ps) {
|
||||
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): make room in the hand rather than lose it
|
||||
if (isMutationCell(m.gd, p.id) && makeHandRoom(m, ps)) {
|
||||
const j = freeSlot(ps.hand);
|
||||
if (j >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }))) continue;
|
||||
}
|
||||
tryDo(() => ps.destroy(p.uid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
// use_equip_recruit_new_char_and_give_char_to_player_most_bond {refresh_cnt} 信标 destroy target, offer N same-tier
|
||||
// chess, gift the original next prep
|
||||
// sell_char_count_gain_equip_owner_bond {count} 商业包装方案 every `count` sells → same-bond chess
|
||||
// char_chess_transformation_equip 突变细胞 after battle: holder → random tier+1
|
||||
// char_chess_transformation_equip 突变细胞 after battle: holder → random NORMAL tier+1
|
||||
// (max 6); its equipment, the cell included,
|
||||
// returns to the hand (not consumed)
|
||||
// trap_copy_front_char 画卷 (Art) copy the chess on the tile / in front
|
||||
// trap_create_self_choice {choice_event} 教鞭 / 神秘顾客 (Art) add a random bounty to your next battle
|
||||
// [ASSUMED simplifications, documented in docs/META.md: 教鞭/神秘顾客 pick the bounty for the player instead of opening
|
||||
// a personal choice overlay; 突变细胞 consumes itself.]
|
||||
// [ASSUMED simplification, documented in docs/META.md: 教鞭/神秘顾客 pick the bounty for the player instead of opening
|
||||
// a personal choice overlay.]
|
||||
|
||||
import { getData } from '../data.js';
|
||||
import { itemKey } from './gamedata.js';
|
||||
@@ -175,15 +177,20 @@ const ITEM_HANDLERS = {
|
||||
}
|
||||
},
|
||||
},
|
||||
// 突变细胞 "战斗结束后,装备者替换为高一阶的随机干员": the carrier is replaced by a random NORMAL operator one tier higher
|
||||
// (at most 6; an elite carrier too — PRTS 卫戍协议:盟约 下半/PRTS盟约记录 备注 "生效时,原干员销毁,获得一名高一阶的随机
|
||||
// 初始干员(最高六阶)"); the destroyed operator's equipment, the cell included, returns to the hand (PRTS 卫戍协议/帮助
|
||||
// "佩戴的装备无法手动卸除,在失去该干员(干员出售、销毁、合并等)或装备合并为进阶品质时自动卸除"; the official text never
|
||||
// says 销毁 for the cell — unlike every consumable item —, and players re-inject it every round: "之后就是一直打针,扎到
|
||||
// 核心卡…就换人扎", bilibili cv47000418; player feedback after 0.1.0). The new operator keeps the tile when legal.
|
||||
char_chess_transformation_equip: {
|
||||
onBattleResult(ctx) {
|
||||
const { piece, holder } = ctx.source;
|
||||
if (!piece || !holder) return;
|
||||
if (!piece || !holder || !ctx.piece(holder.uid)) return;
|
||||
const tier = Math.min(6, ctx.gd.tierOf(holder.id) + 1);
|
||||
const id = ctx.rollChess({ tier });
|
||||
if (!id) return;
|
||||
ctx.destroyPiece(piece.uid);
|
||||
ctx.transform(holder.uid, id);
|
||||
ctx.transform(holder.uid, ctx.gd.baseIdOf(id), { returnItems: true });
|
||||
},
|
||||
},
|
||||
trap_copy_front_char: {
|
||||
|
||||
@@ -440,6 +440,10 @@ export function makeCtx(m, ps, source, hook, ev = null) {
|
||||
counter: (k) => (Number.isFinite(ps.counters[k]) ? ps.counters[k] : 0),
|
||||
setCounter: (k, v) => { if (typeof k === 'string' && Number.isFinite(v)) ps.counters[k] = v; return ps.counters[k] ?? 0; },
|
||||
incCounter: (k, n = 1) => { if (typeof k !== 'string' || !Number.isFinite(n)) return 0; ps.counters[k] = (Number.isFinite(ps.counters[k]) ? ps.counters[k] : 0) + n; return ps.counters[k]; },
|
||||
/** Per-piece counter of the current round (0 in a new round / for a new piece; an elite merged this round keeps the
|
||||
* highest of its copies' — PlayerState.pieceRoundCount). */
|
||||
pieceCounter: (uid, k) => { const l = ps.find(uid); return l ? ps.pieceRoundCount(l.piece, k) : 0; },
|
||||
incPieceCounter: (uid, k, n = 1) => { const l = ps.find(uid); return l ? ps.bumpPieceRoundCount(l.piece, k, n) : 0; },
|
||||
|
||||
// ---- economy
|
||||
addFunds: (n, reason = '') => ps.addFunds(finiteInt(n), { reason }),
|
||||
@@ -498,8 +502,9 @@ export function makeCtx(m, ps, source, hook, ev = null) {
|
||||
return m.dispatcher.triggerGarrisons(ps, l.piece, eventType, { asPiece: as ? as.piece : null, triggeredBy: source.key || null });
|
||||
},
|
||||
promote: (uid) => { const l = ps.find(uid); return l ? ps.promote(l.piece) : false; },
|
||||
/** Replace a chess piece by another chess (keeps its tile when legal, keeps its equipment). */
|
||||
transform: (uid, chessId) => { const l = ps.find(uid); if (!l || l.piece.kind !== 'chess') return null; const p = ps.transformChess(l.piece, chessId); return p ? view(p) : null; },
|
||||
/** Replace a chess piece by another chess (keeps its tile when legal, keeps its equipment — `{ returnItems: true }`:
|
||||
* the equipment returns to the hand instead, as from a destroyed operator; PlayerState.transformChess). */
|
||||
transform: (uid, chessId, opts = {}) => { const l = ps.find(uid); if (!l || l.piece.kind !== 'chess') return null; const p = ps.transformChess(l.piece, chessId, { returnItems: !!(opts && opts.returnItems) }); return p ? view(p) : null; },
|
||||
/** Normal item piece → its golden version in place. */
|
||||
upgradeItem: (uid) => { const l = ps.find(uid); return l && l.piece.kind === 'item' ? ps.upgradeItem(l.piece) : false; },
|
||||
/** Attach an owned hand/temp item to an owned chess without equip effects (copies, restores). */
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
// * [ASSUMED] 余 SERVER_MOST_BOND: ties between most-member bonds are shuffled; the chess is a copy-weighted pool roll
|
||||
// of any tier (the text gives no tier cap); a bond without an available chess falls through to the next tied one.
|
||||
// * [ASSUMED] 松果: the "免费特殊招募" is a free pick-one offer of `rewardOffer.count` (3) chess of the pool's tier.
|
||||
// * 拉普兰德 SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT "若为本回合首次主动刷新": per copy — the first manual refresh this
|
||||
// operator witnesses in the round (players' report after 0.1.0); [ASSUMED] an elite merged this round keeps its
|
||||
// copies' count, and a copy bought after selling one this round is a new copy (fires on its own first refresh).
|
||||
// "本回合每刷新过1次" (SERVER_ADD_REFRESH_CNT_MULTIPLIER_BOND_LAYER, 阿罗玛 / 安洁莉娜 / 售出时)
|
||||
// fires on another event and reads the player's refreshes of the round (roundStats), like 本回合每获得过 / 每花费.
|
||||
|
||||
import { metaBonds, frontPiece, behindPiece, distinctTiers } from '../support/meta.js';
|
||||
|
||||
@@ -188,11 +193,22 @@ H.SERVER_ADD_REFRESH_CNT_MULTIPLIER_BOND_LAYER = {
|
||||
},
|
||||
};
|
||||
|
||||
// 拉普兰德 "<刷新时>若为本回合首次主动刷新…,此干员在整备区时也有效": the refresh count is the operator's own — the manual
|
||||
// refreshes this copy witnessed this round (board or hand), so a 拉普兰德 bought after the round's first refresh still
|
||||
// fires on the next one (players' report after 0.1.0: "获得该干员后该回合的首次刷新" also stacks — the official behaviour;
|
||||
// read as each trait instance counting its own SERVER_REFRESH_SHOP triggers against bb.refresh_cnt). A re-triggered trait
|
||||
// (ev.trigger) is no manual refresh: it neither fires nor counts. A new copy (bought, granted) starts at 0. [ASSUMED]:
|
||||
// the copies of an elite merged this round pass on their highest count (PlayerState.pieceRoundCount — no second trigger
|
||||
// that round, conservative); a copy bought after selling one this round is a new copy — "获得该干员后" — and fires on its
|
||||
// own first refresh (the server cannot tell it from any other copy; each such +4 costs her price + a refresh − the
|
||||
// 1-fund refund, and needs her in the shop again).
|
||||
const REFRESH_CNT_KEY = 'garrison:SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT:refreshes'; // per-piece counter (module-prefixed)
|
||||
H.SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT = {
|
||||
onRefresh(ctx, ev) {
|
||||
if (ev && ev.trigger) return;
|
||||
const { bb, bbStr, garrison } = ctx.source;
|
||||
if ((ctx.roundStats().refreshes | 0) !== num(bb.refresh_cnt, 1)) return;
|
||||
const { bb, bbStr, garrison, piece } = ctx.source;
|
||||
if (!piece || !Number.isInteger(piece.uid) || piece.uid <= 0) return;
|
||||
if (ctx.incPieceCounter(piece.uid, REFRESH_CNT_KEY) !== num(bb.refresh_cnt, 1)) return;
|
||||
addAll(ctx, ids(bbStr.bond), num(bb.layer), requireActiveOf(garrison));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
// The engine built-ins (server/match/builtinMeta.js) already implement most consume-on-equip items and Arts from the
|
||||
// concrete item's params; they are reviewed and kept (盟约之币, 骑士储蓄罐, 随身身份牌, 精打细算玩偶, 简易通讯机 (tier ≤
|
||||
// shop level), 见钱眼开玩偶, 人事部文档 (cap 9), 博士投影 (golden now / normal at the next round start), 拟态物质, 信标,
|
||||
// 商业包装方案 (count from the concrete record: 8 / golden 7)). The Arts' rangeGrid (画卷 = placed tile + the tile in
|
||||
// front) and the per-round limit are engine rules.
|
||||
// 商业包装方案 (count from the concrete record: 8 / golden 7), 突变细胞 (after the battle the carrier becomes a NORMAL
|
||||
// random tier+1 operator, max 6; its equipment — the cell too, which is not consumed (player feedback after 0.1.0) —
|
||||
// returns to the hand)). The Arts' rangeGrid (画卷 = placed tile + the tile in front) and the per-round limit are engine
|
||||
// rules.
|
||||
// Overridden / added here (a built-in, when present, is wrapped — never re-implemented):
|
||||
// 画卷 the copy keeps the target's items; a copied normal item that pairs with an owned one merges and the
|
||||
// golden stays in the hand (the built-in equipped the merged golden on the copy, and its live loop over
|
||||
// the target's items skipped the item after a merge)
|
||||
// 紧急调度券 a shop operator leaves its slot only when it was actually granted (built-in cleared the slot first)
|
||||
// 寻呼模块 the special refresh shows `refresh_cnt` DIFFERENT operators (fewer when the pool has no more)
|
||||
// 突变细胞 after the battle the carrier becomes a NORMAL random tier+1 operator; its other equipment goes back to
|
||||
// the hand (built-in kept it on the new operator)
|
||||
// 教鞭 trap_create_self_choice {choice_event: hunter_band_1}: the bounty is a 战术特训 card (choices.json
|
||||
// 教鞭 trap_create_self_choice {choice_event: hunter_band_1}: the bounty is a 战术特训 card (choices.json
|
||||
// cards.bounty payout `perfect`, e.g. 战术特训·飞行I "若各自行动阶段就达成完美作战,获得1资金") — PRTS
|
||||
// 卫戍协议:盟约 下半/PRTS盟约记录 §法术 教鞭 "于3个战术特训的悬赏任务中选择一项", §机变阶段 "※以下悬赏任务仅由
|
||||
// 法术教鞭生成", and 杜宾 加练! "<教鞭>:使用后为下场战斗添加额外敌人,若自身战斗完美作战可获得资金" (user
|
||||
@@ -103,23 +103,6 @@ export function registerMeta(registry) {
|
||||
},
|
||||
}));
|
||||
|
||||
// 突变细胞 — char_chess_transformation_equip
|
||||
wrap(registry, 'chess_item_5_08_e', () => ({
|
||||
onBattleResult(ctx) {
|
||||
const { piece, holder } = ctx.source;
|
||||
if (!piece || !holder) return;
|
||||
const tier = Math.min(6, ctx.gd.tierOf(holder.id) + 1);
|
||||
const id = ctx.rollChess({ tier });
|
||||
if (!id) return;
|
||||
const others = (ctx.piece(holder.uid)?.items || holder.items || []).filter((it) => it && it.uid !== piece.uid);
|
||||
ctx.destroyPiece(piece.uid);
|
||||
for (const it of others) {
|
||||
if (ctx.destroyPiece(it.uid)) ctx.grantItem(it.id, { source: 'mutation' });
|
||||
}
|
||||
ctx.transform(holder.uid, ctx.gd.baseIdOf(id));
|
||||
},
|
||||
}));
|
||||
|
||||
// 教鞭 / “神秘顾客” — trap_create_self_choice {choice_event}: 教鞭 a 战术特训 card, “神秘顾客” a band bounty (enemyeffect_b_*)
|
||||
for (const [key, family] of [['chess_item_6_03_m', isTraining], ['chess_item_6_01_m', isBandBounty]]) {
|
||||
wrap(registry, key, (base) => ({
|
||||
|
||||
Reference in new issue
Block a user