diff --git a/docs/META.md b/docs/META.md index e685fdee..03966198 100644 --- a/docs/META.md +++ b/docs/META.md @@ -458,7 +458,7 @@ bilibili BV1vzyVBuEN9 ≈ 8:24 / BV1Qkw1zMEoR ≈ 7:25, pointed out in PR #2 — is not consumed: PRTS 下半 记录 备注 "生效时,原干员销毁,获得一名高一阶的随机初始干员(最高六阶)", PRTS 卫戍协议/帮助 "佩戴的 装备无法手动卸除,在失去该干员(干员出售、销毁、合并等)…时自动卸除", players re-inject it every round; player feedback after 0.1.0), 画卷 (copy the operator in range with its -items), 教鞭 / “神秘顾客” (a random bounty is added). +items — the copied items arrive unequipped in the hand, PRTS 画卷 备注 "获得的装备为未装备状态"), 教鞭 / “神秘顾客” (a random bounty is added). **教鞭 / “神秘顾客” stay a random bounty (deliberate).** The official Arts open a personal 悬赏 choice ("选择一项(特殊)悬赏任务进行挑战", `choice_event hunter_band_1`). The server applies a random bounty instead: content diff --git a/docs/research/04-items.md b/docs/research/04-items.md index 9d8c1530..740a78d3 100644 --- a/docs/research/04-items.md +++ b/docs/research/04-items.md @@ -352,7 +352,7 @@ Combo check: `equip_chess_id` in blackboard is the partner's id WITHOUT the `_a/ - Buff keys: 画卷 `trap_copy_front_char{}`; 教鞭 `trap_create_self_choice{choice_event=hunter_band_1}`; 神秘顾客 additionally `trap_disney_special{count=1}`. - `hunter_band_1` (choiceType PERSONAL_CHOOSE, "悬赏决策") pool is server-side. [ASSUMED] offer 3 random `enemyeffect_b_*` bounties (e.g. 碎骨·悬赏 +1, 萨卡兹百夫长·悬赏 +2, 泥岩·悬赏 +4, 锏·悬赏 +5, “复仇者”·悬赏 +6 funds; buff `add_enemy_kill_gain_coin{count,enemy_id,coin,round=1}`): adds 1 enemy to your next battle, killer gets `coin` funds. -- 画卷 copy: duplicates the operator (same chess id incl. elite `_b`) AND clones its equipped items into the copy [DATA desc "包括精锐状态和装备"]; copy goes to hand [ASSUMED]; if hand full -> temporary hand. +- 画卷 copy: duplicates the operator (same chess id incl. elite `_b`) AND its equipped items [DATA desc "包括精锐状态和装备"]; the copied items arrive unequipped (PRTS 画卷 备注 "使用后销毁,获得的装备为未装备状态"), gained like any item — the hand, overflow temp (destroyed with the usual toast when both are full); copy goes to hand [ASSUMED]; if hand full -> temporary hand. ## 8. Item sources other than the shop [DATA] diff --git a/server/sim/content/items/meta.js b/server/sim/content/items/meta.js index ea90ac93..786dff53 100644 --- a/server/sim/content/items/meta.js +++ b/server/sim/content/items/meta.js @@ -116,12 +116,14 @@ export function registerMeta(registry) { })); } - // 画卷 — trap_copy_front_char: copy the operator in range (elite status included) with its equipment. A copied - // normal item that completes a pair with an owned one merges at once and the golden stays in the hand (research 04 - // addendum "两个同名道具(无论是否被装备)会自动合并…并自动返回整备区"); the built-in equipped that golden on the copy. - // A copy that completes a three-copy merge (GitHub #389, PR #390): the elite wears nothing — the promotion returns the - // copies' equipment to the hand (PRTS 帮助 "在失去该干员(…合并等)…时自动卸除") — and the copied items stay in the hand - // too (PRTS 画卷 备注 "获得的装备为未装备状态"), a copied normal item merging with the returned original. + // 画卷 — trap_copy_front_char: copy the operator in range (elite status included) and its equipment. The copied items + // are gained unequipped, like any gained item — the hand, overflow temp, destroyed with the usual toast when both are + // full — never put on the copy (PRTS 画卷 备注 "使用后销毁,获得的装备为未装备状态"; until 0.2.2 a copied item that did + // not merge was equipped onto the copy). A copied normal item that completes a pair with an owned one merges at once, + // the golden in the hand (research 04 addendum "两个同名道具(无论是否被装备)会自动合并…并自动返回整备区"). A copy that + // completes a three-copy merge (GitHub #389, PR #390): the promotion returns the copies' equipment to the hand too + // (PRTS 帮助 "在失去该干员(…合并等)…时自动卸除"), so the elite wears nothing and a copied normal item merges with the + // returned original. wrap(registry, 'chess_item_6_02_m', () => ({ onArt(ctx, ev) { const target = (ev.targets || []).find((p) => p && p.kind === 'chess'); @@ -132,12 +134,7 @@ export function registerMeta(registry) { const itemIds = (target.items || []).map((it) => it.id); const copy = ctx.grantChess(target.id, { requirePool: false, source: 'item:chess_item_6_02_m' }); if (!copy) { ev.error = 'HAND_FULL'; return; } - const promoted = copy.id !== target.id; - for (const itemId of itemIds) { - const got = ctx.grantItem(itemId, { source: 'item:chess_item_6_02_m' }); - const holder = got && !promoted ? ctx.piece(copy.uid) : null; - if (got && got.id === itemId && holder && holder.kind === 'chess') ctx.equipDirect(got.uid, holder.uid); - } + for (const itemId of itemIds) ctx.grantItem(itemId, { source: 'item:chess_item_6_02_m' }); }, })); diff --git a/server/sim/content/kits/ops/chess_char_5_15-thorn2.js b/server/sim/content/kits/ops/chess_char_5_15-thorn2.js index 2c32a73e..8d90dcc5 100644 --- a/server/sim/content/kits/ops/chess_char_5_15-thorn2.js +++ b/server/sim/content/kits/ops/chess_char_5_15-thorn2.js @@ -100,7 +100,9 @@ export default { // (GitHub #124 「引星棘刺一技能不会在满技力时自动释放」) trigger: { rule: 'SP_FULL' }, onStart({ battle, unit }) { - const t = battle.alliesInGrid(unit).filter((a) => a.hp > 0).sort((a, b) => a.hpRatio - b.hpRatio || b.blocking.length - a.blocking.length || dist(a, unit) - dist(b, unit) || a.id - b.id)[0]; + // PRTS 备注 「※优先选择生命比例最低>最晚部署的我方单位(不含装置职业单位)」: the lowest HP ratio, a tie to the + // latest deployment (alliesInGrid has no devices); until 0.2.2 a tie went to more blocking, then the nearer + const t = battle.alliesInGrid(unit).filter((a) => a.hp > 0).sort((a, b) => a.hpRatio - b.hpRatio || b.deploySeq - a.deploySeq || a.id - b.id)[0]; if (!t) return; const key = `thorn2:guard:${unit.id}:${unit.mem.zoneSeq = (unit.mem.zoneSeq ?? 0) + 1}`; // its regen buff ("效果均可叠加") const z = { type: 'guard', r: t.tileR, c: t.tileC, x: t.x, y: t.y, t: 0, acc: 0, dur: num(bb.projectile_delay_time, 6) + extend(battle, unit), key }; diff --git a/test/content/items.test.js b/test/content/items.test.js index 6762f225..2a23cb79 100644 --- a/test/content/items.test.js +++ b/test/content/items.test.js @@ -1121,10 +1121,11 @@ test('画卷 (Art): copies the operator on the tile / in front with its elite st const copy = ps.hand.find((p) => p && p.kind === 'chess'); assert.equal(copy && copy.id, DATA.chess[cid].goldenId, 'elite copy in the hand'); // the copied 激光发射器 pairs with the original's (items merge "无论是否被装备") ⇒ the golden goes to the hand; - // 变形同构体 never merges ⇒ the copy wears it too - assert.deepEqual(copy.items.map((x) => x.id), [A('6_09')]); + // 变形同构体 never merges ⇒ its copy waits in the hand too, unequipped (PRTS 画卷 备注 "获得的装备为未装备状态"; + // until 0.2.2 the copy wore it) + assert.deepEqual(copy.items.map((x) => x.id), [], 'the copy wears nothing'); assert.deepEqual(ps.find(target.uid).piece.items.map((x) => x.id), [A('6_09')]); - assert.deepEqual(handIds(ps, 'item'), [B('3_03')], 'merged golden in the hand (regression: was equipped on the copy)'); + assert.deepEqual(handIds(ps, 'item').slice().sort(), [B('3_03'), A('6_09')].sort(), 'the merged golden and the copied 变形同构体 in the hand'); const art2 = giveItem(m, ps, 'chess_item_6_02_m'); assert.equal(m.handle('p_0', { t: 'g.art', itemUid: art2.uid, row: 11, col: 7 }).error, 'BAD_TARGET', 'no operator in range'); cover('chess_item_6_02_m'); diff --git a/test/content/kits_alt_t5.test.js b/test/content/kits_alt_t5.test.js index 0f4bdbb6..4baea503 100644 --- a/test/content/kits_alt_t5.test.js +++ b/test/content/kits_alt_t5.test.js @@ -1061,6 +1061,33 @@ test('引星棘刺 S1 度算浪波: two alchemy units on one ally add their DEF done(h); }); +test('引星棘刺 S1 度算浪波: at an HP-ratio tie the unit goes to the latest deployed ally (PRTS 备注 「优先选择生命比例最低>最晚部署的我方单位」)', () => { + for (const id of pair('15')) { + const h = run({ + defs: { chess: { t_first: ally('t_first'), t_last: ally('t_last') } }, + // t_first stands nearer to her, t_last deploys after it (until 0.2.2 a tie went to more blocking, then the nearer) + units: [entry(id, 'skchr_thorn2_1', { row: 10, col: 3 }), { chessId: 't_first', row: 11, col: 4 }, { chessId: 't_last', row: 9, col: 5 }], + enemies: [], + }); + const u = sel(h, id, 'skchr_thorn2_1'); + const first = h.unit('t_first'), last = h.unit('t_last'); + h.step(); + assert.ok(last.deploySeq > first.deploySeq, 't_last is the later deployment'); + first.hp = first.s.maxHp * 0.5; last.hp = last.s.maxHp * 0.5; + cast(h, u); + const z = u.mem.zones.find((x) => x.type === 'guard'); + assert.deepEqual([z.r, z.c], [9, 5], 'the latest deployed of the two at 50 %'); + // the lowest ratio still comes first + last.hp = last.s.maxHp; first.hp = first.s.maxHp * 0.4; + const n0 = u.skill.activations; + u.skill.gainSp(1000); + assert.ok(h.runUntil(() => u.skill.activations > n0, 15), 'cast again'); + const z2 = u.mem.zones.filter((x) => x.type === 'guard').at(-1); + assert.deepEqual([z2.r, z2.c], [11, 4], 'a lower ratio beats a later deployment'); + done(h); + } +}); + test('引星棘刺 S1 度算浪波 (AUTO): fires as soon as its SP is full, no enemy needed (GitHub #124)', () => { for (const id of pair('15')) { const h = run({ diff --git a/test/golden/roster.json b/test/golden/roster.json index 64e19ad1..fc258faf 100644 --- a/test/golden/roster.json +++ b/test/golden/roster.json @@ -2017,64 +2017,64 @@ "round": 12, "seed": 3754088384, "waveId": "act1autochess_h05", - "time": 214.93, - "ticks": 6448, + "time": 216.03, + "ticks": 6481, "reason": "cleared", - "killed": 54, + "killed": 39, "total": 167, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 203, + "rngDraws": 182, "errors": {"count":0,"kinds":[]}, "players": { "p1": { - "killed": 54, + "killed": 39, "total": 167, "perfect": false, - "leaks": 113, + "leaks": 128, "leaksUncounted": 0, "coins": 2, - "dmg": 579384, + "dmg": 391926, "bossDmg": 0, - "heal": 69092, - "deaths": 26, - "layerGains": {"indomShip":75} + "heal": 47767, + "deaths": 30, + "layerGains": {"indomShip":85} } }, "units": [ - ["p1",1,"op","chess_char_5_11_a",0,null,0,0,185,6,24682,19544,18577,52,11], - ["p1",2,"op","chess_char_5_19_a",0,null,3241,1,200,8,50200,14729,7170,23,3], - ["p1",3,"op","chess_char_5_13_a",0,null,681.15,1,202,6,179758,34866,5612,46,1], - ["p1",4,"op","chess_char_5_14_a",0,null,1331.1,1,203,0,4188,13059,3998,14,2], - ["p1",5,"op","chess_char_5_06_b",0,"uniequip_003_etlchi",2992,1,204,15,169257,19787,11958,43,10], - ["p1",6,"op","chess_char_5_17_a",0,null,723.48,1,193,1,20066,17784,192,37,9], - ["p1",7,"op","chess_char_5_12_a",1,null,1404,1,3,3,15355,4263,3154,15,3], - ["p1",8,"op","chess_char_5_15_a",0,null,769.65,1,4,7,30505,713,557,126,34], - ["p1",9,"op","chess_char_5_20_a",0,null,0,0,188,0,7281,3522,198,16,1], - ["p1",10,"op","chess_char_5_21_a",0,null,0,0,191,0,5581,7990,180,9,0], - ["p1",11,"op","chess_char_5_22_a",0,null,1237,1,5,8,69963,3136,3136,103,4], - ["p1",12,"op","chess_char_5_23_a",0,null,1422,1,205,0,0,8541,13622,21,7], + ["p1",1,"op","chess_char_5_11_a",0,null,2184.38,1,201,4,20958,13633,14291,46,11], + ["p1",2,"op","chess_char_5_19_a",0,null,2021.06,1,196,5,53968,15830,3047,30,2], + ["p1",3,"op","chess_char_5_13_a",0,null,0,0,146,2,96135,31535,1666,34,1], + ["p1",4,"op","chess_char_5_14_a",0,null,1520.35,1,205,3,8189,4996,1320,24,2], + ["p1",5,"op","chess_char_5_06_b",0,"uniequip_003_etlchi",0,0,167,10,111978,15012,7732,25,6], + ["p1",6,"op","chess_char_5_17_a",0,null,2250,1,204,1,13924,20073,1638,27,7], + ["p1",7,"op","chess_char_5_12_a",1,null,0,0,3,3,13568,3752,2348,12,2], + ["p1",8,"op","chess_char_5_15_a",0,null,0,0,4,4,16352,2127,1201,72,19], + ["p1",9,"op","chess_char_5_20_a",0,null,0,0,188,0,7091,4808,167,15,1], + ["p1",10,"op","chess_char_5_21_a",0,null,0,0,190,0,4781,7272,227,8,0], + ["p1",11,"op","chess_char_5_22_a",0,null,1237,1,203,7,42434,3417,1483,67,2], + ["p1",12,"op","chess_char_5_23_a",0,null,0,0,158,0,0,5524,12141,19,7], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], - ["p1","~1","token","token_10015_dusk_drgn",null,null,1947.38,0,38,0,2547,787,737,8,null] + ["p1","~1","token","token_10015_dusk_drgn",null,null,1716.76,0,38,0,2547,787,507,8,null] ], "enemies": { - "enemy_1005_yokai_3": [1,0,1,5415,17555], - "enemy_1009_lurker": [72,19,53,995,136536], + "enemy_1005_yokai_3": [1,0,1,6177,9927], + "enemy_1009_lurker": [72,17,55,766,100625], "enemy_10159_mntrjn": [1,0,1,0,25020], - "enemy_1019_jshoot_2": [51,24,27,18021,164779], + "enemy_1019_jshoot_2": [51,14,37,18364,123264], "enemy_1118_lidbox_2": [1,1,0,39,14774], "enemy_1196_msfyin": [1,1,0,0,3], - "enemy_1252_lysytb_2": [1,0,1,338,11250], - "enemy_1364_spnaxe_2": [1,0,1,4584,15996], - "enemy_1404_msnip": [36,9,27,111370,227827], - "enemy_1516_jakill": [1,0,1,7554,10944], - "enemy_9011_acrefr": [1,0,1,408,6100] + "enemy_1252_lysytb_2": [1,0,1,373,12737], + "enemy_1364_spnaxe_2": [1,0,1,10148,8334], + "enemy_1404_msnip": [36,6,30,83483,139086], + "enemy_1516_jakill": [1,0,1,9418,5522], + "enemy_9011_acrefr": [1,0,1,0,3435] }, - "hooks": [1,205,6448,1165,1165,4391,4433,16853,80,194,85,83,0,27673,241,133,167,113,1,86,15,0,6], - "events": {"atk":1276,"bounty":1,"deploy":36,"die":81,"dmg":4429,"engage":12,"fx":107,"heal":69,"layer":15,"leak":113,"skill":168,"spawn":182,"status":237}, - "fx": {"aoe":2,"crit":6,"dodge":6,"dollEnd":3,"garrison":15,"healAoe":11,"hpShare":1,"palsy":1,"reborn":2,"revive":11,"sp":5,"substitute":6,"summon":1,"swap":3,"zone":34}, - "snaps": "0092f82c 0dd327ad 53a3d810 58ba1139 90f0fe1e cb512e37 ceb2cf74 a613cd55 5ce6b6e5 d58de777 28df897a f1e4ae5f b4781ed7 3af8405e 2c626d36 ce72c9fa e19c7d08 6db15d3a b2ed2de3 dadece28 8a66c30e ce8ff9d7" + "hooks": [1,205,6481,1015,1015,3503,3526,10073,69,198,60,59,0,21941,201,111,167,128,1,75,17,0,4], + "events": {"atk":1090,"bounty":1,"deploy":36,"die":70,"dmg":3522,"engage":12,"fx":86,"heal":51,"layer":17,"leak":128,"skill":119,"spawn":182,"status":212}, + "fx": {"aoe":2,"crit":4,"dodge":4,"dollEnd":6,"garrison":17,"healAoe":11,"hpShare":1,"palsy":1,"reborn":2,"revive":10,"sp":2,"substitute":6,"summon":1,"zone":19}, + "snaps": "0092f82c 48a2bc74 b6ee7f69 2bd2b1d8 2b402a77 a6de3974 8ee0fed8 13615544 a83cdaa1 f1824fc8 28b8a693 695a9e0a 42c35de0 0f609ec1 ffb4425a 3841da00 aba0f9db ff2e4a84 c0d35663 5c9e36ae 684e8e87 1a6513d1" }, "roster-034": { "about": "pass 1 band 6: chess_char_5_08_b chess_char_5_11_b chess_char_5_19_b chess_char_5_13_b chess_char_5_14_b chess_char_5_07_b chess_char_5_17_b chess_char_5_10_b chess_char_5_12_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b", @@ -2092,22 +2092,22 @@ "total": 104, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 565, + "rngDraws": 571, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":90,"total":104,"perfect":false,"leaks":14,"leaksUncounted":0,"coins":8,"dmg":5110704,"bossDmg":0,"heal":120360,"deaths":1,"layerGains":{}}}, + "players": {"p1":{"killed":90,"total":104,"perfect":false,"leaks":14,"leaksUncounted":0,"coins":8,"dmg":5114900,"bossDmg":0,"heal":118910,"deaths":1,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_5_08_b",0,"uniequip_003_horn",1512.22,1,7,28,1726631,34150,947,96,84], - ["p1",2,"op","chess_char_5_11_b",0,"uniequip_003_demkni",3477.62,1,10,0,39384,11346,66820,184,36], - ["p1",3,"op","chess_char_5_19_b",0,"none",3682,1,11,4,178330,6313,6313,129,5], - ["p1",4,"op","chess_char_5_13_b",0,"uniequip_003_ghost2",3448.92,1,13,3,109842,29514,29300,210,5], - ["p1",5,"op","chess_char_5_14_b",0,"none",3261,1,15,0,16840,13747,13949,217,20], - ["p1",6,"op","chess_char_5_07_b",0,"uniequip_003_surtr",2680,1,8,7,313834,25889,788,121,56], - ["p1",7,"op","chess_char_5_17_b",0,"uniequip_003_f12yin",538.04,1,97,6,252445,5368,450,89,42], - ["p1",8,"op","chess_char_5_10_b",0,"uniequip_003_lisa",1470,1,4,5,160605,0,0,120,6], - ["p1",9,"op","chess_char_5_12_b",1,"uniequip_003_dusk",1667,1,5,32,1794727,0,0,91,6], - ["p1",10,"op","chess_char_5_15_b",0,"none",1907.5,1,12,1,57355,0,0,166,82], + ["p1",1,"op","chess_char_5_08_b",0,"uniequip_003_horn",2996.09,1,7,24,1724859,34441,947,96,84], + ["p1",2,"op","chess_char_5_11_b",0,"uniequip_003_demkni",3087.35,1,10,0,39266,12654,69512,184,38], + ["p1",3,"op","chess_char_5_19_b",0,"none",3682,1,11,3,176264,6843,6948,120,4], + ["p1",4,"op","chess_char_5_13_b",0,"uniequip_003_ghost2",3197.32,1,13,4,109272,27806,27516,210,5], + ["p1",5,"op","chess_char_5_14_b",0,"none",3261,1,15,0,16840,11013,10956,217,19], + ["p1",6,"op","chess_char_5_07_b",0,"uniequip_003_surtr",2680,1,8,7,311939,25889,788,122,55], + ["p1",7,"op","chess_char_5_17_b",0,"uniequip_003_f12yin",875.96,1,97,6,252678,5030,450,89,42], + ["p1",8,"op","chess_char_5_10_b",0,"uniequip_003_lisa",1470,1,4,5,159475,0,0,120,6], + ["p1",9,"op","chess_char_5_12_b",1,"uniequip_003_dusk",1667,1,5,35,1804443,0,0,91,6], + ["p1",10,"op","chess_char_5_15_b",0,"none",1907.5,1,12,1,57321,0,0,166,86], ["p1",11,"op","chess_char_5_20_b",0,"uniequip_003_aglina",1352,1,14,2,213658,1794,1794,138,6], - ["p1",12,"op","chess_char_5_21_b",0,"none",3330.35,1,6,2,247052,0,0,57,3], + ["p1",12,"op","chess_char_5_21_b",0,"none",3330.35,1,6,3,248885,0,0,57,3], ["-","~1","device","trap_1105_accrate",null,null,100,0,1,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null] @@ -2119,16 +2119,16 @@ "enemy_1165_duhond_2": [1,1,0,114,24767], "enemy_1204_msfhu": [1,1,0,0,12], "enemy_1288_duskls": [1,1,0,1594,30964], - "enemy_1381_winman": [27,25,2,4083,938899], - "enemy_1381_winman_2": [39,31,7,24737,1719910], - "enemy_1387_winshd": [30,26,2,76127,2104525], + "enemy_1381_winman": [27,25,2,4053,938899], + "enemy_1381_winman_2": [39,31,7,24234,1723090], + "enemy_1387_winshd": [30,26,2,74009,2105541], "enemy_1425_lrcmra_2": [1,1,0,11329,154793], "enemy_2009_csaudc": [1,1,0,2690,49534] }, - "hooks": [1,120,6900,2558,2558,3995,3996,8433,91,103,351,349,0,31469,6479,104,104,11,1,92,0,0,8], - "events": {"atk":3070,"bounty":4,"deploy":13,"die":92,"dmg":3985,"engage":12,"fx":326,"heal":36,"leak":11,"skill":700,"spawn":119,"status":807}, - "fx": {"aoe":20,"crit":14,"dodge":8,"ember":1,"healAoe":36,"hpShare":5,"sp":139,"zone":103}, - "snaps": "de3bd2e2 23f28d96 7a63c0f2 28a9a6fe e6984bca 8eae402d 2b24991d c8788f23 5a001b21 0fed151a 87c013c7 b38057af 123bb336 e5b070fc 7fac01fd fb1d6e4d 2f2bc32b d3c63947 a34e6643 97d460d1 3c1349d8 783047e8 2de5f6f2" + "hooks": [1,120,6900,2553,2553,3990,3990,8324,91,103,354,352,0,31724,6540,104,104,11,1,92,0,0,9], + "events": {"atk":3068,"bounty":4,"deploy":13,"die":92,"dmg":3979,"engage":12,"fx":330,"heal":38,"leak":11,"skill":706,"spawn":119,"status":806}, + "fx": {"aoe":19,"crit":12,"dodge":9,"ember":1,"healAoe":38,"hpShare":5,"sp":138,"zone":108}, + "snaps": "de3bd2e2 7b2cb2f6 387a1a50 7d4a972d 1e9785f0 bfbc181b 46e0e6f7 d028a75e 0983234c 674b285e 976ff8d6 12ccf30d acf0e4cd 0a9c1ac1 b2bbe21c 3ef3b750 153d049c ee9a46f5 39828267 5064f766 65d6e402 cb0cb3b9 3bbac53a" }, "roster-035": { "about": "pass 1 band 6: chess_char_6_03_a chess_char_6_06_a chess_char_6_07_a chess_char_5_22_b chess_char_5_23_b chess_char_6_01_a chess_char_6_02_a chess_char_6_04_a chess_char_6_05_a chess_char_6_08_a chess_char_6_09_a chess_char_6_11_a token_10017_skadi2_dedant token_10030_mlyss_wtrman", diff --git a/test/match/scroll-copy-merge.test.js b/test/match/scroll-copy-merge.test.js index ca4a597c..795b38e6 100644 --- a/test/match/scroll-copy-merge.test.js +++ b/test/match/scroll-copy-merge.test.js @@ -111,3 +111,44 @@ test('Picture Scroll: a full hand and temp without promotion keeps the refused s assert.deepEqual(ps.privateView(), before); h.invariants(); }); + +// PRTS 画卷 备注 「使用后销毁,获得的装备为未装备状态」: without a promotion too, the copied items are gained like any item — +// the hand, overflow temp — and never put on the copy (until 0.2.2 a copy that did not merge was equipped onto it) +const ADV = [HAMMER, SHIELD].map((id) => id.replace(/_a$/, '_b')); +const looseItems = (ps) => [...ps.hand, ...ps.temp].filter((p) => p?.kind === 'item').map((p) => p.id).sort(); +for (const golden of [false, true]) { + test(`Picture Scroll: one ${golden ? 'elite' : 'normal'} target — the copied (advanced) items arrive unequipped in the hand`, (t) => { + const { h, m, ps, target, cast } = prep(t, { copies: 1, golden, items: ADV }); + assert.deepEqual(cast(), OK); + const copy = ps.allChess().find((p) => p.id === target.id && p !== target); + assert.ok(copy, 'the copy was gained'); + assert.deepEqual(copy.items, [], 'the copy wears nothing'); + assert.deepEqual(target.items.map((p) => p.id).sort(), [...ADV].sort(), 'the target keeps its own'); + assert.deepEqual(looseItems(ps), [...ADV].sort(), 'the copied items wait in the hand'); + assert.equal(m.gd.chess(copy.id).isGolden, golden); + h.invariants(); + }); +} + +test('Picture Scroll: a full hand — the copy and its items overflow into temp; an item with no slot left is destroyed like any gained item', (t) => { + for (const free of [3, 2]) { + const { h, ps, target, cast } = prep(t, { copies: 1, items: ADV }); + while (ps.hand.some((p) => p == null)) assert.ok(ps.acquireItem(FILLER)); + while (ps.temp.filter((p) => p == null).length > free) assert.ok(ps.acquireItem(FILLER)); + const toasts = () => h.allTo('p_0', 'm.toast').map((x) => JSON.stringify(x)); + const before = toasts().length; + assert.deepEqual(cast(), OK); + const copy = ps.allChess().find((p) => p.id === target.id && p !== target); + const where = copy ? ps.find(copy.uid)?.area : null; + assert.ok(where === 'temp' || where === 'hand', `free ${free}: the copy was gained into temp (the hand fills from temp once the scroll left)`); + assert.deepEqual(copy.items, [], `free ${free}: the copy wears nothing`); + assert.deepEqual(target.items.map((p) => p.id).sort(), [...ADV].sort()); + const got = looseItems(ps).filter((id) => id !== FILLER); + if (free === 3) assert.deepEqual(got, [...ADV].sort(), 'room for the copy and both items'); + else { + assert.equal(got.length, 1, 'room for the copy and one item: the other is destroyed'); + assert.ok(toasts().slice(before).some((s) => s.includes('整备区已满,获得的装备已销毁')), 'with the usual toast'); + } + h.invariants(); + } +});