diff --git a/docs/META.md b/docs/META.md index 4d6573e6..6a3012b7 100644 --- a/docs/META.md +++ b/docs/META.md @@ -431,8 +431,10 @@ Writes (all validated, never throw on bad input, never make funds / pools negati ### 2.5 Items: consume-on-equip and Arts Items whose data `kind` starts with `consume_on_equip` resolve through their `item:` handler's `onEquip` and are destroyed (they never take a slot); set `ev.keep = true` to keep the item equipped instead (博士投影 normal), or -`ev.error = 'BAD_TARGET'` (+ `ev.detail`) to refuse. Without a registered handler the equip is refused -(`BAD_TARGET 'effect not available'`). Arts (`MAGIC`): `g.art` needs a handler; at most `maxArtsPerRound` (2) per round; +`ev.error = 'BAD_TARGET'` (+ `ev.detail`) to refuse. On a full carrier the item `replaceUid` names (else the oldest) comes +off before `onEquip` runs and is destroyed (`onDestroy` reason `replace`) once the effect went through — the carrier keeps +a free slot, which a kept item takes —, and goes back where it was when the effect refuses (GitHub #263). Without a +registered handler the equip is refused (`BAD_TARGET 'effect not available'`). Arts (`MAGIC`): `g.art` needs a handler; at most `maxArtsPerRound` (2) per round; `ev.targets` are the pieces under the Art's `rangeGrid` at (row, col); set `ev.error` to refuse, `ev.used = false` to keep it. Other equipment: 2 slots; a third replaces the equipped item the player picks in the replace dialog — `g.equip { itemUid, targetUid, replaceUid }` (research 09 §1.2 `UseEquipUp.unloadInstId`; absent ⇒ the oldest; a `replaceUid` not diff --git a/docs/PLAYING.md b/docs/PLAYING.md index 7d602d6a..f5f5d085 100644 --- a/docs/PLAYING.md +++ b/docs/PLAYING.md @@ -46,7 +46,7 @@ | 招募价格(按阶级 I–VI) | 2 / 3 / 3 / 3 / 4 / 4 | | 刷新 | 1 资金 | | 出售干员 | 任何干员(含精锐)都只返还 **1** 资金 | -| 装备 / 法术 | 调度中心出售除特殊装备外的所有装备:4 把特殊维式重锤(战栗 / 坚固 / 加速 / 灼燃)只来自维多利亚盟约(每 25 层)和洛洛的定制品,突变细胞只来自昆图斯的策略。装备不能出售,整备区里的只能「销毁」(不返还)。已配发的装备锁定在干员身上:只能被第三件装备替换(弹出替换窗口,选择要替换的一件,它会被销毁),或在干员出售 / 合成精锐时退回整备区。**突变细胞**不会用完:战斗结束后装备者被销毁(部署在场上的话,原来的格子空出来,剩余可放置角色加 1),突变细胞和它的其他装备先退回整备区,可以再次配发给任意干员;随后获得一名高一阶(最高 VI)的随机普通干员(装备者是精锐也一样),和其他新获得的干员一样进入整备区(满了进入临时整备区),需要自己重新部署;如果它凑齐了合成所需的同名干员,照常合成精锐。**坚固维式重锤**首次受到致命伤害时锁血 8 秒,**每次部署一次**:被击倒再部署(或突袭跳跃再部署、被 M3茧甲 / 埃芒加德复活)后又能锁一次,娜仁图亚策略借来的锤子也一样;借用到期时正在锁血的会锁满 8 秒;站在阿戈尔干员身前被开场吞噬、自带源石溶剂掉血时也会用掉这一次 | +| 装备 / 法术 | 调度中心出售除特殊装备外的所有装备:4 把特殊维式重锤(战栗 / 坚固 / 加速 / 灼燃)只来自维多利亚盟约(每 25 层)和洛洛的定制品,突变细胞只来自昆图斯的策略。装备不能出售,整备区里的只能「销毁」(不返还)。已配发的装备锁定在干员身上:只能被第三件装备替换(弹出替换窗口,选择要替换的一件,它会被销毁;盟约之币、博士投影等装备即销毁的道具也一样,先销毁选中的那件再生效,干员留下一个空栏位,精锐干员不能使用博士投影,此时什么都不销毁),或在干员出售 / 合成精锐时退回整备区。**突变细胞**不会用完:战斗结束后装备者被销毁(部署在场上的话,原来的格子空出来,剩余可放置角色加 1),突变细胞和它的其他装备先退回整备区,可以再次配发给任意干员;随后获得一名高一阶(最高 VI)的随机普通干员(装备者是精锐也一样),和其他新获得的干员一样进入整备区(满了进入临时整备区),需要自己重新部署;如果它凑齐了合成所需的同名干员,照常合成精锐。**坚固维式重锤**首次受到致命伤害时锁血 8 秒,**每次部署一次**:被击倒再部署(或突袭跳跃再部署、被 M3茧甲 / 埃芒加德复活)后又能锁一次,娜仁图亚策略借来的锤子也一样;借用到期时正在锁血的会锁满 8 秒;站在阿戈尔干员身前被开场吞噬、自带源石溶剂掉血时也会用掉这一次 | | 冻结 | 免费,保留当前所有未买的格子到下一回合;冻结状态下仍可主动刷新 | | 升级调度中心(1→2 … 5→6) | 险境及以上 5 / 8 / 11 / 12 / 13;同盟标准 5 / 8 / 10 / 11 / 11;独立标准 1 / 1 / 5 / 8 / 10。**每过一回合价格 −1**(最低 0),升级后按下一级的价格重新计算。升级到干员栏位更多的等级(险境及以上 2 级 4 个、4 级 5 个;标准 2 级 4 个、6 级 5 个)时,**新栏位立刻刷出一名新干员**,已有的卡片不变 | | 其他收入 | 悬赏敌人、盟约「远见」「奇迹」、装备「骑士储蓄罐」、部分策略和特质 | diff --git a/docs/design/match.md b/docs/design/match.md index dfc67619..9e4338b0 100644 --- a/docs/design/match.md +++ b/docs/design/match.md @@ -56,7 +56,7 @@ Intent handlers (validate → mutate → recompute bonds → push private view): Summons (§20.1): an operator on the board sends its manually deployable summons to the hand — deploy-limit copies in one stack, no deploy slot (tokens.json `placeable`, `gamedata.placeableTokens`); moving or swapping it sends its placed summons back to their stack, withdrawing or selling it removes them. A range-bound summon (tokens.json `ownerRange`: 狼群, 流形) goes only on a tile of its owner's attack range (`PlayerState._legal` / `summonRange`); in a swap each piece is checked from the tile it ends on; an in-place re-orientation or promotion that leaves it outside sends it back to its stack with a toast (one still inside stays [ASSUMED], user-confirmed 2026-10-03), and a re-orientation whose lifted summon would have no room is refused with `HAND_FULL`; on the other paths it leaves the board and returns at the next round start (§21.3). -Equipment (research 04 §2 + addendum, 09 §1.2): 2 slots per chess; a third item on a full carrier replaces the equipped item `replaceUid` names (the replace dialog's pick, official `UseEquipUp.unloadInstId`; absent ⇒ the oldest; a uid not equipped on the target ⇒ `BAD_TARGET`, nothing changes) and the replaced item is destroyed. Equipped items are otherwise **locked**: `destroy` (销毁) accepts hand / temp items only (`BAD_TARGET 'equipped items are locked'`); equipment goes back to the hand only when its carrier merges, is sold, or is destroyed by 突变细胞's transformation (the cell itself returns too; PRTS 帮助 "在失去该干员(干员出售、销毁、合并等)…时自动卸除", §21.1) — a sale is refused with `HAND_FULL` when there is no room for it; returned equipment is stowed before the operator a transformation gains and before a merge elite's summon cards, and every path that hands the player an item — returned equipment included — ends with the auto-merge, so no player holds two identical mergeable normal items (§21.7). +Equipment (research 04 §2 + addendum, 09 §1.2): 2 slots per chess; a third item on a full carrier replaces the equipped item `replaceUid` names (the replace dialog's pick, official `UseEquipUp.unloadInstId`; absent ⇒ the oldest; a uid not equipped on the target ⇒ `BAD_TARGET`, nothing changes) and the replaced item is destroyed. A consume-on-equip item (盟约之币, 博士投影 …) on a full carrier replaces the same way before it resolves, so the carrier is left with a free slot (the normal 博士投影 takes it until the next round start); a target its effect refuses (博士投影 on an elite) keeps both items — PRTS 卫戍协议/帮助 "达到上限强行佩戴会改为替换装备" names no exception (GitHub #263; the client opens the replace dialog for it, `equipReplaces`). Equipped items are otherwise **locked**: `destroy` (销毁) accepts hand / temp items only (`BAD_TARGET 'equipped items are locked'`); equipment goes back to the hand only when its carrier merges, is sold, or is destroyed by 突变细胞's transformation (the cell itself returns too; PRTS 帮助 "在失去该干员(干员出售、销毁、合并等)…时自动卸除", §21.1) — a sale is refused with `HAND_FULL` when there is no room for it; returned equipment is stowed before the operator a transformation gains and before a merge elite's summon cards, and every path that hands the player an item — returned equipment included — ends with the auto-merge, so no player holds two identical mergeable normal items (§21.7). Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (income `min(3+r,12)`; prices by tier; refresh 1; freeze toggles all; sell +1; items not sellable; upgrade prices per mode with −1/round; shared pool copies 12/14/18/16/8/5 (缪尔赛思 4) across all players; elite = 3 copies; odds = copy-weighted draw over unbanned visible chess with tier ≤ level; merge 3 (风丸 2) → elite onto the consumed deployed copy's tile, else to the hand (§20.11) + reward offer of 3 **different** free chess of tier `min(level+1, 6)` (copy-weighted; a tier short of different chess tops up from the tier below [ASSUMED], §20.6); full hand blocks every buy / reward pick, a merge-completing one too (PRTS 卫戍协议/帮助 §手牌区, GitHub #82); overflow to temp; a free hand slot pulls the temp pieces back in at once, right → left (`PlayerState._fillHandFromTemp`); temp blocks ready). diff --git a/docs/research/04-items.md b/docs/research/04-items.md index c2141c46..9d8c1530 100644 --- a/docs/research/04-items.md +++ b/docs/research/04-items.md @@ -11,7 +11,7 @@ Tags: **[DATA]** = read directly from game data, **[WEB]** = community/official - Shop (调度中心) has **1 item slot at every shop level** in all real modes (`shopLevelDataDict[*][L].itemCount = 1`; training mode 0 until L5). Season 1 only had items from shop level 5. [DATA] - **Max 2 equipment per operator**; equipped items are locked; a 3rd equip = replace (the replaced item is destroyed). Items return to hand when the operator is promoted (3->elite) or sold. [WEB: PRTS 卫戍协议/帮助, arknights.wiki.gg, in-game tip] - **2 identical normal items auto-merge into 1 golden** (`upgradeNum=2`). `upgradeNum=100` = never merges (寻呼模块, 信标, 拟态物质, 突变细胞, 人事部文档, 变形同构体; talent note "本装备不会合并为进阶品质"). [DATA] -- Many items are **consumed on equip** ("装备时销毁"): economy/recruit effects. They need an operator target but never take a slot. [DATA] +- Many items are **consumed on equip** ("装备时销毁"): economy/recruit effects. They need an operator target but never take a slot. [DATA] On a full carrier they still replace first: the equipped item the player picks is destroyed, then the item resolves, leaving a free slot (PRTS 帮助 "达到上限强行佩戴会改为替换装备" names no exception; GitHub #263, 0.2.1). - `trapDuration`: -1 on all normal EQUIP (permanent), 0 on golden & MAGIC (unused by us). `canGiveBond=true` only on 变形同构体; `giveBondId` is set on 18 items (14 bonds) and is the lookup table used by 变形同构体. `givePowerId` always null. [DATA] - **MAGIC (法术)**: 画卷, 教鞭, “神秘顾客”; all `hideInShop=true`; only granted by strategies (bands). Drag onto a map tile during prep; instant. Limit: max **2** current-round Arts on the map; all Arts removed at round end. [DATA + WEB wiki.gg] @@ -26,7 +26,7 @@ Tags: **[DATA]** = read directly from game data, **[WEB]** = community/official | Merge | 2 x same normal (`_a`, upgradeNum 2) -> 1 golden (`_b`), automatic, immediate | [DATA]+[WEB] tip "两件同名装备可以合成一件更强力的装备" | | Merge counts equipped copies? | Yes: copies in hand and on operators both count; resulting golden goes to hand (slot freed) | [ASSUMED] (consistent with "道具晋级" being a removal condition) | | Non-mergeable | upgradeNum 100 items (list above); golden entries have upgradeNum 0 | [DATA] | -| Consume-on-equip | Effects tagged `kind=consume_on_equip` resolve once on drop, item destroyed, no slot used | [DATA] | +| Consume-on-equip | Effects tagged `kind=consume_on_equip` resolve once on drop, item destroyed, no slot used; on a full carrier the replace above comes first (a free slot is left) | [DATA]; [WEB] PRTS 帮助, GitHub #263 | | Hand capacity | Items share the 10 regular hand slots with operators (+5 temporary) | [DATA] constData.maxDeckChessCnt=10; [WEB] PRTS 帮助 | | Shop item slot | 1 slot per refresh at all shop levels (L1-L6) | [DATA] shopLevelDataDict.itemCount | | Shop item pool | Non-hidden normal EQUIP with tier <= shop level (mirrors operator rule "调度中心等级≥干员所在等阶") | [ASSUMED] | diff --git a/public/js/ui/gameLogic.js b/public/js/ui/gameLogic.js index 0e1a48f8..a3082bd2 100644 --- a/public/js/ui/gameLogic.js +++ b/public/js/ui/gameLogic.js @@ -34,7 +34,7 @@ export { sortedPlayers } from './gameLogic/shared.js'; export { ownFieldId, homeFieldId, cycleField, watchTarget, switcherLabel, fieldLabel, activeBubbles } from './gameLogic/watch.js'; export { sortBonds, bondTier, nextThreshold, grantedBonds, pieceBondIds, morphPairings, HARMONY_BOND, harmonyMembers, bondMembers, memberHeadCount, bannedPerBond, disabledBondSets, briefingBondTip, modeOffBonds, bandOffBonds, bandOffLine } from './gameLogic/bonds.js'; export { priceTone, mergeProgress, mergeTarget, handFull, completesMerge, offerHeader, shopBlockReason, readyFundsPrompt } from './gameLogic/shop.js'; -export { deploySets, stageOverrides, deployMap, effectiveStage, indexPieces, placementContext, piecePosition, tileAllows, summonRange, summonExcluded, canPlace, itemAttaches, equipMerges, boardTargets, dropIntent, dropFailureReason } from './gameLogic/placement.js'; +export { deploySets, stageOverrides, deployMap, effectiveStage, indexPieces, placementContext, piecePosition, tileAllows, summonRange, summonExcluded, canPlace, equipReplaces, equipMerges, boardTargets, dropIntent, dropFailureReason } from './gameLogic/placement.js'; export { terrainInfo } from './gameLogic/terrain.js'; export { normalizeDraft, normalizeSp } from './gameLogic/draft.js'; export { groupEnemies, PEN, penZoneTiles, penPlacement, previewEnemyKey, factionTypes } from './gameLogic/enemies.js'; diff --git a/public/js/ui/gameLogic/placement.js b/public/js/ui/gameLogic/placement.js index 222a2e30..e166fefc 100644 --- a/public/js/ui/gameLogic/placement.js +++ b/public/js/ui/gameLogic/placement.js @@ -392,9 +392,27 @@ function equipCheck(ctx, itemPiece, targetPiece) { return { ok: true, action: 'equip' }; } -/** Whether equipping `itemId` attaches it (true) or consumes it on equip (false: no slot is used / replaced). */ -export function itemAttaches(item) { - return !(typeof item?.kind === 'string' && item.kind.startsWith('consume_on_equip')); +/** Whether an item record is consumed on equip (data `kind` consume_on_equip*: it resolves at once and takes no slot). */ +const consumedOnEquip = (item) => typeof item?.kind === 'string' && item.kind.startsWith('consume_on_equip'); +/** Item effects that promote the carrier (博士投影): the server refuses them on an elite (builtinMeta 'already elite'). */ +const PROMOTE_KEYS = new Set(['use_equip_upgrade_char', 'equip_round_start_upgrade_char']); + +/** + * Whether dropping item `uid` on the chess `targetPiece` opens the replace dialog: its two slots are used and the server + * would replace one of them — every item, a consume-on-equip one too (PRTS 卫戍协议/帮助 "达到上限强行佩戴会改为替换装备"; + * GitHub #263: the pick is destroyed, then the item resolves and the slot stays free). Not when nothing is replaced: an + * attaching item that completes an item merge (the server merges it instead of equipping, `equipMerges`) or a + * 博士投影 on an elite (refused by the server). + * @param {ReturnType} ctx + * @param {number} uid + * @param {any} targetPiece + */ +export function equipReplaces(ctx, uid, targetPiece) { + if (!Array.isArray(targetPiece?.items) || targetPiece.items.length < 2) return false; + const rec = ctx.getItem(ctx.pieces.get(uid)?.piece?.id); + if (!consumedOnEquip(rec)) return !equipMerges(ctx, uid); + const promotes = Array.isArray(rec.buffs) && rec.buffs.some((b) => PROMOTE_KEYS.has(b?.key)); + return !(promotes && targetPiece.golden); } /** @@ -454,11 +472,8 @@ export function dropIntent(ctx, uid, target) { if (res.action === 'art') return { t: 'g.art', fields: { itemUid: uid, row: target.row, col: target.col } }; if (res.action === 'equip') { const occ = target.area === 'hand' ? ctx.handAt.get(target.idx) : ctx.boardAt.get(tileKey(target.row, target.col)); - // both slots used: the replace dialog picks the equipped item to destroy (g.equip replaceUid) — unless the item is - // consumed on equip, or it completes an item merge (the server merges it instead of equipping: nothing replaced) - const full = Array.isArray(occ?.piece?.items) && occ.piece.items.length >= 2 - && itemAttaches(ctx.getItem(ctx.pieces.get(uid)?.piece?.id)) && !equipMerges(ctx, uid); - return { t: 'g.equip', fields: { itemUid: uid, targetUid: occ.piece.uid }, confirmReplace: full }; + // both slots used: the replace dialog picks the equipped item to destroy (g.equip replaceUid) — see equipReplaces + return { t: 'g.equip', fields: { itemUid: uid, targetUid: occ.piece.uid }, confirmReplace: equipReplaces(ctx, uid, occ.piece) }; } const to = target.area === 'hand' ? { area: 'hand', idx: target.idx } : { area: 'board', row: target.row, col: target.col }; return { t: 'g.move', fields: { uid, to } }; diff --git a/server/match/PlayerState.js b/server/match/PlayerState.js index 5088a520..aace612b 100644 --- a/server/match/PlayerState.js +++ b/server/match/PlayerState.js @@ -45,7 +45,8 @@ // * 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 -// (destroy for 0). consume-on-equip items resolve through the effect registry and never take a slot. +// (destroy for 0). consume-on-equip items resolve through the effect registry and never take a slot — on a full +// carrier they still replace (destroy) the picked item first, leaving a free slot (PRTS 帮助, GitHub #263). // * Tokens (PRTS 卫戍协议/帮助 §战斗部署, user playtest #6): placing an owner with manually deployable summons // (tokens.json `placeable`: 赫默's 医疗探机 and 巫恋's 诅咒娃娃 with their S2, 凯瑟琳's 爬行号·防护单元, 海嗣 / 狼群 / // 流形) sends one stack (deployLimit copies — 凯瑟琳 2) to the hand, placed by hand like any piece (no deploy slot); diff --git a/server/match/bot.js b/server/match/bot.js index 1408cb70..4c6456f9 100644 --- a/server/match/bot.js +++ b/server/match/bot.js @@ -1736,7 +1736,8 @@ function lastPerfect(m, ps) { * deployed operator; * 突变细胞 (the carrier becomes a random tier + 1 operator after the battle): cellTarget; * 随身身份牌 / 简易通讯机 / 寻呼模块 (act on the carrier's bonds): the operator whose bonds matter most (focus first); - * funds, 紧急调度券, 人事部文档 …: anyone; a bond signature item (requiresBondId): a member of that bond; + * funds, 紧急调度券, 人事部文档 …: anyone, one with a free slot first (on a full carrier the item replaces one of its + * items, GitHub #263); a bond signature item (requiresBondId): a member of that bond; * other equipment: the strongest deployed damage dealers with a free slot (SURVIVAL items blockers first). */ export function itemTarget(m, ps, item, ctx = context(m, ps)) { @@ -1784,8 +1785,10 @@ export function itemTarget(m, ps, item, ctx = context(m, ps)) { } const consume = typeof rec.kind === 'string' && rec.kind.startsWith('consume_on_equip'); const list = byVal(deployed); - if (consume) return list[0] || owned[0] || null; const free = (p) => (p.items || []).length < gd.equipPerChess; + // a consumable on a full carrier destroys one of its items (the replace rule, GitHub #263): a carrier with a free slot + // first — the effect is the same on anyone + if (consume) return list.find(free) || owned.find(free) || list[0] || owned[0] || null; if (rec.requiresBondId) { const member = list.find((p) => free(p) && (chessRec(m, p.id, ps)?.bonds || []).includes(rec.requiresBondId)); if (member) return member; diff --git a/server/match/player/items.js b/server/match/player/items.js index 204f3590..1936e02a 100644 --- a/server/match/player/items.js +++ b/server/match/player/items.js @@ -15,6 +15,8 @@ export class PlayerItems { * g.equip {itemUid, targetUid, replaceUid?}. A third item on a carrier with both slots used replaces the equipped item * the player picked in the replace dialog (`replaceUid`, research 09 §1.2 UseEquipUp.unloadInstId; absent ⇒ the * oldest); the replaced item is destroyed. A `replaceUid` that is not one of the target's equipped items is refused. + * A consume-on-equip item replaces the same way before it resolves (GitHub #263): the carrier keeps a free slot, and a + * target its effect refuses (博士投影 on an elite) gets the picked item back — nothing is destroyed. */ equip(itemUid, targetUid, replaceUid = null) { const g = this._gate(); if (g) return g; @@ -31,9 +33,18 @@ export class PlayerItems { if (consume) { const key = 'item:' + itemKey(item.id); if (!this.m.registry.has(key)) return fail(ERR.BAD_TARGET, 'effect not available'); + // a full carrier replaces first, consumables included (PRTS 卫戍协议/帮助 "达到上限强行佩戴会改为替换装备:指定一件已佩戴 + // 装备替换为将要佩戴的装备,并销毁被指定的装备"; GitHub #263): the pick (else the oldest) comes off before the effect — + // which so sees the carrier without it (its bonds) — and is destroyed once the effect went through, leaving the slot + // free (the normal 博士投影 then takes it: ev.keep, so it no longer pushes the oldest item out). A refusal puts it back. + const off = this._takeReplaced(target, replaceUid); const ev = { item, target, golden: !!rec.isGolden, keep: false, error: null, consumed: true }; this.m.dispatchItem(this, item, target, 'onEquip', ev); - if (ev.error) return fail(ERR[ev.error] ? ev.error : ERR.BAD_TARGET, typeof ev.detail === 'string' ? ev.detail : undefined); + if (ev.error) { + if (off) target.items.splice(Math.min(off.idx, target.items.length), 0, off.piece); + return fail(ERR[ev.error] ? ev.error : ERR.BAD_TARGET, typeof ev.detail === 'string' ? ev.detail : undefined); + } + if (off) this.m.dispatchItem(this, off.piece, target, 'onDestroy', { item: off.piece, holder: target, reason: 'replace' }); // the handler may have destroyed the target (信标) — resolve the item again const again = this.find(item.uid); if (again && again.area !== 'equipped') this._detach(again); @@ -76,6 +87,19 @@ export class PlayerItems { target.items.push(item); } + /** + * A consume-on-equip item on a full carrier: take the item `replaceUid` names (else the oldest) off `target` — not yet + * destroyed — and return { piece, idx } (null while a slot is free). + */ + _takeReplaced(target, replaceUid = null) { + target.items = target.items || []; + if (target.items.length < this.gd.equipPerChess) return null; + const i = replaceUid != null ? target.items.findIndex((x) => x.uid === replaceUid) : -1; + const idx = i >= 0 ? i : 0; + const [piece] = target.items.splice(idx, 1); + return { piece, idx }; + } + /** g.art {itemUid, row, col, dir?}: the Art's range grid is rotated by `dir` (absent ⇒ RIGHT). */ useArt(itemUid, row, col, dir) { const g = this._gate(); if (g) return g; diff --git a/test/golden/matches.json b/test/golden/matches.json index cae9f70a..75eda345 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -16,7 +16,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_5","hiddenBossId":null,"factions":["TIMES","DOT","INVISIBLE"]}, - "end": {"victory":false,"roundsPassed":8,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":88535,"virtualMs":68100}, + "end": {"victory":false,"roundsPassed":8,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":91555,"virtualMs":68100}, "rng": {"setup":16,"shop":81,"waves":0,"draft":0,"bots":5205,"meta":0}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -43,8 +43,8 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 0, "reward": 100, - "stats": {"activatedLayers":75,"bossDamage":111465,"dmgDealt":714994,"gold":71,"itemsEquipped":14,"kills":169,"leaks":0,"lpLost":0,"merges":2,"perfectRounds":8,"refreshes":4}, - "lineup": ["2_14_a@12,3","1_13_a@11,3","3_14_a@11,8[5_02_e_b+1_01_e_a]","3_10_a@10,3[2_01_e_a]","3_16_a@10,4[3_03_e_a+2_04_e_a]","5_14_a@10,7[4_08_e_a+4_05_e_a]","3_05_a@10,8","3_20_b@9,8[3_11_e_a+5_02_e_b]"], + "stats": {"activatedLayers":75,"bossDamage":108445,"dmgDealt":711959,"gold":71,"itemsEquipped":14,"kills":169,"leaks":0,"lpLost":0,"merges":2,"perfectRounds":8,"refreshes":4}, + "lineup": ["2_14_a@12,3","1_13_a@11,3","3_14_a@11,8[5_02_e_b+2_01_e_a]","3_10_a@10,3","3_16_a@10,4[3_03_e_a+2_04_e_a]","5_14_a@10,7[4_08_e_a+4_05_e_a]","3_05_a@10,8","3_20_b@9,8[5_02_e_b+1_01_e_a]"], "bonds": ["deputShip 2/9","emptyShip 1/6","indomShip 0/5","kjeragShip 1/16","preciShip 0/2","sargonShip 0/8","steadShip 1/38","swiftShip 1/6"] } } @@ -59,7 +59,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_5","hiddenBossId":null,"factions":["ELEMENT","DOT","REFLECTION"]}, - "end": {"victory":false,"roundsPassed":8,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":87617,"virtualMs":68100}, + "end": {"victory":false,"roundsPassed":8,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":107027,"virtualMs":68100}, "rng": {"setup":16,"shop":161,"waves":0,"draft":0,"bots":5205,"meta":43}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -83,8 +83,8 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 0, "reward": 100, - "stats": {"activatedLayers":128,"bossDamage":112383,"dmgDealt":716688,"gold":78,"itemsEquipped":16,"kills":142,"leaks":0,"lpLost":0,"merges":0,"perfectRounds":8,"refreshes":18}, - "lineup": ["3_20_a@12,3[4_07_e_a+3_04_e_b]","2_14_a@12,4","2_05_a@12,6","3_10_a@11,3[2_03_e_a]","4_14_a@11,8[4_02_e_a+4_08_e_a]","1_06_a@10,3[1_05_e_b+5_02_e_a]","1_02_a@10,4[3_08_e_a+2_01_e_b]","4_11_a@10,8[4_04_e_a+1_01_e_a]"], + "stats": {"activatedLayers":128,"bossDamage":92973,"dmgDealt":696253,"gold":78,"itemsEquipped":16,"kills":142,"leaks":0,"lpLost":0,"merges":0,"perfectRounds":8,"refreshes":18}, + "lineup": ["3_20_a@12,3[4_07_e_a+3_04_e_b]","2_14_a@12,4","2_05_a@12,6","3_10_a@11,3","4_14_a@11,8[4_02_e_a+4_08_e_a]","1_06_a@10,3[5_02_e_a+2_03_e_a]","1_02_a@10,4[3_08_e_a+2_01_e_b]","4_11_a@10,8[4_04_e_a+1_01_e_a]"], "bonds": ["deputShip 2/40","emptyShip 1/12","kjeragShip 1/28","steadShip 0/26","swiftShip 1/18","victoriaShip 1/30"] } } @@ -318,8 +318,8 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_10","factions":["ELEMENT","DOT","REFLECTION"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":17,"bossPoolLeft":0,"virtualMs":107800}, - "rng": {"setup":25,"shop":329,"waves":0,"draft":42,"bots":8690,"meta":113}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":13,"bossPoolLeft":0,"virtualMs":107800}, + "rng": {"setup":25,"shop":315,"waves":0,"draft":42,"bots":8690,"meta":112}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ {"round":1,"ai_0":[1,29,4,0,0,1,2,"e4d89f1f",0,"",5,5,0,4]}, @@ -347,23 +347,23 @@ }, { "round": 9, - "ai_0": [1,19,13,0,6,4,8,"a57f7bd8",92,"arcaneShip 1/14, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 1/27, sargonShip 1/33, siracusaShip 1/18, soloShip 1/0, steadShip 0/8, swiftShip 0/5",32,32,0,77] + "ai_0": [1,19,13,0,6,4,8,"94e22f87",92,"arcaneShip 1/14, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 1/27, sargonShip 1/33, siracusaShip 1/18, soloShip 1/0, steadShip 0/8, swiftShip 0/5",32,32,0,77] }, { "round": 10, - "ai_0": [1,19,18,0,1,5,8,"382cd765",126,"arcaneShip 1/16, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 1/36, sargonShip 1/42, siracusaShip 1/32, soloShip 1/0, steadShip 0/8, swiftShip 0/5",30,30,0,94] + "ai_0": [1,19,18,0,1,5,8,"e1be6168",126,"arcaneShip 1/16, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 1/36, sargonShip 1/42, siracusaShip 1/32, soloShip 1/0, steadShip 0/8, swiftShip 0/5",30,30,0,94] }, { "round": 11, - "ai_0": [1,19,13,0,1,5,8,"9119bea5",196,"arcaneShip 1/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 1/52, sargonShip 1/62, siracusaShip 1/58, soloShip 1/0, steadShip 0/8, swiftShip 0/5",28,28,0,108] + "ai_0": [1,19,13,0,1,5,8,"a0ee08e8",204,"arcaneShip 1/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 1/56, sargonShip 1/66, siracusaShip 1/58, soloShip 1/0, steadShip 0/8, swiftShip 0/5",28,28,0,108] }, { "round": 12, - "ai_0": [1,19,13,0,1,6,8,"ff253d54",238,"arcaneShip 1/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 2/71, sargonShip 1/85, siracusaShip 1/58, steadShip 0/8, swiftShip 0/5",45,45,0,121] + "ai_0": [1,19,13,0,1,6,8,"a5da0b66",250,"arcaneShip 1/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, preciShip 2/77, sargonShip 1/91, siracusaShip 1/58, steadShip 0/8, swiftShip 0/5",45,45,0,121] }, { "round": 13, - "ai_0": [1,17,13,0,2,6,8,"d04adca2",182,"arcaneShip 0/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, miraShip 1/1, preciShip 1/95, sargonShip 0/85, siracusaShip 2/86, steadShip 0/8, swiftShip 0/5",27,29,2,135] + "ai_0": [1,13,13,0,1,6,8,"a849c6cf",167,"arcaneShip 0/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, miraShip 1/2, preciShip 1/77, sargonShip 0/91, siracusaShip 2/88, steadShip 0/8, swiftShip 0/5",23,29,6,135] } ], "players": { @@ -372,14 +372,14 @@ "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 17, + "lp": 13, "bandId": "band_ducklord", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 425, - "stats": {"activatedLayers":245,"bossDamage":780000,"dmgDealt":3137836,"gold":149,"itemsEquipped":21,"kills":297,"leaks":12,"lpLost":12,"merges":4,"perfectRounds":10,"refreshes":33}, - "lineup": ["5_20_a@12,5[1_05_e_b+3_08_e_b]","5_02_a@12,6[2_04_e_a]","6_01_a@12,7","6_12_a@11,8","4_10_b@10,3[3_07_e_a+3_11_e_a]","5_07_a@10,7[4_05_e_a+4_10_e_b]","4_16_a@10,8[4_02_e_a]","5_10_a@9,8"], - "bonds": ["arcaneShip 1/36","emptyShip 1/0","kazimierzShip 0/2","kjeragShip 0/8","lateranoShip 0/3","miraShip 0/1","preciShip 2/95","raidShip 1/0","sargonShip 0/85","siracusaShip 1/114","steadShip 0/8","swiftShip 0/5"] + "stats": {"activatedLayers":235,"bossDamage":780000,"dmgDealt":3069328,"gold":148,"itemsEquipped":21,"kills":291,"leaks":16,"lpLost":16,"merges":4,"perfectRounds":10,"refreshes":31}, + "lineup": ["5_20_a@12,5[3_08_e_a+3_07_e_a]","5_02_a@12,6[1_05_e_b]","6_01_a@12,7","6_12_a@11,8","4_10_b@10,3[3_11_e_a]","5_07_a@10,7[4_05_e_a+4_10_e_b]","4_16_a@10,8[4_02_e_a]","5_10_a@9,8"], + "bonds": ["arcaneShip 1/39","emptyShip 1/0","kazimierzShip 0/2","kjeragShip 0/8","lateranoShip 0/3","miraShip 0/2","preciShip 2/77","raidShip 1/0","sargonShip 0/91","siracusaShip 1/119","steadShip 0/8","swiftShip 0/5"] } } }, @@ -516,8 +516,8 @@ "combat": "client", "boost": null, "setup": {"stageId":"act2autochess_m04","bossId":"boss_3","hiddenBossId":"boss_8","factions":["FLY","TIMES","DOT"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":56,"bossPoolLeft":0,"virtualMs":118400}, - "rng": {"setup":25,"shop":456,"waves":0,"draft":40,"bots":21608,"meta":4}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":39,"bossPoolLeft":0,"virtualMs":116900}, + "rng": {"setup":25,"shop":413,"waves":0,"draft":40,"bots":21608,"meta":11}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ { @@ -563,27 +563,27 @@ { "round": 9, "ai_0": [1,41,12,0,1,4,8,"b05d4239",75,"arcaneShip 2/15, preciShip 0/10, raidShip 0/5, sargonShip 0/10, siracusaShip 1/30, skillfulShip 1/6, soloShip 1/6, suntShip 1/0, swiftShip 1/18",16,20,4,77], - "ai_1": [1,23,12,0,1,4,8,"ea817069",73,"arcaneShip 2/14, indomShip 0/2, kazimierzShip 1/22, kjeragShip 0/2, preciShip 1/30, sargonShip 0/10, soloShip 1/0, steadShip 0/6, suntShip 1/0, swiftShip 1/7",15,20,5,73] + "ai_1": [1,23,12,0,1,4,8,"f18d3577",73,"arcaneShip 2/14, indomShip 0/2, kazimierzShip 1/22, kjeragShip 0/2, preciShip 1/30, sargonShip 0/10, soloShip 1/0, steadShip 0/6, suntShip 1/0, swiftShip 1/7",15,20,5,73] }, { "round": 10, "ai_0": [1,37,13,0,0,5,8,"29aea524",87,"arcaneShip 2/18, egirShip 1/28, preciShip 0/10, raidShip 1/5, sargonShip 0/10, siracusaShip 1/30, skillfulShip 0/8, soloShip 1/6, suntShip 1/0, swiftShip 0/20",4,8,4,89], - "ai_1": [1,19,13,0,0,5,8,"41242f43",97,"arcaneShip 2/18, indomShip 0/2, kazimierzShip 1/34, kjeragShip 0/2, preciShip 1/38, sargonShip 0/10, soloShip 1/0, steadShip 0/6, suntShip 1/0, swiftShip 1/7",4,8,4,86] + "ai_1": [1,19,13,0,0,5,8,"c05a26ad",97,"arcaneShip 2/18, indomShip 0/2, kazimierzShip 1/34, kjeragShip 0/2, preciShip 1/38, sargonShip 0/10, soloShip 1/0, steadShip 0/6, suntShip 1/0, swiftShip 1/7",4,8,4,86] }, { "round": 11, "ai_0": [1,37,12,0,0,5,8,"c43f7697",147,"arcaneShip 1/23, egirShip 1/55, preciShip 0/10, raidShip 1/6, sargonShip 0/10, siracusaShip 1/32, skillfulShip 0/8, soloShip 1/6, suntShip 1/0, swiftShip 1/25",20,23,3,101], - "ai_1": [1,19,12,0,0,5,8,"812fd9eb",117,"arcaneShip 2/22, indomShip 0/2, kazimierzShip 1/43, kjeragShip 0/2, preciShip 2/52, sargonShip 0/10, steadShip 0/6, suntShip 1/0, swiftShip 0/11",23,23,0,99] + "ai_1": [1,19,12,0,0,5,8,"6a60d378",142,"arcaneShip 1/22, indomShip 1/6, kazimierzShip 1/44, kjeragShip 0/2, preciShip 2/61, sargonShip 0/10, steadShip 1/9, suntShip 1/0, swiftShip 0/11",23,23,0,99] }, { "round": 12, "ai_0": [1,37,12,0,0,6,8,"e51a7992",179,"arcaneShip 1/26, egirShip 1/78, preciShip 0/10, raidShip 1/7, sargonShip 0/10, siracusaShip 1/32, skillfulShip 0/8, soloShip 1/6, suntShip 1/0, swiftShip 1/30",105,105,0,114], - "ai_1": [1,19,12,0,0,6,8,"c916c4e3",161,"arcaneShip 2/22, indomShip 0/2, kazimierzShip 1/48, kjeragShip 0/2, preciShip 2/80, sargonShip 0/10, steadShip 0/6, suntShip 1/0, swiftShip 1/11",105,105,0,111] + "ai_1": [1,19,12,0,0,6,8,"1d33396d",189,"arcaneShip 2/22, indomShip 0/6, kazimierzShip 1/64, kjeragShip 0/10, preciShip 2/76, sargonShip 0/10, soloShip 1/0, steadShip 0/9, suntShip 1/0, swiftShip 1/27",105,105,0,111] }, { "round": 13, - "ai_0": [1,37,12,0,0,6,8,"f3619a79",269,"arcaneShip 2/43, egirShip 1/124, preciShip 1/17, raidShip 0/7, sargonShip 0/10, siracusaShip 1/44, skillfulShip 0/8, soloShip 1/6, steadShip 1/0, suntShip 1/0, swiftShip 1/35",13,34,21,126], - "ai_1": [1,19,12,0,0,6,8,"187f8019",183,"arcaneShip 2/28, indomShip 0/2, kazimierzShip 1/53, kjeragShip 0/2, preciShip 2/102, sargonShip 0/10, steadShip 0/6, suntShip 1/0, swiftShip 0/11",34,34,0,123] + "ai_0": [1,30,12,0,0,6,8,"588a1c67",208,"arcaneShip 2/49, egirShip 1/109, preciShip 0/10, raidShip 0/7, sargonShip 0/10, siracusaShip 1/44, skillfulShip 0/8, soloShip 1/6, steadShip 1/0, suntShip 1/0, swiftShip 0/30",27,34,7,126], + "ai_1": [1,9,12,0,2,6,8,"a04ac729",253,"arcaneShip 1/24, indomShip 0/6, kazimierzShip 1/80, kjeragShip 0/10, miraShip 1/0, preciShip 2/108, sargonShip 0/10, siracusaShip 1/6, soloShip 1/0, steadShip 0/9, suntShip 1/0, swiftShip 1/35",23,34,11,124] } ], "players": { @@ -592,28 +592,28 @@ "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 37, + "lp": 30, "bandId": "band_sarkazb", - "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, + "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":303,"bossDamage":283279,"dmgDealt":2679730,"gold":138,"itemsEquipped":17,"kills":310,"leaks":32,"lpLost":8,"merges":3,"perfectRounds":9,"refreshes":23}, - "lineup": ["1_17_a@11,7","4_14_a@11,8[3_01_e_a+4_08_e_a]","4_10_b@10,5[6_09_e_a+1_02_e_b]","3_18_a@10,7[4_05_e_a+3_05_e_a]","5_13_a@10,8[4_06_e_a]","3_05_a@9,4","3_09_a@9,8[1_05_e_a+4_07_e_a]","1_08_b@9,9[4_03_e_a+4_04_e_a]"], - "bonds": ["arcaneShip 2/55","egirShip 1/124","preciShip 1/22","raidShip 0/7","sargonShip 0/10","siracusaShip 1/56","skillfulShip 0/8","soloShip 1/6","steadShip 1/0","suntShip 1/0","swiftShip 1/40"] + "stats": {"activatedLayers":239,"bossDamage":440927,"dmgDealt":2994796,"gold":139,"itemsEquipped":18,"kills":322,"leaks":18,"lpLost":15,"merges":4,"perfectRounds":9,"refreshes":17}, + "lineup": ["6_16_a@11,8","4_10_b@10,5[3_01_e_a+1_02_e_b]","6_15_a@10,7","5_05_a@10,8[3_09_e_a]","3_05_a@9,4[3_05_e_a+3_07_e_a]","3_18_a@9,5[4_05_e_a]","3_09_a@9,8[4_07_e_a+1_05_e_b]","1_08_b@9,9[4_03_e_a+4_04_e_a]"], + "bonds": ["arcaneShip 2/65","egirShip 1/109","preciShip 0/10","raidShip 1/7","sargonShip 0/10","siracusaShip 1/52","skillfulShip 0/8","soloShip 1/6","suntShip 1/0","swiftShip 0/30"] }, "ai_1": { "alive": true, "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 19, + "lp": 9, "bandId": "band_dusk", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":185,"bossDamage":1134221,"dmgDealt":4858908,"gold":135,"itemsEquipped":19,"kills":344,"leaks":9,"lpLost":9,"merges":4,"perfectRounds":11,"refreshes":14}, - "lineup": ["6_08_a@12,8","1_17_b@11,7[3_04_e_a+4_08_e_b]","4_20_a@11,8[5_02_e_a+4_04_e_a]","4_10_a@10,5[1_02_e_b+4_10_e_a]","6_17_a@10,8","6_08_a@9,7[4_07_e_a+3_01_e_a]","4_02_a@9,8","1_19_b@9,9[2_04_e_a+1_05_e_b]"], - "bonds": ["arcaneShip 2/30","indomShip 0/2","kazimierzShip 1/53","kjeragShip 0/2","preciShip 2/102","sargonShip 0/10","steadShip 0/6","suntShip 1/0","swiftShip 0/11"] + "stats": {"activatedLayers":269,"bossDamage":976573,"dmgDealt":3630098,"gold":139,"itemsEquipped":19,"kills":312,"leaks":20,"lpLost":19,"merges":5,"perfectRounds":10,"refreshes":13}, + "lineup": ["1_17_b@11,7","4_20_b@11,8[5_02_e_a+1_02_e_a]","4_10_a@10,5","6_19_a@10,7[4_06_e_a+4_05_e_a]","1_19_b@10,8[2_04_e_b+4_08_e_b]","5_02_a@9,7","5_20_a@9,8","1_08_b@9,9"], + "bonds": ["arcaneShip 1/28","indomShip 0/6","kazimierzShip 1/80","kjeragShip 0/10","miraShip 1/0","preciShip 2/108","sargonShip 0/10","siracusaShip 1/18","soloShip 1/0","steadShip 0/9","suntShip 1/0","swiftShip 1/35"] } } }, @@ -805,13 +805,13 @@ { "round": 11, "ai_0": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/4, deputShip 0/48, emptyShip 0/5, kazimierzShip 0/4, lateranoShip 0/36, skillfulShip 0/38, soloShip 0/8, steadShip 0/18, suntShip 0/2, swiftShip 0/23",null,null,null,91], - "ai_1": [1,9,12,0,2,5,8,"6d142192",162,"deputShip 0/16, kazimierzShip 0/7, raidShip 1/29, siracusaShip 1/41, soloShip 1/4, suntShip 1/10, swiftShip 1/69, visiShip 1/9",7,25,18,101], + "ai_1": [1,9,12,0,2,5,8,"17a87146",162,"deputShip 0/16, kazimierzShip 0/7, raidShip 1/29, siracusaShip 1/41, soloShip 1/4, suntShip 1/10, swiftShip 1/69, visiShip 1/9",7,25,18,101], "ai_2": [0,0,12,0,0,5,0,"811c9dc5",0,"deputShip 0/4, indomShip 0/8, miraShip 0/12, siracusaShip 0/84, skillfulShip 0/4, soloShip 0/12, suntShip 0/4, swiftShip 0/16, yanShip 0/1",10,25,15,105] }, { "round": 12, "ai_0": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/4, deputShip 0/48, emptyShip 0/5, kazimierzShip 0/4, lateranoShip 0/36, skillfulShip 0/38, soloShip 0/8, steadShip 0/18, suntShip 0/2, swiftShip 0/23",null,null,null,91], - "ai_1": [0,0,16,0,0,6,0,"811c9dc5",0,"arcaneShip 0/6, deputShip 0/16, kazimierzShip 0/7, raidShip 0/34, siracusaShip 0/47, soloShip 0/4, suntShip 0/10, swiftShip 0/74, visiShip 0/22",7,57,50,119], + "ai_1": [0,0,16,0,0,6,0,"811c9dc5",0,"arcaneShip 0/6, deputShip 0/16, kazimierzShip 0/7, raidShip 0/34, siracusaShip 0/47, soloShip 0/4, suntShip 0/10, swiftShip 0/74, visiShip 0/22",4,57,53,119], "ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"deputShip 0/4, indomShip 0/8, miraShip 0/12, siracusaShip 0/84, skillfulShip 0/4, soloShip 0/12, suntShip 0/4, swiftShip 0/16, yanShip 0/1",null,null,null,105] } ], @@ -840,7 +840,7 @@ "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, "trophies": 2, "reward": 255, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2784986,"gold":119,"itemsEquipped":12,"kills":273,"leaks":102,"lpLost":46,"merges":2,"perfectRounds":6,"refreshes":23}, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2604413,"gold":119,"itemsEquipped":12,"kills":270,"leaks":105,"lpLost":46,"merges":2,"perfectRounds":6,"refreshes":23}, "lineup": [], "bonds": ["arcaneShip 0/6","deputShip 0/16","kazimierzShip 0/7","raidShip 0/34","siracusaShip 0/47","soloShip 0/4","suntShip 0/10","swiftShip 0/74","visiShip 0/22"] }, @@ -898,70 +898,70 @@ { "round": 4, "ai_0": [1,26,7,0,0,2,8,"c4b7b874",11,"deputShip 2/11, kjeragShip 0/2, sargonShip 0/15, steadShip 0/2",12,12,0,22], - "ai_1": [1,45,7,0,0,1,8,"925b6cbc",6,"deputShip 1/6, preciShip 0/2, sargonShip 0/2, siracusaShip 0/3",12,12,0,22], + "ai_1": [1,45,7,0,0,1,8,"30891793",6,"deputShip 1/6, preciShip 0/2, sargonShip 0/2, siracusaShip 0/3",12,12,0,22], "ai_2": [1,28,7,0,0,2,8,"efbc97b4",4,"sargonShip 0/8, siracusaShip 1/4, yanShip 0/1",12,12,0,22], "ai_3": [1,31,7,0,0,2,7,"f2e303c3",11,"deputShip 1/2, preciShip 1/9, siracusaShip 0/3, yanShip 0/1",12,12,0,22] }, { "round": 5, "ai_0": [1,26,8,0,2,3,8,"9781edeb",18,"deputShip 2/18, kjeragShip 0/2, sargonShip 0/15, steadShip 0/2",16,16,0,30], - "ai_1": [1,45,8,0,0,2,8,"9d1510c8",23,"deputShip 1/8, preciShip 0/4, sargonShip 0/2, siracusaShip 1/15, swiftShip 0/6",16,16,0,30], + "ai_1": [1,45,8,0,0,2,8,"033ae342",23,"deputShip 1/8, preciShip 0/4, sargonShip 0/2, siracusaShip 1/15, swiftShip 0/6",16,16,0,30], "ai_2": [1,28,8,0,0,3,8,"36b2b83c",6,"sargonShip 0/8, siracusaShip 1/6, yanShip 0/1",16,16,0,30], "ai_3": [1,31,8,0,2,3,7,"a758480c",18,"deputShip 1/4, preciShip 1/14, siracusaShip 0/3, yanShip 0/1",15,16,1,30] }, { "round": 6, "ai_0": [1,26,11,0,0,3,8,"618b130f",35,"deputShip 2/25, kjeragShip 0/2, sargonShip 0/15, steadShip 2/10",30,30,0,42], - "ai_1": [1,45,9,0,0,3,8,"4b9a8330",31,"deputShip 1/10, preciShip 0/4, sargonShip 0/2, siracusaShip 1/21, swiftShip 0/6",30,30,0,38], + "ai_1": [1,45,9,0,0,3,8,"2865599a",31,"deputShip 1/10, preciShip 0/4, sargonShip 0/2, siracusaShip 1/21, swiftShip 0/6",30,30,0,38], "ai_2": [1,28,9,0,0,3,8,"a6f6c116",18,"sargonShip 0/8, siracusaShip 1/14, steadShip 2/4, swiftShip 0/6, yanShip 0/1",30,30,0,39], "ai_3": [1,31,11,0,1,3,7,"ff70b795",38,"deputShip 1/6, kjeragShip 0/5, preciShip 1/24, siracusaShip 1/8, yanShip 0/1",30,30,0,41] }, { "round": 7, "ai_0": [1,26,10,0,0,4,8,"3b67af64",69,"deputShip 2/32, kjeragShip 0/2, sargonShip 1/19, steadShip 2/18",29,29,0,53], - "ai_1": [1,45,10,0,0,4,8,"11024c67",39,"deputShip 1/12, preciShip 0/4, sargonShip 0/2, siracusaShip 1/27, swiftShip 0/6",29,29,0,49], + "ai_1": [1,45,10,0,0,4,8,"4aa3d84d",39,"deputShip 1/12, preciShip 0/4, sargonShip 0/2, siracusaShip 1/27, swiftShip 0/6",29,29,0,49], "ai_2": [1,28,10,0,0,4,8,"7f56cec6",30,"deputShip 1/2, sargonShip 0/8, siracusaShip 1/18, steadShip 1/10, swiftShip 0/6, yanShip 0/1",29,29,0,50], "ai_3": [1,31,11,0,0,4,8,"eb93ef9f",54,"deputShip 2/10, kjeragShip 0/5, preciShip 1/32, siracusaShip 1/12, yanShip 0/7",29,29,0,51] }, { "round": 8, "ai_0": [1,26,11,0,0,4,8,"388f6918",92,"deputShip 2/39, kjeragShip 0/7, sargonShip 1/23, steadShip 2/26, visiShip 1/4",15,15,0,62], - "ai_1": [1,45,11,0,0,4,8,"81f206ca",74,"deputShip 1/14, preciShip 1/11, sargonShip 0/2, siracusaShip 1/36, swiftShip 1/13",15,15,0,60], + "ai_1": [1,45,11,0,0,4,8,"6bb07588",74,"deputShip 1/14, preciShip 1/11, sargonShip 0/2, siracusaShip 1/36, swiftShip 1/13",15,15,0,60], "ai_2": [1,28,11,0,0,4,8,"731b7931",59,"deputShip 1/4, sargonShip 0/8, siracusaShip 1/26, steadShip 1/16, swiftShip 1/13, yanShip 0/1",15,15,0,60], "ai_3": [1,31,11,0,0,4,7,"5ec63e46",77,"deputShip 1/12, kjeragShip 0/5, preciShip 1/43, siracusaShip 1/22, yanShip 0/7",15,15,0,61] }, { "round": 9, "ai_0": [1,26,12,0,0,4,8,"dcbc34d9",117,"deputShip 2/46, kjeragShip 0/7, sargonShip 1/27, steadShip 2/36, visiShip 1/8",30,30,0,75], - "ai_1": [1,45,12,0,0,4,8,"7e29602e",116,"deputShip 2/18, emptyShip 1/4, preciShip 1/28, sargonShip 0/2, siracusaShip 1/44, swiftShip 1/22",30,30,0,71], + "ai_1": [1,45,12,0,0,4,8,"291e7d48",116,"deputShip 2/18, emptyShip 1/4, preciShip 1/28, sargonShip 0/2, siracusaShip 1/44, swiftShip 1/22",30,30,0,71], "ai_2": [1,28,12,0,0,4,8,"e97cc791",111,"deputShip 1/6, preciShip 1/31, sargonShip 0/8, siracusaShip 1/54, steadShip 0/16, swiftShip 1/20, yanShip 0/1",30,30,0,73], "ai_3": [1,31,12,0,0,4,8,"db03c900",105,"deputShip 1/14, kjeragShip 0/5, preciShip 1/59, siracusaShip 1/32, yanShip 0/7",30,30,0,76] }, { "round": 10, "ai_0": [1,26,12,0,0,5,8,"28ed8fda",129,"deputShip 2/53, kjeragShip 0/7, sargonShip 1/31, steadShip 2/45, visiShip 0/8",29,29,0,88], - "ai_1": [1,45,12,0,0,5,8,"ce4b0441",166,"deputShip 2/22, emptyShip 1/8, preciShip 1/41, sargonShip 0/2, siracusaShip 1/58, swiftShip 1/37",29,29,0,81], + "ai_1": [1,45,12,0,0,5,8,"453b9c4f",166,"deputShip 2/22, emptyShip 1/8, preciShip 1/41, sargonShip 0/2, siracusaShip 1/58, swiftShip 1/37",29,29,0,81], "ai_2": [1,28,12,0,0,5,8,"ce1a56b2",135,"deputShip 1/8, preciShip 1/42, sargonShip 0/8, siracusaShip 1/58, steadShip 0/16, swiftShip 1/27, yanShip 0/1",29,29,0,84], "ai_3": [1,31,12,0,0,5,7,"33b16596",115,"deputShip 0/16, kjeragShip 0/5, preciShip 1/75, siracusaShip 1/40, yanShip 0/7",29,29,0,89] }, { "round": 11, "ai_0": [1,26,12,0,0,5,8,"dc9d71e9",152,"deputShip 1/55, indomShip 1/2, kjeragShip 0/7, sargonShip 1/33, steadShip 2/62, visiShip 0/8",28,28,0,102], - "ai_1": [1,45,12,0,0,5,8,"38f3210b",224,"deputShip 1/24, emptyShip 1/10, preciShip 2/82, sargonShip 0/2, siracusaShip 1/64, swiftShip 1/44",28,28,0,91], + "ai_1": [1,45,12,0,0,5,8,"bb7e68ea",224,"deputShip 1/24, emptyShip 1/10, preciShip 2/82, sargonShip 0/2, siracusaShip 1/64, swiftShip 1/44",28,28,0,91], "ai_2": [1,28,12,0,0,5,8,"d8242751",175,"deputShip 1/10, preciShip 1/49, sargonShip 0/8, siracusaShip 1/82, steadShip 0/16, swiftShip 1/34, yanShip 0/1",28,28,0,96], "ai_3": [1,31,12,0,0,5,7,"9e061915",143,"deputShip 0/16, kjeragShip 0/5, preciShip 1/87, siracusaShip 1/56, swiftShip 0/10, yanShip 0/7",28,28,0,102] }, { "round": 12, "ai_0": [1,26,12,0,0,6,8,"127f991f",165,"deputShip 1/57, indomShip 0/2, kjeragShip 0/7, sargonShip 1/35, steadShip 1/73, visiShip 0/8",46,46,0,114], - "ai_1": [1,45,12,0,0,6,8,"f1e90cde",282,"deputShip 2/28, emptyShip 1/14, preciShip 1/119, sargonShip 0/2, siracusaShip 1/68, swiftShip 1/53",46,46,0,103], + "ai_1": [1,45,12,0,0,6,8,"15260ca2",282,"deputShip 2/28, emptyShip 1/14, preciShip 1/119, sargonShip 0/2, siracusaShip 1/68, swiftShip 1/53",46,46,0,103], "ai_2": [1,28,12,0,0,6,8,"441ee35b",231,"deputShip 1/12, preciShip 2/80, sargonShip 0/8, siracusaShip 1/98, steadShip 0/16, swiftShip 1/41, yanShip 0/1",46,46,0,107], "ai_3": [1,31,12,0,0,6,7,"1ee80c63",155,"deputShip 0/16, kjeragShip 0/5, preciShip 1/99, siracusaShip 1/56, swiftShip 0/10, yanShip 0/7",46,46,0,113] }, { "round": 13, "ai_0": [1,26,12,0,0,6,8,"626d042c",184,"deputShip 1/59, indomShip 1/4, kjeragShip 0/7, sargonShip 1/37, steadShip 2/84, visiShip 0/8",32,32,0,127], - "ai_1": [1,45,12,0,0,6,8,"d2a2642d",332,"deputShip 1/30, emptyShip 1/16, preciShip 2/144, sargonShip 0/2, siracusaShip 1/76, swiftShip 1/66",32,32,0,116], + "ai_1": [1,45,12,0,0,6,8,"e479e4b7",332,"deputShip 1/30, emptyShip 1/16, preciShip 2/144, sargonShip 0/2, siracusaShip 1/76, swiftShip 1/66",32,32,0,116], "ai_2": [1,28,12,0,0,6,8,"ca7f3fdd",247,"deputShip 0/12, preciShip 2/85, sargonShip 0/8, siracusaShip 1/116, steadShip 0/16, swiftShip 1/46, yanShip 0/1",32,32,0,117], "ai_3": [1,31,12,0,0,6,7,"6e7bba0f",207,"deputShip 0/16, kjeragShip 0/5, preciShip 1/115, siracusaShip 1/72, swiftShip 1/20, yanShip 0/7",32,32,0,126] } @@ -977,7 +977,7 @@ "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, "trophies": 3, "reward": 250, - "stats": {"activatedLayers":203,"bossDamage":70064,"dmgDealt":1489868,"gold":140,"itemsEquipped":20,"kills":293,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":20}, + "stats": {"activatedLayers":203,"bossDamage":70558,"dmgDealt":1490268,"gold":140,"itemsEquipped":20,"kills":293,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":20}, "lineup": ["6_14_a@12,3","3_16_a@11,7[4_06_e_a+6_03_e_a]","4_24_a@10,3[6_07_e_a]","2_17_b@10,4[1_02_e_a]","2_08_b@9,3[4_04_e_a+5_01_e_a]","1_12_b@9,4[3_02_e_a+3_08_e_a]","3_13_b@9,8[2_04_e_a+1_05_e_a]","6_05_a@9,9[3_05_e_b+3_07_e_a]"], "bonds": ["deputShip 1/63","indomShip 0/4","kjeragShip 0/12","sargonShip 1/45","steadShip 2/95","visiShip 0/8"] }, @@ -991,8 +991,8 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 3, "reward": 250, - "stats": {"activatedLayers":384,"bossDamage":420794,"dmgDealt":1945174,"gold":128,"itemsEquipped":20,"kills":284,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":9}, - "lineup": ["5_10_a@12,8[4_03_e_b+3_04_e_b]","5_02_a@11,6","6_12_a@11,7","1_13_b@11,8[3_11_e_a]","6_20_a@10,3","3_18_b@10,8[2_01_e_b+3_01_e_a]","4_14_a@9,8[4_06_e_a+1_02_e_a]","1_08_b@9,9[1_01_e_b+4_12_e_a]"], + "stats": {"activatedLayers":384,"bossDamage":417152,"dmgDealt":1941627,"gold":128,"itemsEquipped":19,"kills":284,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":9}, + "lineup": ["5_10_a@12,8[4_03_e_b+3_04_e_b]","5_02_a@11,6","6_12_a@11,7","1_13_b@11,8","6_20_a@10,3","3_18_b@10,8[2_01_e_b+4_12_e_a]","4_14_a@9,8[4_06_e_a+3_11_e_a]","1_08_b@9,9[1_01_e_b+3_01_e_a]"], "bonds": ["deputShip 1/32","emptyShip 1/18","preciShip 2/159","sargonShip 0/2","siracusaShip 1/90","swiftShip 1/85"] }, "ai_2": { @@ -1019,7 +1019,7 @@ "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, "trophies": 3, "reward": 250, - "stats": {"activatedLayers":249,"bossDamage":402295,"dmgDealt":1839478,"gold":138,"itemsEquipped":31,"kills":282,"leaks":1,"lpLost":0,"merges":2,"perfectRounds":12,"refreshes":18}, + "stats": {"activatedLayers":249,"bossDamage":405443,"dmgDealt":1842625,"gold":138,"itemsEquipped":31,"kills":282,"leaks":1,"lpLost":0,"merges":2,"perfectRounds":12,"refreshes":18}, "lineup": ["6_12_a@11,7[4_03_e_a+2_01_e_b]","6_05_a@11,8","4_10_a@10,3[4_06_e_a]","4_10_a@10,4[5_03_e_a+3_01_e_b]","2_16_a@10,7[5_02_e_a+4_06_e_b]","4_16_a@10,8[4_05_e_a+4_07_e_a]","4_14_b@9,8[6_03_e_a+4_09_e_a]"], "bonds": ["deputShip 0/16","kjeragShip 0/5","preciShip 2/131","siracusaShip 1/88","swiftShip 1/30","yanShip 0/7"] } @@ -1365,8 +1365,8 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 425, - "stats": {"activatedLayers":2931,"bossDamage":4580000,"dmgDealt":6350821,"gold":167,"itemsEquipped":19,"kills":371,"leaks":2,"lpLost":2,"merges":5,"perfectRounds":12,"refreshes":35}, - "lineup": ["1_17_b@12,7[1_05_e_a]","3_17_a@12,8[1_02_e_b+2_01_e_b]","6_16_a@11,5","5_06_a@11,8[5_02_e_a+4_04_e_a]","4_14_b@10,4[4_03_e_a+1_01_e_b]","2_18_b@10,5[4_05_e_a+1_02_e_a]","3_16_a@9,5[4_02_e_a]","1_19_b@9,9[1_05_e_b+2_01_e_a]"], + "stats": {"activatedLayers":2931,"bossDamage":4580000,"dmgDealt":6350822,"gold":167,"itemsEquipped":19,"kills":371,"leaks":2,"lpLost":2,"merges":5,"perfectRounds":12,"refreshes":35}, + "lineup": ["1_17_b@12,7[1_05_e_a]","3_17_a@12,8[1_02_e_b+2_01_e_b]","6_16_a@11,5","5_06_a@11,8[5_02_e_a+4_04_e_a]","4_14_b@10,4[1_01_e_b]","2_18_b@10,5[1_02_e_a+4_03_e_a]","3_16_a@9,5[4_02_e_a]","1_19_b@9,9[1_05_e_b+2_01_e_a]"], "bonds": ["arcaneShip 1/348","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 1/300","investShip 0/300","kazimierzShip 1/311","kjeragShip 0/310","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/338","raidShip 0/300","sargonShip 0/300","siracusaShip 0/306","skillfulShip 1/350","soloShip 1/307","steadShip 1/322","suntShip 1/300","swiftShip 1/355","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] } } @@ -1381,7 +1381,7 @@ "combat": "client", "boost": {"lp":400,"layers":300}, "setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]}, - "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":398,"bossPoolLeft":0,"virtualMs":123150}, + "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":529,"bossPoolLeft":0,"virtualMs":123150}, "rng": {"setup":25,"shop":681,"waves":0,"draft":61,"bots":22075,"meta":140}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1447,7 +1447,7 @@ }, { "round": 13, - "ai_0": [1,400,12,0,2,6,8,"31644d65",2842,"arcaneShip 0/314, deputShip 1/313, egirShip 0/300, emptyShip 1/302, indomShip 1/302, investShip 0/300, kazimierzShip 0/302, kjeragShip 1/331, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/326, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/319, soloShip 0/306, steadShip 1/339, suntShip 1/308, swiftShip 0/302, victoriaShip 0/300, visiShip 1/302, yanShip 0/300",41,41,0,149], + "ai_0": [1,400,12,0,2,6,8,"1a3174a6",2842,"arcaneShip 0/314, deputShip 1/313, egirShip 0/300, emptyShip 1/302, indomShip 1/302, investShip 0/300, kazimierzShip 0/302, kjeragShip 1/331, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/326, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/319, soloShip 0/306, steadShip 1/339, suntShip 1/308, swiftShip 0/302, victoriaShip 0/300, visiShip 1/302, yanShip 0/300",41,41,0,149], "ai_1": [1,400,14,0,4,6,8,"10ed5f25",2256,"arcaneShip 1/312, deputShip 1/312, egirShip 0/300, emptyShip 0/300, indomShip 1/302, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/388, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/302, soloShip 1/310, steadShip 1/330, suntShip 0/300, swiftShip 0/300, victoriaShip 0/316, visiShip 0/300, yanShip 0/300",43,43,0,135] } ], @@ -1457,13 +1457,13 @@ "victory": true, "roundsPassed": 15, "eliminatedRound": null, - "lp": 199, + "lp": 265, "bandId": "band_sarkazb", "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2908,"bossDamage":4534947,"dmgDealt":16222557,"gold":176,"itemsEquipped":24,"kills":286,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":31}, - "lineup": ["3_16_b@12,8[4_02_e_b+5_01_e_a]","3_11_b@11,8[3_01_e_a+1_01_e_b]","3_20_a@10,3[5_02_e_a+3_07_e_a]","1_02_b@10,4[1_02_e_a+4_05_e_a]","5_06_a@10,8[4_04_e_a+3_04_e_b]","4_21_b@9,7","6_12_a@9,8[3_08_e_a+1_05_e_a]","5_10_a@9,9[2_01_e_a+4_06_e_a]"], + "stats": {"activatedLayers":2908,"bossDamage":3909426,"dmgDealt":15927135,"gold":176,"itemsEquipped":24,"kills":286,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":31}, + "lineup": ["3_16_b@12,8[4_02_e_b+5_01_e_a]","3_11_b@11,8[3_01_e_a+1_01_e_b]","3_20_a@10,3[3_07_e_a+3_04_e_b]","1_02_b@10,4[1_02_e_a+4_05_e_a]","5_06_a@10,8[4_04_e_a+2_01_e_a]","4_21_b@9,7","6_12_a@9,8[3_08_e_a+1_05_e_a]","5_10_a@9,9[4_06_e_a]"], "bonds": ["arcaneShip 0/314","deputShip 1/317","egirShip 0/300","emptyShip 1/306","indomShip 1/306","investShip 0/300","kazimierzShip 0/302","kjeragShip 1/345","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/340","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/323","soloShip 0/306","steadShip 1/353","suntShip 1/312","swiftShip 0/302","victoriaShip 0/300","visiShip 1/306","yanShip 0/300"] }, "ai_1": { @@ -1471,13 +1471,13 @@ "victory": true, "roundsPassed": 15, "eliminatedRound": null, - "lp": 199, + "lp": 264, "bandId": "band_doberm", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2599,"bossDamage":9351573,"dmgDealt":28348513,"gold":160,"itemsEquipped":32,"kills":277,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":36}, - "lineup": ["3_16_a@12,8[3_03_e_b+4_04_e_b]","2_10_b@11,5[2_03_e_a+2_03_e_b]","1_06_b@11,8[3_02_e_a+4_09_e_b]","2_17_a@10,4[5_02_e_a+5_09_e_a]","5_06_a@10,8[3_09_e_b+3_09_e_b]","4_21_a@9,6[5_03_e_a+3_06_e_a]","5_02_a@9,8[3_06_e_b+2_03_e_b]","1_17_a@9,9[4_09_e_b+3_10_e_b]"], + "stats": {"activatedLayers":2599,"bossDamage":9978950,"dmgDealt":31241047,"gold":160,"itemsEquipped":33,"kills":277,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":36}, + "lineup": ["3_16_a@12,8[3_03_e_b+4_04_e_b]","2_10_b@11,5[2_03_e_b+3_02_e_a]","1_06_b@11,8[4_09_e_b+2_03_e_b]","2_17_a@10,4[5_02_e_a+5_09_e_a]","5_06_a@10,8[3_09_e_b+3_09_e_b]","4_21_a@9,6[5_03_e_a+3_06_e_a]","5_02_a@9,8[3_06_e_b+1_05_e_a]","1_17_a@9,9[4_09_e_b+3_10_e_b]"], "bonds": ["arcaneShip 1/316","deputShip 1/316","egirShip 0/300","emptyShip 0/300","indomShip 1/306","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/392","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/306","soloShip 1/314","steadShip 1/345","suntShip 1/304","swiftShip 0/300","victoriaShip 0/316","visiShip 0/300","yanShip 0/300"] } } @@ -1492,7 +1492,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_5","hiddenBossId":"boss_10","factions":["DOT","INVISIBLE","REFLECTION"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":14,"bossPoolLeft":0,"virtualMs":119900}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":28,"bossPoolLeft":0,"virtualMs":119900}, "rng": {"setup":25,"shop":525,"waves":0,"draft":34,"bots":18074,"meta":23}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1539,27 +1539,27 @@ { "round": 9, "h_0": [1,28,12,0,0,4,8,"f78a00e2",63,"arcaneShip 1/7, kazimierzShip 0/8, preciShip 1/14, siracusaShip 1/18, suntShip 1/0, swiftShip 1/24, yanShip 0/13",17,23,6,76], - "ai_0": [1,23,12,0,0,4,8,"ae419bd2",132,"arcaneShip 0/2, deputShip 1/14, kazimierzShip 0/8, lateranoShip 1/11, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/24, skillfulShip 1/34, suntShip 1/8, swiftShip 1/41",23,23,0,75] + "ai_0": [1,23,12,0,0,4,8,"b8494f7f",142,"arcaneShip 0/2, deputShip 1/14, kazimierzShip 0/8, lateranoShip 1/21, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/24, skillfulShip 1/34, suntShip 1/8, swiftShip 1/41",23,23,0,75] }, { "round": 10, "h_0": [1,28,12,0,0,5,8,"1a942a0c",66,"arcaneShip 0/7, kazimierzShip 0/8, preciShip 1/19, siracusaShip 1/18, skillfulShip 1/0, suntShip 1/0, swiftShip 1/29, yanShip 0/13",31,31,0,88], - "ai_0": [1,23,12,0,1,5,8,"82fa7de0",166,"arcaneShip 0/2, deputShip 1/22, kazimierzShip 0/8, lateranoShip 1/19, miraShip 1/2, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/26, skillfulShip 1/42, suntShip 1/10, swiftShip 1/45",31,31,0,87] + "ai_0": [1,23,12,0,1,5,8,"f21d9f0c",176,"arcaneShip 0/2, deputShip 1/22, kazimierzShip 0/8, lateranoShip 1/29, miraShip 1/2, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/26, skillfulShip 1/42, suntShip 1/10, swiftShip 1/45",31,31,0,87] }, { "round": 11, - "h_0": [1,25,12,0,2,5,8,"a1840a82",60,"arcaneShip 0/7, kazimierzShip 0/8, miraShip 1/4, preciShip 0/19, siracusaShip 1/18, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, swiftShip 1/38, yanShip 0/13",20,25,5,102], - "ai_0": [1,23,13,0,0,5,8,"c0b11e3b",214,"arcaneShip 0/2, deputShip 1/30, kazimierzShip 0/8, lateranoShip 1/29, miraShip 1/4, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/40, skillfulShip 1/52, suntShip 1/12, swiftShip 1/47",25,25,0,98] + "h_0": [1,26,12,0,2,5,8,"a1840a82",60,"arcaneShip 0/7, kazimierzShip 0/8, miraShip 1/4, preciShip 0/19, siracusaShip 1/18, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, swiftShip 1/38, yanShip 0/13",20,25,5,102], + "ai_0": [1,23,13,0,0,5,8,"e76ee4ed",224,"arcaneShip 0/2, deputShip 1/30, kazimierzShip 0/8, lateranoShip 1/39, miraShip 1/4, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/40, skillfulShip 1/52, suntShip 1/12, swiftShip 1/47",25,25,0,98] }, { "round": 12, - "h_0": [1,25,14,0,0,6,8,"efcfaab5",123,"arcaneShip 0/7, kazimierzShip 0/8, miraShip 1/18, preciShip 1/24, siracusaShip 1/34, soloShip 1/0, suntShip 1/0, swiftShip 1/47, yanShip 0/13",15,31,16,116], - "ai_0": [1,23,12,0,1,6,8,"97827397",254,"arcaneShip 0/2, deputShip 1/32, kazimierzShip 0/8, lateranoShip 1/49, miraShip 1/6, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/42, skillfulShip 1/62, suntShip 1/14, swiftShip 1/49",31,31,0,112] + "h_0": [1,26,14,0,0,6,8,"efcfaab5",123,"arcaneShip 0/7, kazimierzShip 0/8, miraShip 1/18, preciShip 1/24, siracusaShip 1/34, soloShip 1/0, suntShip 1/0, swiftShip 1/47, yanShip 0/13",15,31,16,116], + "ai_0": [1,23,12,0,1,6,8,"3f2e303b",264,"arcaneShip 0/2, deputShip 1/32, kazimierzShip 0/8, lateranoShip 1/59, miraShip 1/6, preciShip 0/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/42, skillfulShip 1/62, suntShip 1/14, swiftShip 1/49",31,31,0,112] }, { "round": 13, - "h_0": [1,15,12,0,2,6,8,"31530f99",169,"arcaneShip 0/7, kazimierzShip 0/8, miraShip 1/30, preciShip 1/33, raidShip 1/0, sargonShip 1/9, siracusaShip 1/46, suntShip 1/0, swiftShip 1/51, yanShip 0/13",8,57,49,130], - "ai_0": [1,13,13,0,2,6,8,"c4308d5d",194,"arcaneShip 0/2, deputShip 0/32, kazimierzShip 0/8, lateranoShip 1/63, miraShip 1/6, preciShip 1/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/54, skillfulShip 0/68, suntShip 1/14, swiftShip 1/55",10,57,47,127] + "h_0": [1,16,12,0,2,6,8,"31530f99",169,"arcaneShip 0/7, kazimierzShip 0/8, miraShip 1/30, preciShip 1/33, raidShip 1/0, sargonShip 1/9, siracusaShip 1/46, suntShip 1/0, swiftShip 1/51, yanShip 0/13",8,57,49,130], + "ai_0": [1,13,13,0,2,6,8,"38248b3d",204,"arcaneShip 0/2, deputShip 0/32, kazimierzShip 0/8, lateranoShip 1/73, miraShip 1/6, preciShip 1/2, raidShip 0/2, sargonShip 0/2, siracusaShip 1/54, skillfulShip 0/68, suntShip 1/14, swiftShip 1/55",7,57,50,127] } ], "players": { @@ -1568,12 +1568,12 @@ "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 8, + "lp": 15, "bandId": "band_amedic", "title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":189,"bossDamage":281870,"dmgDealt":2422133,"gold":144,"itemsEquipped":20,"kills":241,"leaks":76,"lpLost":13,"merges":6,"perfectRounds":9,"refreshes":24}, + "stats": {"activatedLayers":189,"bossDamage":277141,"dmgDealt":2376575,"gold":144,"itemsEquipped":20,"kills":238,"leaks":76,"lpLost":12,"merges":6,"perfectRounds":9,"refreshes":24}, "lineup": ["5_10_a@12,3[1_02_e_b]","5_15_a@11,3[3_11_e_a]","3_13_b@11,8[1_01_e_a]","3_19_b@10,3[4_07_e_a+3_01_e_a]","4_16_a@10,7[4_03_e_b+3_07_e_a]","1_08_b@10,8[4_11_e_b+4_02_e_a]","6_05_a@9,8[6_09_e_a+6_01_e_b]","5_02_a@9,9"], "bonds": ["arcaneShip 0/11","kazimierzShip 0/16","kjeragShip 0/8","miraShip 1/34","preciShip 1/37","sargonShip 1/9","siracusaShip 1/46","soloShip 1/0","suntShip 1/0","swiftShip 1/63","yanShip 0/13"] }, @@ -1582,14 +1582,14 @@ "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 6, + "lp": 13, "bandId": "band_chiave", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":280,"bossDamage":498130,"dmgDealt":3639604,"gold":140,"itemsEquipped":22,"kills":271,"leaks":47,"lpLost":10,"merges":6,"perfectRounds":12,"refreshes":21}, - "lineup": ["1_01_b@12,3[2_01_e_a+3_08_e_b]","4_21_a@12,7","3_21_b@11,3[3_01_e_a+1_05_e_b]","5_20_a@11,8[3_07_e_b]","3_01_b@10,3[4_08_e_b+6_05_e_a]","3_18_a@10,7","4_16_a@10,8[4_11_e_a+4_02_e_a]","6_01_a@9,8[5_09_e_a+3_07_e_a]"], - "bonds": ["arcaneShip 0/2","deputShip 0/32","kazimierzShip 0/8","lateranoShip 1/71","miraShip 1/6","preciShip 0/2","raidShip 0/2","sargonShip 0/2","siracusaShip 1/66","skillfulShip 1/68","suntShip 1/14","swiftShip 1/55"] + "stats": {"activatedLayers":290,"bossDamage":502859,"dmgDealt":3633322,"gold":140,"itemsEquipped":22,"kills":269,"leaks":50,"lpLost":10,"merges":6,"perfectRounds":12,"refreshes":21}, + "lineup": ["1_01_b@12,3[2_01_e_a+3_07_e_b]","4_21_a@12,7","3_21_b@11,3[1_05_e_b+4_08_e_b]","5_20_a@11,8","3_01_b@10,3[5_09_e_a+6_05_e_a]","3_18_a@10,7","4_16_a@10,8[4_11_e_a+4_02_e_a]","6_01_a@9,8[3_07_e_a+3_08_e_b]"], + "bonds": ["arcaneShip 0/2","deputShip 0/32","kazimierzShip 0/8","lateranoShip 1/81","miraShip 1/6","preciShip 0/2","raidShip 0/2","sargonShip 0/2","siracusaShip 1/66","skillfulShip 1/68","suntShip 1/14","swiftShip 1/55"] } }, "standIns": { @@ -1690,8 +1690,8 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":467,"bossDamage":1199875,"dmgDealt":5212489,"gold":144,"itemsEquipped":32,"kills":343,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":44}, - "lineup": ["5_02_a@12,8[3_11_e_a+5_01_e_b]","6_05_a@11,8[2_01_e_b+3_08_e_b]","6_08_a@10,6[3_05_e_a+2_03_e_a]","4_04_a@10,7[4_11_e_a+3_10_e_a]","4_23_b@10,8[6_01_e_a+3_07_e_a]","5_diy1_a@9,4[3_01_e_a+4_04_e_b]","6_07_a@9,8[1_02_e_a+6_09_e_a]","3_05_b@9,9[4_02_e_b+4_04_e_a]"], + "stats": {"activatedLayers":467,"bossDamage":1212084,"dmgDealt":5219781,"gold":144,"itemsEquipped":33,"kills":343,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":44}, + "lineup": ["5_02_a@12,8[3_11_e_a+5_01_e_b]","6_05_a@11,8[2_01_e_b+3_08_e_b]","6_08_a@10,6[2_03_e_a+3_07_e_a]","4_04_a@10,7[4_11_e_a+1_01_e_a]","4_23_b@10,8[6_01_e_a+3_10_e_a]","5_diy1_a@9,4[3_01_e_a+4_04_e_b]","6_07_a@9,8[1_02_e_a+6_09_e_a]","3_05_b@9,9[4_02_e_b+4_04_e_a]"], "bonds": ["preciShip 2/49","raidShip 1/93","sargonShip 1/152","steadShip 1/38","suntShip 1/0","victoriaShip 1/59","visiShip 1/76"] }, "ai_0": { @@ -1704,7 +1704,7 @@ "title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":217,"bossDamage":375125,"dmgDealt":2983148,"gold":140,"itemsEquipped":20,"kills":254,"leaks":35,"lpLost":1,"merges":3,"perfectRounds":9,"refreshes":17}, + "stats": {"activatedLayers":217,"bossDamage":362916,"dmgDealt":2975857,"gold":140,"itemsEquipped":20,"kills":254,"leaks":35,"lpLost":1,"merges":3,"perfectRounds":9,"refreshes":17}, "lineup": ["5_23_a@12,7","4_14_a@12,8","5_20_b@11,7[1_01_e_a+3_11_e_a]","6_05_a@11,8[5_01_e_a+4_10_e_a]","2_04_a@10,6[2_01_e_a+4_03_e_a]","4_16_a@10,8[3_07_e_a+4_08_e_a]","5_12_a@9,8[1_05_e_b+3_03_e_b]","1_08_b@9,9[1_02_e_a+4_06_e_a]"], "bonds": ["kjeragShip 0/2","miraShip 1/19","preciShip 1/10","raidShip 0/30","sargonShip 0/2","siracusaShip 1/116","soloShip 1/0","steadShip 0/2","suntShip 1/0","swiftShip 1/14","visiShip 1/25","yanShip 1/33"] } diff --git a/test/match/bot-smarter.test.js b/test/match/bot-smarter.test.js index 533cd58e..0d52a6e7 100644 --- a/test/match/bot-smarter.test.js +++ b/test/match/bot-smarter.test.js @@ -112,6 +112,11 @@ test('items: 信标 on a bench single; both 博士投影 on a normal operator ev m.pool.take(pairId, m.pool.left(pairId)); const mimic2 = itemTarget(m, ps, giveItem(m, ps, 'chess_item_5_05_e_a')); assert.ok(mimic2 && ![pairA.uid, pairB.uid].includes(mimic2.uid), '拟态物质 not on a pair whose pool is out'); + // 盟约之币 (the same on anyone): a full carrier would lose an item to it (the replace rule, GitHub #263) — every + // deployed operator wears two items here, so a bench operator with free slots takes it + for (const p of deployed()) while (p.items.length < m.gd.equipPerChess) p.items.push(ps.newPiece('item', 'chess_item_3_03_e_a')); + const coin = itemTarget(m, ps, giveItem(m, ps, 'chess_item_1_03_e_a')); + assert.ok(coin && [single.uid, pairA.uid, pairB.uid].includes(coin.uid), '盟约之币 on an operator with a free slot'); m.dispose(); }); diff --git a/test/match/merge.test.js b/test/match/merge.test.js index 41f9b8e7..f5878186 100644 --- a/test/match/merge.test.js +++ b/test/match/merge.test.js @@ -444,3 +444,64 @@ test('equipment: the replace dialog picks WHICH equipped item a third one destro checkInvariants(m); m.dispose(); }); + +test('equipment: a consume-on-equip item on a full carrier replaces the picked item first and leaves the slot free; a refused 博士投影 keeps both (GitHub #263)', () => { + // PRTS 卫戍协议/帮助 "达到上限强行佩戴会改为替换装备:指定一件已佩戴装备替换为将要佩戴的装备,并销毁被指定的装备" + const { m, ps } = prep(27); + const pool = chessOfTier(2).filter((c) => m.pool.has(c) && DATA.chess[c].bonds.length); + let n = 0; + /** A normal operator in the hand wearing two different plain items (older, newer). */ + const fullCarrier = (id = pool[n]) => { + const a = give(m, ps, id); + const [older, newer] = [['chess_item_1_01_e_a', 'chess_item_1_02_e_a'], ['chess_item_3_03_e_a', 'chess_item_1_05_e_a'], ['chess_item_3_06_e_a', 'chess_item_2_04_e_a'], ['chess_item_3_05_e_a', 'chess_item_3_02_e_a'], ['chess_item_2_01_e_a', 'chess_item_3_07_e_a']][n++] + .map((itemId) => giveItem(m, ps, itemId)); + for (const it of [older, newer]) assert.deepEqual(m.handle('p_0', { t: 'g.equip', itemUid: it.uid, targetUid: a.uid }), { ok: true }); + assert.deepEqual(a.items.map((x) => x.uid), [older.uid, newer.uid]); + return { a, older, newer }; + }; + const equip = (it, a, rep) => m.handle('p_0', { t: 'g.equip', itemUid: it.uid, targetUid: a.uid, ...(rep ? { replaceUid: rep.uid } : {}) }); + + // the normal 博士投影 takes the slot of the item the player picked (the NEWER one), not the oldest + let { a, older, newer } = fullCarrier(); + const holo = giveItem(m, ps, 'chess_item_5_06_e_a'); + assert.deepEqual(equip(holo, a, newer), { ok: true }); + assert.deepEqual(a.items.map((x) => x.uid), [older.uid, holo.uid], 'the pick destroyed, the older kept, 博士投影 equipped'); + assert.equal(ps.find(newer.uid), null); + // 盟约之币: the pick is destroyed, the coin pays and is consumed — one item and a free slot are left + ({ a, older, newer } = fullCarrier()); + const coin = giveItem(m, ps, 'chess_item_1_03_e_a'); + const funds = ps.funds; + assert.deepEqual(equip(coin, a, newer), { ok: true }); + assert.equal(ps.funds, funds + 1); + assert.deepEqual(a.items.map((x) => x.uid), [older.uid], 'a free slot is left'); + assert.equal(ps.find(newer.uid), null); + assert.equal(ps.find(coin.uid), null, 'the coin is consumed'); + // 随身身份牌 (picking the OLDER item): the layers go to the carrier's bonds, the newer item stays + ({ a, older, newer } = fullCarrier()); + const card = giveItem(m, ps, 'chess_item_1_04_e_a'); + const bond = DATA.chess[a.id].bonds[0]; + const layers = ps.layers[bond] || 0; + assert.deepEqual(equip(card, a, older), { ok: true }); + assert.equal(ps.layers[bond], layers + 3); + assert.deepEqual(a.items.map((x) => x.uid), [newer.uid]); + assert.equal(ps.find(older.uid), null); + // the golden 博士投影 promotes at once; the item not picked stays on the elite + ({ a, older, newer } = fullCarrier()); + const gold = giveItem(m, ps, 'chess_item_5_06_e_b'); + assert.deepEqual(equip(gold, a, older), { ok: true }); + assert.ok(DATA.chess[a.id].isGolden, 'promoted'); + assert.deepEqual(a.items.map((x) => x.uid), [newer.uid]); + // refused (博士投影 — either quality — on an elite): nothing is destroyed, the order is kept, the item stays in the hand + ({ a, older, newer } = fullCarrier(DATA.chess[pool[n]].goldenId)); + for (const id of ['chess_item_5_06_e_a', 'chess_item_5_06_e_b']) { + const it = giveItem(m, ps, id); + for (const rep of [older, newer, null]) { + assert.deepEqual(equip(it, a, rep), { error: ERR.BAD_TARGET, detail: 'already elite' }, id); + assert.deepEqual(a.items.map((x) => x.uid), [older.uid, newer.uid], `${id}: both kept`); + } + assert.equal(ps.find(it.uid).area, 'hand'); + assert.deepEqual(m.handle('p_0', { t: 'g.destroy', uid: it.uid }), { ok: true }); + } + checkInvariants(m); + m.dispose(); +}); diff --git a/test/ui/gameLogic.test.js b/test/ui/gameLogic.test.js index 9a870a30..7a383f7f 100644 --- a/test/ui/gameLogic.test.js +++ b/test/ui/gameLogic.test.js @@ -489,7 +489,8 @@ describe('placement mirror (canPlace)', () => { const consumable = Object.values(items).find((x) => x.itemType === 'EQUIP' && String(x.kind).startsWith('consume_on_equip')); const cons = item(consumable.id); const ctxC = ctxFor(privWith({ board: [b], hand: [{ idx: 3, piece: cons }] })); - assert.equal(dropIntent(ctxC, cons.uid, { area: 'board', row: 9, col: 3 }).confirmReplace, false, 'consumed on equip: nothing is replaced'); + // consumed on equip: a full carrier still replaces first (GitHub #263, test/ui/leftovers.test.js) + assert.equal(dropIntent(ctxC, cons.uid, { area: 'board', row: 9, col: 3 }).confirmReplace, true, 'consumed on equip: still replaces on a full carrier'); const ar = dropIntent(ctx, art.uid, { area: 'board', row: 12, col: 6 }); assert.deepEqual(ar, { t: 'g.art', fields: { itemUid: art.uid, row: 12, col: 6 } }); for (const i of [mv, back, eqI, eqH, ar]) assert.equal(validateC2S({ t: i.t, ...i.fields }), null, i.t); diff --git a/test/ui/leftovers.test.js b/test/ui/leftovers.test.js index fe664e1e..7c506d06 100644 --- a/test/ui/leftovers.test.js +++ b/test/ui/leftovers.test.js @@ -61,7 +61,7 @@ describe('1. equip-replace dialog', () => { assert.deepEqual(replaceIntent(intent, null).fields, { itemUid: 3, targetUid: 1 }, 'no pick ⇒ no replaceUid'); }); - test('no dialog when a slot is free, the item is consumed on equip, or the equip completes an item merge', () => { + test('no dialog when a slot is free or the equip completes an item merge', () => { const one = ctxOf({ ...priv, board: [{ ...priv.board[0], items: [{ uid: 11, id: A }] }] }); const i1 = dropIntent(one, 3, { area: 'board', row: 9, col: 3 }); assert.equal(i1.confirmReplace, false); @@ -72,9 +72,28 @@ describe('1. equip-replace dialog', () => { assert.equal(equipMerges(twin, 3), true); assert.equal(dropIntent(twin, 3, { area: 'board', row: 9, col: 3 }).confirmReplace, false); assert.equal(equipMerges(ctx, 3), false); - const consumable = Object.values(DATA.items).find((x) => x.itemType === 'EQUIP' && String(x.kind).startsWith('consume_on_equip')); - const cons = ctxOf({ ...priv, hand: [{ uid: 3, kind: 'item', id: consumable.id }, ...priv.hand.slice(1)] }); - assert.equal(dropIntent(cons, 3, { area: 'board', row: 9, col: 3 }).confirmReplace, false); + }); + + test('a consume-on-equip item on a full operator opens the dialog too — all 24, 博士投影 included — except a 博士投影 on an elite (GitHub #263)', () => { + // PRTS 卫戍协议/帮助 "达到上限强行佩戴会改为替换装备": the pick is destroyed, then the item resolves (a free slot is left) + const consumables = Object.values(DATA.items).filter((x) => x.itemType === 'EQUIP' && String(x.kind).startsWith('consume_on_equip')); + assert.equal(consumables.length, 24, '12 consume-on-equip items × normal / golden'); + const holo = ['chess_item_5_06_e_a', 'chess_item_5_06_e_b']; + for (const it of consumables) { + const hand = [{ uid: 3, kind: 'item', id: it.id }, ...priv.hand.slice(1)]; + const cx = ctxOf({ ...priv, hand }); + const intent = dropIntent(cx, 3, { area: 'board', row: 9, col: 3 }); + assert.equal(intent.confirmReplace, true, it.id); + assert.deepEqual(replaceRequest(cx, intent, getChess, getItem).options.map((o) => o.uid), [11, 12], it.id); + const one = ctxOf({ ...priv, board: [{ ...priv.board[0], items: [{ uid: 11, id: A }] }], hand }); + assert.equal(dropIntent(one, 3, { area: 'board', row: 9, col: 3 }).confirmReplace, false, `${it.id}: a free slot`); + // an elite: the server refuses a 博士投影 (nothing would be replaced), every other consumable still replaces + const elite = ctxOf({ ...priv, board: [{ ...priv.board[0], id: getChess(RANGED).goldenId, golden: true }], hand }); + assert.equal(dropIntent(elite, 3, { area: 'board', row: 9, col: 3 }).confirmReplace, !holo.includes(it.id), `${it.id} on an elite`); + } + // an operator in the hand (equippable since 下半) with two items: the same dialog + const benchFull = ctxOf({ ...priv, board: [], hand: [{ uid: 3, kind: 'item', id: holo[0] }, { uid: 1, kind: 'chess', id: RANGED, items: [{ uid: 11, id: A }, { uid: 12, id: B }] }, ...priv.hand.slice(2)] }); + assert.equal(dropIntent(benchFull, 3, { area: 'hand', idx: 1 }).confirmReplace, true); }); test('销毁 only for loose items: an equipped item is locked', () => { @@ -127,6 +146,44 @@ describe('1. equip-replace dialog', () => { assert.equal(itemDestroyable(ctxOf(after), a.uid), false); h.invariants(); }); + + test('real match engine: 博士投影 and 盟约之币 on a full operator — the dialog asks and the picked item is the one destroyed (GitHub #263)', () => { + const h = makeMatch({ mode: 'solo', difficulty: 'FUNNY', humans: 1, seed: 5 }); + h.start(); + h.toPrep(1); + const m = h.m; + const ps = h.ps('p_0'); + const chessId = Object.values(DATA.chess).find((c) => c.visible && !c.isGolden && c.tier === 1).chessId; + const at = legalTileFor(m, ps, chessId); + const unit = give(m, ps, chessId, 'board', at); + const tile = { area: 'board', row: at[0], col: at[1] }; + const [a, b, c] = [giveItem(m, ps, A), giveItem(m, ps, B), giveItem(m, ps, C)]; + for (const it of [a, b]) assert.equal(m.handle('p_0', { t: 'g.equip', itemUid: it.uid, targetUid: unit.uid }).error, undefined); + /** Drop `it` on the operator, pick `pick` in the dialog and send the intent; the operator's items after. */ + const dropAndPick = (it, pick) => { + h.flushAll(); + const cx = ctxOf(h.lastTo('p_0', 'm.private')); + const intent = dropIntent(cx, it.uid, tile); + assert.equal(intent.confirmReplace, true, `${it.id}: the client asks`); + const r = replaceIntent(intent, pick.uid); + assert.equal(m.handle('p_0', { t: r.t, ...r.fields }).error, undefined); + h.flushAll(); + return h.lastTo('p_0', 'm.private').board.find((p) => p.uid === unit.uid).items.map((x) => x.uid); + }; + // 博士投影 (normal) takes the slot of the NEWER item the player picked — it used to push the oldest out unasked + const holo = giveItem(m, ps, 'chess_item_5_06_e_a'); + assert.deepEqual(dropAndPick(holo, b), [a.uid, holo.uid], 'b destroyed, a kept, 博士投影 equipped'); + // 盟约之币 on [a, 博士投影]: the pick (a) is destroyed, the coin pays — 博士投影 and a free slot are left + const coin = giveItem(m, ps, 'chess_item_1_03_e_a'); + const funds = ps.funds; + assert.deepEqual(dropAndPick(coin, a), [holo.uid]); + assert.equal(ps.funds, funds + 1); + // the free slot takes the next item without a dialog + h.flushAll(); + assert.equal(dropIntent(ctxOf(h.lastTo('p_0', 'm.private')), c.uid, tile).confirmReplace, false); + for (const gone of [a, b, coin]) assert.equal(ps.find(gone.uid), null); + h.invariants(); + }); }); describe('2. boss-round countdown and the overtime (DOT) warning', () => {