diff --git a/public/js/render/fx/kinds.js b/public/js/render/fx/kinds.js index 6875fc1d..22b4eb4b 100644 --- a/public/js/render/fx/kinds.js +++ b/public/js/render/fx/kinds.js @@ -67,6 +67,12 @@ export const FX_KINDS = Object.freeze({ // 限伤 (sim/damage.js leaderHitCancelled): a leader's hit of ≥ 300000 dealt nothing — the official shows no number and // no effect [ASSUMED], so nothing is drawn (`a: 'none'`) hitCap: { a: 'none', c: 0xffffff }, + // 自选 operator kits (0.2.0): 逻各斯, 真言, 维伊 (O3); 早露, 提丰, 娜仁图亚 (O7); 嵯峨's 重伤 (O8) + bulletClear: { a: 'wave', c: 0xd8b0ff }, logosLexicon: { a: 'mark', c: 0xc9a2ff }, logosExecute: { a: 'strike', c: 0xb07dff }, + mantraArc: { a: 'bolt', c: 0xc77dff }, mantraGrant: { a: 'mark', c: 0xc77dff }, mantraGate: { a: 'reticle', c: 0xc77dff }, + veenBounce: { a: 'counter', c: 0xffd27a }, veenVolley: { a: 'volley', c: 0xffd27a }, + harpoon: { a: 'beam', c: 0xd0c0a0 }, arrowRain: { a: 'volley', c: 0xfff2d0 }, mark: { a: 'reticle', c: 0xff7b8a }, + bounce: { a: 'counter', c: 0xffd9a0 }, dying: { a: 'mark', c: 0xff5a4a }, // beams beam: { a: 'beam', c: 0xff7a5a }, link: { a: 'beam', c: 0x9ff0dc }, lightning: { a: 'bolt', c: 0xc9a2ff }, tentacle: { a: 'beam', c: 0x5fe0ff }, sandChains: { a: 'beam', c: 0xd8c8a0 }, sandChainsCharged: { a: 'beam', c: 0xffd45a }, diff --git a/test/content/op_heyak.test.js b/test/content/op_heyak.test.js index 17bd0ded..c074d7e9 100644 --- a/test/content/op_heyak.test.js +++ b/test/content/op_heyak.test.js @@ -80,12 +80,14 @@ test('霍尔海雅 in every 自选 form: her operator kit (all three skills auth [{ maxHp: 120, atk: 40 }, { maxHp: 175, atk: 65 }, { maxHp: 105, atk: 32, def: 15 }, { maxHp: 140, atk: 45, def: 25 }, { maxHp: 100, atk: 55 }, { maxHp: 130, atk: 80 }]); }); -test('a 自选 pick: 霍尔海雅 is offered at tiers 5 and 6 and a roster with her (ISW-A included) passes validateDiyPicks', () => { +test('a 自选 pick: 霍尔海雅 is offered at tiers 5 and 6 and a roster with her passes validateDiyPicks (ISW-A, a 集成战略 module, is refused)', () => { const data = { chess: CHESS, backups: BACKUPS }; assert.ok(KITTED_CHARS.includes(ID)); for (const t of [5, 6]) assert.ok(diyPool(t, { data, kitted: KITTED_CHARS }).includes(ID), `tier ${t}`); - assert.deepEqual(validateDiyPicks({ [SLOT[6]]: { charId: ID, skillIndex: 2, uniEquipId: ISWA } }, { data, kitted: KITTED_CHARS }), - { ok: true, picks: { [SLOT[6]]: { charId: ID, skillIndex: 2, uniEquipId: ISWA } } }); + assert.equal(validateDiyPicks({ [SLOT[6]]: { charId: ID, skillIndex: 2, uniEquipId: ISWA } }, { data, kitted: KITTED_CHARS }).error, 'BAD_TARGET', + 'a 集成战略 module (ISW-A) is not a 自选 choice (shared/diy.js, W5K)'); + assert.deepEqual(validateDiyPicks({ [SLOT[6]]: { charId: ID, skillIndex: 2, uniEquipId: CCRX } }, { data, kitted: KITTED_CHARS }), + { ok: true, picks: { [SLOT[6]]: { charId: ID, skillIndex: 2, uniEquipId: CCRX } } }); }); test('S1 但为求索 (AUTO, 1 / 2 charges, data DEFAULT): the next attack hits two enemies at 200 % / 240 % ATK arts; with one enemy only it lifts it 2.5 / 3 s — before 传承终焉, so that hit is × 1.2 and silences', () => { diff --git a/test/content/op_irene.test.js b/test/content/op_irene.test.js index 0f6a1f90..76ea9212 100644 --- a/test/content/op_irene.test.js +++ b/test/content/op_irene.test.js @@ -80,12 +80,14 @@ test('艾丽妮 in every 自选 form: her operator kit (all three skills authore [[{ atk: 50, def: 35 }, { atk: 65, def: 50 }], [{ atk: 50, aspd: 5 }, { atk: 80, aspd: 7 }], [{ maxHp: 240, atk: 40 }, { maxHp: 300, atk: 65 }]]); }); -test('a 自选 pick: 艾丽妮 is offered at tiers 5 and 6 (she has a kit) and a roster with her passes validateDiyPicks', () => { +test('a 自选 pick: 艾丽妮 is offered at tiers 5 and 6 (she has a kit) and a roster with her passes validateDiyPicks (ISW-A, a 集成战略 module, is refused)', () => { const data = { chess: CHESS, backups: BACKUPS }; assert.ok(KITTED_CHARS.includes(IRENE)); for (const t of [5, 6]) assert.ok(diyPool(t, { data, kitted: KITTED_CHARS }).includes(IRENE), `tier ${t}`); - assert.deepEqual(validateDiyPicks({ [SLOT[5]]: { charId: IRENE, skillIndex: 1, uniEquipId: ISWA } }, { data, kitted: KITTED_CHARS }), - { ok: true, picks: { [SLOT[5]]: { charId: IRENE, skillIndex: 1, uniEquipId: ISWA } } }); + assert.equal(validateDiyPicks({ [SLOT[5]]: { charId: IRENE, skillIndex: 1, uniEquipId: ISWA } }, { data, kitted: KITTED_CHARS }).error, 'BAD_TARGET', + 'a 集成战略 module (ISW-A) is not a 自选 choice (shared/diy.js, W5K)'); + assert.deepEqual(validateDiyPicks({ [SLOT[5]]: { charId: IRENE, skillIndex: 1, uniEquipId: SWOX } }, { data, kitted: KITTED_CHARS }), + { ok: true, picks: { [SLOT[5]]: { charId: IRENE, skillIndex: 1, uniEquipId: SWOX } } }); }); test('T1 审判之火 vs an air unit: every physical instance ignores 50 % (SWO-X stage 3: 55 %) of its DEF with no random draw; a levitated enemy is an air unit; arts untouched', () => { diff --git a/test/content/op_typhon.test.js b/test/content/op_typhon.test.js index 47ad9d0d..3cacbd65 100644 --- a/test/content/op_typhon.test.js +++ b/test/content/op_typhon.test.js @@ -76,12 +76,14 @@ test('提丰 in every 自选 form: her operator kit (all three skills authored), [{ maxHp: 120, atk: 60 }, { maxHp: 210, atk: 90 }, { atk: 70, aspd: 5 }, { atk: 94, aspd: 7 }, { maxHp: 100, atk: 70 }, { maxHp: 150, atk: 90 }]); }); -test('a 自选 pick: 提丰 is offered at tiers 5 and 6 (she has a kit) and a roster with her passes validateDiyPicks', () => { +test('a 自选 pick: 提丰 is offered at tiers 5 and 6 (she has a kit) and a roster with her passes validateDiyPicks (ISW-A, a 集成战略 module, is refused)', () => { const data = { chess: CHESS, backups: BACKUPS }; assert.ok(KITTED_CHARS.includes(TYPHON)); for (const t of [5, 6]) assert.ok(diyPool(t, { data, kitted: KITTED_CHARS }).includes(TYPHON), `tier ${t}`); - assert.deepEqual(validateDiyPicks({ [SLOT[6]]: { charId: TYPHON, skillIndex: 1, uniEquipId: ISWA } }, { data, kitted: KITTED_CHARS }), - { ok: true, picks: { [SLOT[6]]: { charId: TYPHON, skillIndex: 1, uniEquipId: ISWA } } }); + assert.equal(validateDiyPicks({ [SLOT[6]]: { charId: TYPHON, skillIndex: 1, uniEquipId: ISWA } }, { data, kitted: KITTED_CHARS }).error, 'BAD_TARGET', + 'a 集成战略 module (ISW-A) is not a 自选 choice (shared/diy.js, W5K)'); + assert.deepEqual(validateDiyPicks({ [SLOT[6]]: { charId: TYPHON, skillIndex: 1, uniEquipId: SIEX } }, { data, kitted: KITTED_CHARS }), + { ok: true, picks: { [SLOT[6]]: { charId: TYPHON, skillIndex: 1, uniEquipId: SIEX } } }); }); test('trait 攻城手: the heaviest enemy of her range first; a flyer is a target', () => { diff --git a/test/render/issue25-downback.test.js b/test/render/issue25-downback.test.js index 45e8e325..54630f4e 100644 --- a/test/render/issue25-downback.test.js +++ b/test/render/issue25-downback.test.js @@ -68,7 +68,7 @@ describe('the manifest (data/assets.json)', () => { const backs = Object.entries(M.chars).filter(([, c]) => c.spine?.back); const withFall = backs.filter(([, c]) => dieClipDur(c.spine.back) > 0).map(([id]) => id).sort(); assert.ok(backs.length >= 130, `${backs.length} Back skeletons`); - assert.deepEqual(withFall, ['char_388_mint', 'char_4064_mlynar', 'char_440_pinecn', 'char_602_cdfend']); + assert.deepEqual(withFall, ['char_322_lmlee', 'char_388_mint', 'char_4064_mlynar', 'char_440_pinecn', 'char_602_cdfend'], '老鲤 (a 自选 pick, 0.2.0) too'); for (const [id, c] of Object.entries(M.chars)) assert.ok(dieClipDur(c.spine.front) > 0, `${id} Front has a Die clip`); assert.equal(dieClipDur(back(CAPER)), 0); assert.equal(dieClipDur(front(CAPER)), front(CAPER).animations.Die);