feedback5: merge W5M — the nine 0.1.4 PRs (#150 #66 #146 #74 #72 #103 #149 #109 #160) and W4A (高台 per the trait, 阿戈尔 revive slots, 联防 devour) ported into the split layout
Conflicts: server/match/bot.js (W4A's comment + W5K's player data view pgd), test/golden/diy.json (regenerated). data/ equals an offline build of the merged tools/build-data.mjs. Golden regenerated on the merged code: against feedback5 the moved roster / bonds / fields / matches / standins scenarios are exactly W5M's per-PR attribution (#160 skill events, #109 deploy-timed lifecycle, #66 灵巧 sources and hidden layers, 83fc408 歌蕾蒂娅 placement), plus coop2-NORMAL-70-diy (fields operators those PRs touch) and diy-051 / diy-052 (events.skill only: Misery's deploy passives, #160); against fb5-merge4 only feedback5's own additions (the diy kits, coop2-NORMAL-70-diy).
This commit is contained in:
commit
9ce5435373
74 files changed
+2966
-1338
No files matched your search
@@ -71,6 +71,28 @@ test('迅捷: member skill end +12 SP (p=1 at high L), ≥40 layers every operat
|
||||
assert.equal(off.s_x, 0);
|
||||
});
|
||||
|
||||
test('迅捷 / 突袭: SP gifts after end do not recharge a zero-SP deployment skill or trigger a ready raid', () => {
|
||||
const h = makeBattle({
|
||||
defs: { chess: { t_deploy: chessRec({ id: 't_deploy', bonds: ['swiftShip', 'raidShip'], skill: { spCost: 0 } }) } },
|
||||
units: [{ chessId: 't_deploy', row: 10, col: 4 }],
|
||||
bonds: { swiftShip: bond(1, 1000), raidShip: bond(1) }, captureNoisy: true,
|
||||
kits: { t_deploy: () => ({ skill: {
|
||||
kind: 'duration', activateOnDeploy: true, duration: 2, spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
} }) },
|
||||
});
|
||||
h.b.start();
|
||||
const u = h.unit('t_deploy'), seq = u.deploySeq;
|
||||
h.run(5);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['duration']);
|
||||
assert.ok(h.hooksOf('spGain').some((c) => c.unit === u), 'swift end handler attempted SP gain');
|
||||
assert.equal(u.skill.sp, 0);
|
||||
assert.equal(u.skill.charges, 0);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(u.skill.activations, 1);
|
||||
assert.equal(u.deploySeq, seq, 'skill end alone does not meet raid readiness');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test('迅捷 / 不屈 proc chances: p = min(1, base + per·L) at each layer count', () => {
|
||||
const sw = bondBb('swiftShip'), ind = bondBb('indomShip');
|
||||
close(procChance(sw, 0), 0.20);
|
||||
@@ -83,7 +105,7 @@ test('迅捷 / 不屈 proc chances: p = min(1, base + per·L) at each layer coun
|
||||
close(procChance(ind, 1000), 1, 'capped');
|
||||
});
|
||||
|
||||
test('灵巧: aura on members + 4 neighbours (once), 8 tiles at ≥40 layers, follows death and relocation', () => {
|
||||
test('灵巧: aura on members + 4 neighbours (once), 8 tiles at ≥40 layers, survives death and follows relocation', () => {
|
||||
const defs = { chess: { k_1: op('k_1', ['skillfulShip']), k_2: op('k_2', ['skillfulShip']), k_n: op('k_n', []), k_d: op('k_d', []), k_f: op('k_f', []) } };
|
||||
const h = makeBattle({
|
||||
defs, bonds: { skillfulShip: bond(1, 5) },
|
||||
@@ -107,7 +129,117 @@ test('灵巧: aura on members + 4 neighbours (once), 8 tiles at ≥40 layers, fo
|
||||
assert.equal(a('k_n'), 100, 'relocated out of the aura');
|
||||
h.b.dealDamage(null, h.unit('k_1'), { amount: 1e9, type: 'true' });
|
||||
h.step(2);
|
||||
assert.equal(a('k_d'), 100, 'aura source died');
|
||||
assert.equal(a('k_d'), 150, 'downed aura source still buffs neighbours');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
for (const layers of [5, 40]) {
|
||||
test(`灵巧: downed sources keep one aura at ${layers} layers, update live layers and restore it on redeploy`, () => {
|
||||
const defs = { chess: {
|
||||
k_1: op('k_1', ['skillfulShip']), k_2: op('k_2', ['skillfulShip']),
|
||||
k_n: op('k_n', []), k_d: op('k_d', []),
|
||||
} };
|
||||
const h = makeBattle({
|
||||
defs, bonds: { skillfulShip: bond(1, layers) },
|
||||
units: [
|
||||
{ chessId: 'k_1', row: 10, col: 4 }, { chessId: 'k_2', row: 10, col: 6 },
|
||||
{ chessId: 'k_n', row: 10, col: 5 }, { chessId: 'k_d', row: 11, col: 5 },
|
||||
],
|
||||
});
|
||||
h.step();
|
||||
const first = h.unit('k_1'), second = h.unit('k_2'), neighbour = h.unit('k_n'), diagonal = h.unit('k_d');
|
||||
assert.equal(neighbour.s.aspd, 110 + layers, 'overlapping sources buff once');
|
||||
h.b.kill(first);
|
||||
assert.ok(h.b.isDown(first));
|
||||
assert.equal(neighbour.s.aspd, 110 + layers, 'no attack speed drop on death');
|
||||
h.b.kill(second);
|
||||
assert.ok(h.b.isDown(second));
|
||||
h.run(1);
|
||||
assert.equal(neighbour.s.aspd, 110 + layers, 'all sources down: polling keeps the aura');
|
||||
assert.equal(diagonal.s.aspd, layers >= 40 ? 110 + layers : 100, '4 / 8 tile range still applies');
|
||||
assert.equal(neighbour.buffs.filter((b) => b.key === 'bond:skillfulShip').length, 1);
|
||||
|
||||
gainLayers(h.b, { playerId: 'p1', bonds: 'skillfulShip', n: 40 - layers + 2 });
|
||||
h.step(2);
|
||||
assert.equal(neighbour.s.aspd, 152, 'downed sources use live layer values');
|
||||
assert.equal(diagonal.s.aspd, 152, 'downed sources widen their aura at 40 layers');
|
||||
h.b.kill(neighbour);
|
||||
assert.ok(h.b.redeploy(neighbour, { free: true }));
|
||||
assert.equal(neighbour.s.aspd, 152, 'recipient redeployed next to a downed source');
|
||||
assert.ok(h.b.redeploy(first, { free: true }));
|
||||
assert.equal(first.s.aspd, 152, 'source regains its own bonus on redeploy');
|
||||
assert.equal(neighbour.s.aspd, 152, 'redeploy does not stack the aura');
|
||||
h.b.retreat(first);
|
||||
assert.equal(neighbour.s.aspd, 152, 'remaining downed source still covers the recipient');
|
||||
assert.ok(h.b.redeploy(second, { free: true }));
|
||||
h.b.retreat(second);
|
||||
assert.equal(neighbour.s.aspd, 100, 'withdrawn sources no longer provide an aura');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
}
|
||||
|
||||
test('灵巧: a downed source provides its aura around the body tile when it returns home after death', () => {
|
||||
const defs = { chess: {
|
||||
k_1: op('k_1', ['skillfulShip']), k_2: op('k_2', ['skillfulShip']),
|
||||
k_home: op('k_home', []), k_away: op('k_away', []), k_tile: op('k_tile', []),
|
||||
} };
|
||||
const h = makeBattle({
|
||||
defs, bonds: { skillfulShip: bond(1, 5) },
|
||||
units: [
|
||||
{ chessId: 'k_1', row: 10, col: 4 }, { chessId: 'k_2', row: 12, col: 3 },
|
||||
{ chessId: 'k_home', row: 10, col: 5 }, { chessId: 'k_away', row: 10, col: 8 },
|
||||
{ chessId: 'k_tile', row: 10, col: 7 },
|
||||
],
|
||||
});
|
||||
h.step();
|
||||
h.b.retreat(h.unit('k_tile'), { permanent: true });
|
||||
assert.ok(h.b.relocate(h.unit('k_1'), 10, 7));
|
||||
h.run(0.4);
|
||||
assert.equal(h.unit('k_home').s.aspd, 100);
|
||||
assert.equal(h.unit('k_away').s.aspd, 115);
|
||||
h.b.kill(h.unit('k_1'));
|
||||
assert.deepEqual(h.unit('k_1').body, [10, 4]);
|
||||
assert.equal(h.unit('k_home').s.aspd, 115, 'aura follows the downed body back home');
|
||||
assert.equal(h.unit('k_away').s.aspd, 100, 'last living tile no longer provides an aura');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
for (const reason of ['retreat', 'forcedExit', 'merchant']) {
|
||||
test(`灵巧: ${reason} leaves a down model without preserving the killed-source aura`, () => {
|
||||
const h = makeBattle({
|
||||
defs: { chess: { k_1: op('k_1', ['skillfulShip']), k_n: op('k_n', []) } },
|
||||
bonds: { skillfulShip: bond(1, 5) },
|
||||
units: [{ chessId: 'k_1', row: 10, col: 4 }, { chessId: 'k_n', row: 10, col: 5 }],
|
||||
});
|
||||
h.step();
|
||||
const source = h.unit('k_1'), recipient = h.unit('k_n');
|
||||
assert.equal(recipient.s.aspd, 115);
|
||||
h.b.retreat(source, { reason });
|
||||
assert.ok(h.b.isDown(source), 'a down model is not proof of being killed');
|
||||
assert.equal(source.removeReason, reason);
|
||||
assert.equal(recipient.s.aspd, 100, 'aura stops immediately on retreat');
|
||||
assert.ok(h.b.redeploy(source, { free: true }));
|
||||
assert.equal(recipient.s.aspd, 115, 'the living source restores its aura');
|
||||
h.b.kill(source);
|
||||
assert.equal(recipient.s.aspd, 115, 'a subsequent real kill keeps the aura');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
}
|
||||
|
||||
test('灵巧: unite carry-down forcedExit starts without an aura and restores it on redeploy', () => {
|
||||
const h = makeBattle({
|
||||
kind: 'unite',
|
||||
defs: { chess: { k_1: op('k_1', ['skillfulShip']), k_n: op('k_n', []) } },
|
||||
bonds: { skillfulShip: bond(1, 40) },
|
||||
units: [{ chessId: 'k_1', row: 10, col: 4, carryState: { down: true } }, { chessId: 'k_n', row: 11, col: 5 }],
|
||||
});
|
||||
h.step();
|
||||
const source = h.unit('k_1'), recipient = h.unit('k_n');
|
||||
assert.equal(source.removeReason, 'forcedExit');
|
||||
assert.ok(h.b.isDown(source));
|
||||
assert.equal(recipient.s.aspd, 100, 'forcedExit is not a kill in this battle');
|
||||
assert.ok(h.b.redeploy(source, { free: true }));
|
||||
assert.equal(recipient.s.aspd, 150, 'the redeployed source covers all eight neighbours');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
@@ -414,8 +546,9 @@ test('突袭 #51: the most advanced enemy out of reach → the jump goes to the
|
||||
|
||||
// GitHub #49: skills.js `ready` is false for every passive skill, so a passive 突袭 member (缄默德克萨斯, 宴 …) only jumped on
|
||||
// the 10 s idle trigger; the reporter's footage of the official game shows 缄默德克萨斯 jumping within her passive's 10 s.
|
||||
// raidPoll (only there) counts a passive skill that is on as 技能就绪; the landing rule (#51) still keeps it from hopping.
|
||||
test('突袭 #49: a passive skill that is on counts as 技能就绪 — 缄默德克萨斯 jumps within a few seconds, then stays while her enemy is in range', () => {
|
||||
// raidPoll (only there) counts a passive skill that is on as 技能就绪, and — since #109 made her skills deploy-timed
|
||||
// duration skills — a deploy-timed skill while its window runs; the landing rule (#51) still keeps it from hopping.
|
||||
test('突袭 #49: a passive skill that is on, or a deploy-timed skill while it runs (#109), counts as 技能就绪 — 缄默德克萨斯 jumps within a few seconds, then stays while her enemy is in range', () => {
|
||||
const tex = 'chess_char_4_16_a';
|
||||
const h = makeBattle({
|
||||
defs: { enemies: DUMMY }, bonds: { raidShip: bond(1, 10) }, enemies: [{ key: 'enemy_addon_dummy', pos: [9, 8] }],
|
||||
@@ -423,7 +556,7 @@ test('突袭 #49: a passive skill that is on counts as 技能就绪 — 缄默
|
||||
});
|
||||
h.step();
|
||||
const u = h.unit(tex), e = h.enemy('enemy_addon_dummy');
|
||||
assert.deepEqual([u.skill.kind, u.skill.ready, u.skill.active], ['passive', false, true], 'her passive: on, and the global `ready` unchanged');
|
||||
assert.deepEqual([u.skill.kind, !!u.skill.spec.activateOnDeploy, u.skill.ready, u.skill.active], ['duration', true, false, true], 'her deploy-timed skill (#109): on, and the global `ready` false');
|
||||
assert.ok(!inRange(u, e), 'nothing in her range');
|
||||
assert.ok(h.runUntil(() => raidJumps(h).length > 0, 3), 'jumped within 3 s (no 10 s idle wait)');
|
||||
assert.ok(h.b.time < bondBb('raidShip').no_attack_duration - 5, `at ${h.b.time.toFixed(2)} s`);
|
||||
@@ -433,6 +566,25 @@ test('突袭 #49: a passive skill that is on counts as 技能就绪 — 缄默
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test('突袭 #49 with #109: a deploy-timed skill counts only while its window runs — once it has ended, only the idle trigger is left', () => {
|
||||
const tex = 'chess_char_4_16_a';
|
||||
const idle = bondBb('raidShip').no_attack_duration;
|
||||
const h = makeBattle({
|
||||
defs: { enemies: DUMMY }, bonds: { raidShip: bond(1, 10) }, enemies: [{ key: 'enemy_addon_dummy', pos: [9, 8] }],
|
||||
units: [{ chessId: tex, row: 12, col: 3 }], hooks: ['deploy', 'death'], autoFinish: false, timeLimit: 60,
|
||||
});
|
||||
h.step();
|
||||
const u = h.unit(tex);
|
||||
u.skill.end('duration'); // her window over before the first poll, as after its duration
|
||||
assert.deepEqual([u.skill.active, u.skill.ready], [false, false], 'ended: no charge until the next deployment');
|
||||
h.run(3);
|
||||
assert.equal(raidJumps(h).length, 0, 'no 技能就绪 jump once the window has ended');
|
||||
assert.ok(h.runUntil(() => raidJumps(h).length > 0, idle + 1), 'the idle trigger');
|
||||
assert.ok(h.b.time >= idle - 1e-6, `at ${h.b.time.toFixed(2)} s`);
|
||||
assert.ok(u.skill.active, 'the landing is a new deployment: a new window');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test('突袭 #49: non-passive members unchanged — a charged skill jumps at once, an uncharged one waits for the idle time; a passive one jumps at once', () => {
|
||||
const idle = bondBb('raidShip').no_attack_duration;
|
||||
const defs = {
|
||||
|
||||
+132
-78
@@ -374,38 +374,39 @@ test('阿戈尔 devour order: left first, then top first (row 0 is the bottom: t
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test('阿戈尔 5: the first 3 members by position (left column first, top first) revive at once on their first knock-out, whatever the order of knock-outs; the others never; a second knock-out is final', () => {
|
||||
test('阿戈尔 5: the 3 slots go to the first 3 members knocked out, wherever they stand; a later first knock-out stays down; a second knock-out is final; 3 members: none', () => {
|
||||
const list = [];
|
||||
for (let i = 0; i < 5; i++) list.push([`g${i}_a`, ['egirShip'], { skill: { spCost: 30, initSp: 5, duration: 10 } }]);
|
||||
// one per row pair so nobody devours anybody: col 3 rows 9–12 (g0 bottom … g3 top), g4 at (9,5). PRTS 盟约记录 阿戈尔
|
||||
// "前3名【阿戈尔】干员首次被击倒时立刻复活", the first 3 being the first deployed — "更靠左和靠上": g3, g2, g1
|
||||
// "前3名【阿戈尔】干员首次被击倒时立刻复活"; the owner's decision of 2026-10-05: the first 3 knocked out, in knock-out order
|
||||
const units = list.map(([id], i) => ({ chessId: id, row: 9 + (i % 4), col: 3 + 2 * Math.floor(i / 4) }));
|
||||
const h = makeBattle({ defs: defsOf(list), units, bonds: { egirShip: bondOn(5, 0, null, [3, 5]) }, hooks: ['death', 'deploy'] });
|
||||
h.step(1);
|
||||
const us = list.map(([id]) => h.unit(id));
|
||||
const kill = (u) => h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
|
||||
const redeploys = (u) => h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial).length;
|
||||
// knocked out first, yet no beneficiary: g0 (bottom of the first column) and g4 (second column) stay down
|
||||
for (const u of [us[0], us[4]]) {
|
||||
kill(u);
|
||||
assert.ok(!u.alive, `${u.defId} is not among the first 3 by position: no revive`);
|
||||
assert.equal(redeploys(u), 0);
|
||||
}
|
||||
us[1].skill.gainSp(20, 'test');
|
||||
us[0].skill.gainSp(20, 'test');
|
||||
const tiles = us.map((u) => [u.tileR, u.tileC]);
|
||||
for (const i of [1, 3, 2]) {
|
||||
// g4 (second column) and g0 (bottom of the first column) first — the last two by position — then g2
|
||||
for (const i of [4, 0, 2]) {
|
||||
const u = us[i];
|
||||
const before = redeploys(u);
|
||||
kill(u);
|
||||
assert.ok(u.alive && u.deployed, `member ${i} back on the field`);
|
||||
close(u.hp, u.s.maxHp);
|
||||
assert.deepEqual([u.tileR, u.tileC], tiles[i], 'same tile');
|
||||
assert.ok(h.hooksOf('death').some((c) => c.unit === u && c.reason === 'killed'), 'it was knocked out (被击倒 triggers fire)');
|
||||
assert.equal(redeploys(u), before + 1, `member ${i} redeployed`);
|
||||
assert.equal(redeploys(u), 1, `member ${i} redeployed`);
|
||||
}
|
||||
assert.equal(us[1].skill.sp, 5, 'a redeploy restarts from the initial SP');
|
||||
kill(us[1]);
|
||||
assert.ok(!us[1].alive, 'second knock-out is final');
|
||||
assert.equal(us[0].skill.sp, 5, 'a redeploy restarts from the initial SP');
|
||||
// the 3 are gone: g3 (top-left, first by position) and g1 stay down on their first knock-out
|
||||
for (const i of [3, 1]) {
|
||||
kill(us[i]);
|
||||
assert.ok(!us[i].alive, `member ${i}: no slot left`);
|
||||
assert.equal(redeploys(us[i]), 0);
|
||||
}
|
||||
kill(us[4]);
|
||||
assert.ok(!us[4].alive, 'a second knock-out is final');
|
||||
assert.equal(h.eventsOf('fx').filter((x) => x[1] === 'revive').length, 3, 'three revives');
|
||||
checkInvariants(h.b);
|
||||
// a member moved off its board tile (突袭 / relocation) revives where it was knocked out; later redeploys go home
|
||||
const hm = makeBattle({ defs: defsOf(list), units, bonds: { egirShip: bondOn(5, 0, null, [3, 5]) }, hooks: [] });
|
||||
@@ -424,44 +425,126 @@ test('阿戈尔 5: the first 3 members by position (left column first, top first
|
||||
assert.ok(!h3.unit('g3_a').alive);
|
||||
});
|
||||
|
||||
test('阿戈尔 5 + the devour chain (GitHub #33): the devour knocks out the food, only the members among the first 3 by position come back; the others keep theirs for the fight (normal and 联防)', () => {
|
||||
// one row, all facing right: 浊心斯卡蒂 → 乌尔比安 → 幽灵鲨 → 海霓 → 深巡 → 隐现. PRTS 盟约记录 阿戈尔 备注 "标记按付与顺序触发
|
||||
// 【吞噬】效果,目标首次被击倒后解除自身被付与但还未触发的【吞噬】效果". The 3 revives belong to the first 3 by position
|
||||
// (浊心斯卡蒂, 乌尔比安, 幽灵鲨 — community reports 「阿戈尔盟约复活有bug,只能复活一个干员」); until 0.1.3 the first 3
|
||||
// knocked out took them, so the food (幽灵鲨, 海霓, 深巡) spent all 3 at t = 0 and nobody came back in the fight
|
||||
const ids = ['chess_char_6_04_a', 'chess_char_5_05_a', 'chess_char_2_07_a', 'chess_char_3_09_a', 'chess_char_1_04_a', 'chess_char_1_01_a'];
|
||||
test('阿戈尔 5 + the devour chain (GitHub #33, #105, #140): the eaten members take the 3 slots in knock-out order, uneaten ones keep none — survivors = uneaten + 3 (normal and 联防)', () => {
|
||||
// row 10, all facing right: 浊心斯卡蒂 → 幽灵鲨 → 海霓 → 深巡 → 歌蕾蒂娅 → 隐现; 乌尔比安 on row 12 faces an empty tile.
|
||||
// PRTS 盟约记录 阿戈尔 备注 "标记按付与顺序触发【吞噬】效果,目标首次被击倒后解除自身被付与但还未触发的【吞噬】效果". Until 0.1.3
|
||||
// the slots belonged to the first 3 by position (浊心斯卡蒂, 乌尔比安, 幽灵鲨: two uneaten members held them, so only 幽灵鲨
|
||||
// of the food came back — "没被吃的阿戈尔干员也会占用复活名额", "无论食物链怎么吃最终都只有三名阿戈尔人存活")
|
||||
const ids = ['chess_char_6_04_a', 'chess_char_2_07_a', 'chess_char_3_09_a', 'chess_char_1_04_a', 'chess_char_4_12_a', 'chess_char_1_01_a'];
|
||||
const ULPIA = 'chess_char_5_05_a';
|
||||
for (const kind of ['normal', 'unite']) {
|
||||
const h = makeBattle({
|
||||
kind, units: ids.map((chessId, i) => ({ chessId, row: 10, col: 2 + i })), bonds: { egirShip: bondOn(5, 0, null, [3, 5]) },
|
||||
hooks: ['damaged', 'death', 'deploy'], captureNoisy: true, autoFinish: false, timeLimit: 60,
|
||||
kind, units: [...ids.map((chessId, i) => ({ chessId, row: 10, col: 2 + i })), { chessId: ULPIA, row: 12, col: 3 }],
|
||||
bonds: { egirShip: bondOn(6, 0, null, [3, 5]) }, hooks: ['damaged', 'death', 'deploy'], captureNoisy: true, autoFinish: false, timeLimit: 60,
|
||||
});
|
||||
h.step(1);
|
||||
const [skadi, ulpia, ghost, hn, deep, fodder] = ids.map((id) => h.unit(id));
|
||||
// 浊心斯卡蒂 marks the whole row and resolves first; every later mark is on a unit already knocked out (乌尔比安's), or
|
||||
// comes from one (幽灵鲨's, 海霓's, 深巡's)
|
||||
const [skadi, ghost, hn, deep, glady, fodder] = ids.map((id) => h.unit(id));
|
||||
const ulpia = h.unit(ULPIA);
|
||||
// 浊心斯卡蒂 marks the whole row (through the members) and resolves first; every later mark is on a unit knocked out
|
||||
assert.deepEqual(tagged(h, 'bond:egir:devour').map((c) => [c.source.defId, c.target.defId]),
|
||||
[ulpia, ghost, hn, deep, fodder].map((t) => [skadi.defId, t.defId]), kind);
|
||||
[ghost, hn, deep, glady, fodder].map((t) => [skadi.defId, t.defId]), kind);
|
||||
const kos = (u) => h.hooksOf('death').filter((c) => c.unit === u && c.reason === 'killed').length;
|
||||
const revived = (u) => h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial).length;
|
||||
for (const u of [ghost, hn, deep]) {
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === u && c.reason === 'killed').length, 1, `${kind}: ${u.defId} knocked out once`);
|
||||
}
|
||||
assert.equal(revived(ghost), 1, `${kind}: 幽灵鲨 (third by position) revived at once`);
|
||||
assert.equal(revived(hn) + revived(deep), 0, `${kind}: 海霓 and 深巡 (fourth, fifth) not revived`);
|
||||
for (const u of [ghost, hn, deep, glady]) assert.equal(kos(u), 1, `${kind}: ${u.defId} knocked out once`);
|
||||
for (const u of [ghost, hn, deep]) assert.equal(revived(u), 1, `${kind}: ${u.defId} (knocked out 1st–3rd) revived at once`);
|
||||
assert.equal(revived(glady), 0, `${kind}: 歌蕾蒂娅 (knocked out 4th) stays down`);
|
||||
assert.equal(kos(skadi) + kos(ulpia) + revived(skadi) + revived(ulpia), 0, `${kind}: the uneaten members take no slot`);
|
||||
h.run(3);
|
||||
for (const u of [skadi, ulpia, ghost]) assert.ok(u.alive && u.deployed && u.hp > 0, `${kind}: ${u.defId} standing`);
|
||||
for (const u of [hn, deep, fodder]) assert.ok(!u.alive, `${kind}: ${u.defId} down`);
|
||||
// 浊心斯卡蒂 and 乌尔比安 still hold theirs: their first knock-out in the fight is revived, 幽灵鲨's second is final
|
||||
for (const u of [skadi, ulpia]) {
|
||||
const members = [skadi, ghost, hn, deep, glady, ulpia];
|
||||
assert.deepEqual(members.filter((u) => u.alive).map((u) => u.defId), [skadi, ghost, hn, deep, ulpia].map((u) => u.defId),
|
||||
`${kind}: survivors = the 2 uneaten + 3`);
|
||||
assert.ok(!fodder.alive, `${kind}: the fodder (no member) is down`);
|
||||
// nothing is left for the fight: 浊心斯卡蒂's first knock-out and 幽灵鲨's second stay down
|
||||
for (const u of [skadi, ghost]) {
|
||||
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
|
||||
assert.ok(u.alive && revived(u) === 1, `${kind}: ${u.defId} revived in the fight`);
|
||||
assert.ok(!u.alive, `${kind}: ${u.defId} stays down`);
|
||||
}
|
||||
h.b.dealDamage(null, ghost, { amount: 1e9, type: 'true' });
|
||||
assert.ok(!ghost.alive, `${kind}: 幽灵鲨 has no revive left`);
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test('阿戈尔 5 beneficiaries: fixed at battle start — a 联防 member entering down keeps the slot, a 调和 member is one, the mirrored boss side counts from its own left', () => {
|
||||
test('阿戈尔 5: a member devoured by several 阿戈尔 spends one slot at most — its pending marks are cancelled once it is knocked out', () => {
|
||||
// A (10,3) → B (10,4) → X (10,5) and C (11,5, facing DOWN) → X: three marks on X; D (12,8) faces an empty tile
|
||||
const list = [['A_a', ['egirShip']], ['B_a', ['egirShip']], ['X_a', ['egirShip']], ['C_a', ['egirShip']], ['D_a', ['egirShip']]];
|
||||
const defs = defsOf(list.map(([id, b]) => [id, b, { stats: { maxHp: 3000 } }]));
|
||||
const units = [
|
||||
{ chessId: 'A_a', row: 10, col: 3 }, { chessId: 'B_a', row: 10, col: 4 }, { chessId: 'X_a', row: 10, col: 5 },
|
||||
{ chessId: 'C_a', row: 11, col: 5, dir: 'DOWN' }, { chessId: 'D_a', row: 12, col: 8 },
|
||||
];
|
||||
const h = makeBattle({ defs, units, bonds: { egirShip: bondOn(5, 0, null, [3, 5]) }, hooks: ['damaged', 'death', 'deploy'], captureNoisy: true, autoFinish: false, timeLimit: 60 });
|
||||
h.step(1);
|
||||
const [a, b, x, c, d] = ['A_a', 'B_a', 'X_a', 'C_a', 'D_a'].map((id) => h.unit(id));
|
||||
const kos = (u) => h.hooksOf('death').filter((e) => e.unit === u && e.reason === 'killed').length;
|
||||
const revived = (u) => h.hooksOf('deploy').filter((e) => e.unit === u && !e.initial).length;
|
||||
// marked by A (through B), by B and by C; A's mark knocks it out, B's and C's are cancelled
|
||||
assert.deepEqual(tagged(h, 'bond:egir:devour').map((e) => [e.source.defId, e.target.defId]), [['A_a', 'B_a'], ['A_a', 'X_a']]);
|
||||
for (const u of [b, x]) assert.ok(u.alive && kos(u) === 1 && revived(u) === 1, `${u.defId}: one knock-out, one slot`);
|
||||
close(x.hp, x.s.maxHp, 1e-6, 'X at full HP: no second mark');
|
||||
// two slots used: D's first knock-out takes the third, C's stays down
|
||||
h.b.dealDamage(null, d, { amount: 1e9, type: 'true' });
|
||||
assert.ok(d.alive, 'D: the third slot');
|
||||
h.b.dealDamage(null, c, { amount: 1e9, type: 'true' });
|
||||
assert.ok(!c.alive, 'C: none left');
|
||||
assert.ok(a.alive && kos(a) === 0, 'A never knocked out');
|
||||
checkInvariants(h.b);
|
||||
// X with more HP survives A's and B's marks and falls to C's: still one knock-out, one slot
|
||||
const tough = defsOf(list.map(([id, bd]) => [id, bd, { stats: { maxHp: id === 'X_a' ? 8000 : 3000 } }]));
|
||||
const t = makeBattle({ defs: tough, units, bonds: { egirShip: bondOn(5, 0, null, [3, 5]) }, hooks: ['damaged', 'death', 'deploy'], captureNoisy: true, autoFinish: false, timeLimit: 60 });
|
||||
t.step(1);
|
||||
const tx = t.unit('X_a');
|
||||
assert.deepEqual(tagged(t, 'bond:egir:devour').map((e) => [e.source.defId, e.target.defId]), [['A_a', 'B_a'], ['A_a', 'X_a'], ['B_a', 'X_a'], ['C_a', 'X_a']]);
|
||||
assert.equal(t.hooksOf('death').filter((e) => e.unit === tx && e.reason === 'killed').length, 1, 'X knocked out once, by the third mark');
|
||||
assert.ok(tx.alive && t.eventsOf('fx').filter((e) => e[1] === 'revive').length === 2, 'B and X: two slots');
|
||||
checkInvariants(t.b);
|
||||
});
|
||||
|
||||
test('阿戈尔 5: an operator\'s own save uses no slot (斯卡蒂\'s DRE-Y, M3茧甲, 埃芒加德 [ASSUMED for the band]); the slot waits for its first real knock-out', () => {
|
||||
// 斯卡蒂 (elite, module DRE-Y by default) enters 联防 at half HP in front of 乌尔比安: his mark is lethal, DRE-Y saves her
|
||||
// (PRTS: once per deployment, full HP with max HP −60 %) — no knock-out, so no slot
|
||||
const SKADI = 'chess_char_3_05_b', ULPIA = 'chess_char_5_05_a', GHOST = 'chess_char_2_07_a', HN = 'chess_char_3_09_a', DEEP = 'chess_char_1_04_a';
|
||||
const h = makeBattle({
|
||||
kind: 'unite', autoFinish: false, timeLimit: 60, hooks: ['damaged', 'death', 'deploy'], captureNoisy: true,
|
||||
units: [
|
||||
{ chessId: ULPIA, row: 10, col: 3 }, { chessId: SKADI, row: 10, col: 4, dir: 'UP', carryState: { hpPct: 0.5 } },
|
||||
{ chessId: GHOST, row: 12, col: 3 }, { chessId: HN, row: 12, col: 5 }, { chessId: DEEP, row: 12, col: 7 },
|
||||
],
|
||||
bonds: { egirShip: bondOn(5, 0, null, [3, 5]) },
|
||||
});
|
||||
h.step(1);
|
||||
const sk = h.unit(SKADI);
|
||||
assert.deepEqual(tagged(h, 'bond:egir:devour').map((c) => [c.source.defId, c.target.defId]), [[ULPIA, SKADI]]);
|
||||
assert.ok(sk.alive && buffOf(sk, 'trait:skadi_tide'), 'saved by her module');
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === sk).length, 0, 'not knocked out');
|
||||
assert.equal(h.eventsOf('fx').filter((x) => x[1] === 'revive' && x[4] && x[4].n).length, 0, 'no 阿戈尔 revive');
|
||||
const kill = (id) => { const u = h.unit(id); h.b.dealDamage(null, u, { amount: 1e9, type: 'true' }); return u.alive; };
|
||||
// all 3 slots are still there: 幽灵鲨, 海霓 and then 斯卡蒂 (DRE-Y spent in this deployment) come back, 深巡 does not
|
||||
assert.deepEqual([GHOST, HN, SKADI, DEEP].map(kill), [true, true, true, false]);
|
||||
checkInvariants(h.b);
|
||||
|
||||
const list = [['g0_a', ['egirShip']], ['g1_a', ['egirShip']], ['g2_a', ['egirShip']], ['g3_a', ['egirShip']], ['g4_a', ['egirShip']]];
|
||||
const units = (it = {}) => list.map(([chessId], i) => ({ chessId, row: 9 + (i % 2) * 3, col: 3 + i, items: it[i] }));
|
||||
const killed = (b, id) => { const u = b.unit(id); b.b.dealDamage(null, u, { amount: 1e9, type: 'true' }); return u; };
|
||||
// M3茧甲 (an item 复活, in place): g0 is saved, the 3 slots go to g1, g2, g3; g4 stays down
|
||||
const hi = makeBattle({ defs: defsOf(list), autoFinish: false, timeLimit: 60, hooks: ['death', 'deploy'], units: units({ 0: ['chess_item_4_12_e_a'] }), bonds: { egirShip: bondOn(5, 0, null, [3, 5]) } });
|
||||
hi.step(1);
|
||||
const g0 = killed(hi, 'g0_a');
|
||||
assert.ok(g0.alive && hi.hooksOf('death').filter((c) => c.unit === g0).length === 0, 'item: saved in place (no knock-out)');
|
||||
assert.deepEqual(['g1_a', 'g2_a', 'g3_a', 'g4_a'].map((id) => killed(hi, id).alive), [true, true, true, false], 'item: three slots left after the save');
|
||||
checkInvariants(hi.b);
|
||||
// 埃芒加德 (命结之秘: the battle's first 3 knock-downs revive in place — its own count, unchanged): g0, g1, g2 saved by it;
|
||||
// the 阿戈尔 slots then go to g3, g4 and g0's first real knock-out; g1's real one finds none
|
||||
const hb = makeBattle({ defs: defsOf(list), autoFinish: false, timeLimit: 60, hooks: ['death', 'deploy'], units: units(), bandId: 'band_ermengard', bonds: { egirShip: bondOn(5, 0, null, [3, 5]) } });
|
||||
hb.step(1);
|
||||
for (const id of ['g0_a', 'g1_a', 'g2_a']) {
|
||||
const u = killed(hb, id);
|
||||
assert.ok(u.alive && hb.hooksOf('death').filter((c) => c.unit === u).length === 0, `band: ${id} saved in place`);
|
||||
}
|
||||
assert.deepEqual(hb.eventsOf('fx').filter((x) => x[1] === 'revive' && x[4] && x[4].left != null).map((x) => x[4].left), [2, 1, 0], '埃芒加德 spent its 3');
|
||||
assert.deepEqual(['g3_a', 'g4_a', 'g0_a', 'g1_a'].map((id) => killed(hb, id).alive), [true, true, true, false], 'band: the 阿戈尔 slots are untouched');
|
||||
checkInvariants(hb.b);
|
||||
});
|
||||
|
||||
test('阿戈尔 5 slots in 联防 and on the boss field: a member entering down takes none until it is knocked out after standing up; a 调和 member takes one; a mirrored player counts its own knock-outs', () => {
|
||||
const list = [['g0_a', ['egirShip']], ['g1_a', ['egirShip']], ['g2_a', ['egirShip']], ['g3_a', ['egirShip']], ['g4_a', ['egirShip']], ['mani_a', ['maniShip']]];
|
||||
// five members in five columns (one per column, nobody in front of anybody): g0 col 3 … g4 col 7
|
||||
const units = list.slice(0, 5).map(([chessId], i) => ({ chessId, row: 9 + (i % 2) * 3, col: 3 + i }));
|
||||
@@ -470,8 +553,8 @@ test('阿戈尔 5 beneficiaries: fixed at battle start — a 联防 member enter
|
||||
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
|
||||
return u.alive;
|
||||
};
|
||||
// 联防: g0 entered down (forced out before battleStart). The slots stay the first 3 by position — g0, g1, g2.
|
||||
// The forced exit is not a 击倒, so g0's charge waits until they stand back up. g3 is not promoted into it.
|
||||
// 联防: g0 entered down (forced out before battleStart — not a 击倒, so no slot); g4 and g3 take two slots, g0's first
|
||||
// knock-out after it stands back up takes the third, g1 finds none
|
||||
const hu = makeBattle({
|
||||
kind: 'unite', defs: defsOf(list), autoFinish: false, timeLimit: 60, bonds: { egirShip: bondOn(5, 0, null, [3, 5]) },
|
||||
units: units.map((u, i) => (i === 0 ? { ...u, carryState: { down: true } } : u)),
|
||||
@@ -479,59 +562,30 @@ test('阿戈尔 5 beneficiaries: fixed at battle start — a 联防 member enter
|
||||
hu.step(1);
|
||||
const g0 = hu.unit('g0_a');
|
||||
assert.ok(!g0.alive, 'g0 forced out');
|
||||
assert.deepEqual(['g4_a', 'g3_a', 'g1_a', 'g2_a'].map((id) => revives(hu, id)), [false, false, true, true]);
|
||||
assert.deepEqual(['g4_a', 'g3_a'].map((id) => revives(hu, id)), [true, true]);
|
||||
assert.ok(hu.runUntil(() => g0.alive && g0.deployed, 25), 'g0 stands back up inside the redeploy time');
|
||||
assert.equal(revives(hu, 'g0_a'), true, 'g0 still held the first slot');
|
||||
hu.b.dealDamage(null, g0, { amount: 1e9, type: 'true' });
|
||||
assert.ok(!g0.alive, 'g0 has no second revive');
|
||||
assert.equal(revives(hu, 'g0_a'), true, 'g0: its first knock-out takes the third slot');
|
||||
assert.equal(revives(hu, 'g1_a'), false, 'g1: none left');
|
||||
checkInvariants(hu.b);
|
||||
// 调和 (an active 调和 member enjoys the core bonds) in the first column: one of the 3
|
||||
// 调和 (an active 调和 member enjoys the core bonds): knocked out first, it takes the first slot
|
||||
const hm = makeBattle({
|
||||
defs: defsOf(list), autoFinish: false, timeLimit: 60,
|
||||
units: [{ chessId: 'mani_a', row: 11, col: 2 }, ...units.slice(0, 4)],
|
||||
bonds: { egirShip: bondOn(5, 0, null, [3, 5]), maniShip: { count: 1, active: true, tier: 1, layers: 0 } },
|
||||
});
|
||||
hm.step(1);
|
||||
assert.deepEqual(['g2_a', 'mani_a', 'g0_a', 'g1_a'].map((id) => revives(hm, id)), [false, true, true, true]);
|
||||
// the right-hand boss player (mirrored): its board's left is the field's right — board cols 3, 4, 5 = field cols 17,
|
||||
// 16, 15 hold the revives (read in field columns from the left they would go to g4, g3, g2)
|
||||
assert.deepEqual(['mani_a', 'g3_a', 'g2_a', 'g0_a'].map((id) => revives(hm, id)), [true, true, true, false]);
|
||||
// the right-hand boss player (mirrored): the order of its own knock-outs, wherever they stand
|
||||
const hb = makeBattle({
|
||||
kind: 'boss', defs: defsOf(list), autoFinish: false, timeLimit: 60,
|
||||
players: [{ playerId: 'R1', side: 'R', colOffset: 8, units: units.map((u, i) => ({ ...u, uid: i + 1 })), bonds: { egirShip: bondOn(5, 0, null, [3, 5]) } }],
|
||||
});
|
||||
hb.step(1);
|
||||
assert.deepEqual(['g4_a', 'g3_a', 'g0_a', 'g1_a', 'g2_a'].map((id) => [hb.unit(id).tileC, revives(hb, id)]),
|
||||
[[13, false], [14, false], [17, true], [16, true], [15, true]]);
|
||||
[[13, true], [14, true], [17, true], [16, false], [15, false]]);
|
||||
checkInvariants(hb.b);
|
||||
});
|
||||
|
||||
test('阿戈尔 5: a beneficiary 埃芒加德 or M3茧甲 save in place has used its 阿戈尔 revive [ASSUMED]; the others keep theirs', () => {
|
||||
const list = [['g0_a', ['egirShip']], ['g1_a', ['egirShip']], ['g2_a', ['egirShip']], ['g3_a', ['egirShip']], ['g4_a', ['egirShip']]];
|
||||
const units = (it = {}) => list.map(([chessId], i) => ({ chessId, row: 9 + (i % 2) * 3, col: 3 + i, items: it[i] }));
|
||||
const kill = (h, id) => { const u = h.unit(id); h.b.dealDamage(null, u, { amount: 1e9, type: 'true' }); return u; };
|
||||
for (const how of ['band', 'item']) {
|
||||
const h = makeBattle({
|
||||
defs: defsOf(list), autoFinish: false, timeLimit: 60, hooks: ['death', 'deploy'],
|
||||
units: units(how === 'item' ? { 0: ['chess_item_4_12_e_a'] } : {}), ...(how === 'band' ? { bandId: 'band_ermengard' } : {}),
|
||||
bonds: { egirShip: bondOn(5, 0, null, [3, 5]) },
|
||||
});
|
||||
h.step(1);
|
||||
// g0, g1, g2 (columns 3–5) hold the 阿戈尔 revives; g0 is saved in place first
|
||||
const g0 = kill(h, 'g0_a');
|
||||
assert.ok(g0.alive && h.hooksOf('death').filter((c) => c.unit === g0).length === 0, `${how}: saved in place (no knock-out)`);
|
||||
// 埃芒加德 saves the battle's first 3 knock-downs: its other 2 go to g3 and g4 (no 阿戈尔 beneficiaries)
|
||||
if (how === 'band') for (const id of ['g3_a', 'g4_a']) assert.ok(kill(h, id).alive, 'band revive');
|
||||
kill(h, 'g0_a');
|
||||
assert.ok(!g0.alive, `${how}: its 阿戈尔 revive went with the save — its first knock-out is final`);
|
||||
assert.equal(h.hooksOf('deploy').filter((c) => c.unit === g0 && !c.initial).length, 0);
|
||||
for (const id of ['g1_a', 'g2_a']) {
|
||||
const u = kill(h, id);
|
||||
assert.ok(u.alive && h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial).length === 1, `${how}: ${id} revived by 阿戈尔`);
|
||||
}
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test('阿戈尔 devour, a unit back during the pass: a marker revived by the 5-tier revive still gives its marks; a target revived in place (埃芒加德) or redeployed (不屈) takes none', () => {
|
||||
// g1 (10,3) → g2 (10,4, 3000 × 1.35 HP: knocked out by g1's mark, revived) → g3 (10,5) → fodder (10,6); g4 / g5 face empty tiles
|
||||
const list = [
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// test/content/feedback4-egir-unite.test.js — 阿戈尔's battle-start devour in 联防 (GitHub #105, #140 comment 4; the
|
||||
// owner's decision of 2026-10-05). PRTS 盟约记录 阿戈尔: "战斗开始时【阿戈尔】干员依次吞噬身前一格干员" — no own-side limit:
|
||||
// the operator on the front tile is devoured whoever owns it, a teammate's living operator or a teammate's operator
|
||||
// that entered 联防 knocked out (carry.down, forced out right after the deployment), and the marker gains its base ATK
|
||||
// and block count exactly as in a solo battle. 联防 still adds no layers ("该阶段不能叠加层数"). The remake's two 联防
|
||||
// helpers stand on board cols 3–9 and 11–17 (colOffset 8) with the road column 10 between them, so these fields put the
|
||||
// teammate's operator on col 10 (a LOW tile the sim deploys on) to be in front. The last test runs the own-board case
|
||||
// through unite.js's carry: a chain whose food is down since the own combat gives 联防 the gains of the own combat.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeBattle, checkInvariants } from '../helpers/battleHarness.js';
|
||||
import { uniteBattleOpts } from '../../server/match/unite.js';
|
||||
import { makeMatch } from '../match/harness.js';
|
||||
import { FORCED_EXIT } from '../../server/sim/constants.js';
|
||||
|
||||
const close = (a, b, eps = 1e-6, msg = '') => assert.ok(Math.abs(a - b) <= eps, `${msg} expected ${b}, got ${a}`);
|
||||
const bondOn = (count, layers = 0) => ({ count, active: count >= 3, tier: count >= 5 ? 2 : count >= 3 ? 1 : 0, layers });
|
||||
const buffOf = (u, key) => u.buffs.find((b) => b.key === key) ?? null;
|
||||
const devours = (h) => h.hooksOf('damaged').filter((c) => c.dmg && c.dmg.tags && c.dmg.tags.includes('bond:egir:devour'));
|
||||
|
||||
const ULPIA = 'chess_char_5_05_a', GHOST = 'chess_char_2_07_a', GLADY = 'chess_char_4_12_a', DEEP = 'chess_char_1_04_a';
|
||||
const HN = 'chess_char_3_09_a', MIZUKI = 'chess_char_4_09_a', FODDER = 'chess_char_1_01_a';
|
||||
|
||||
/** p1's 3 阿戈尔: 乌尔比安 on (10,9) facing right — its front tile is field (10,10) — and two facing empty tiles. */
|
||||
const p1Units = () => [
|
||||
{ uid: 1, kind: 'chess', chessId: ULPIA, row: 10, col: 9, dir: 'RIGHT' },
|
||||
{ uid: 2, kind: 'chess', chessId: GLADY, row: 12, col: 3, dir: 'RIGHT' },
|
||||
{ uid: 3, kind: 'chess', chessId: DEEP, row: 12, col: 5, dir: 'RIGHT' },
|
||||
];
|
||||
const player = (playerId, seat, colOffset, units, bonds) => ({ playerId, seat, side: 'L', colOffset, units, bonds, bandId: null, playerEffects: [] });
|
||||
|
||||
/**
|
||||
* A battle where `front` (chess ids, left to right) stands on field (10,10), (10,11) …: owned by p1 itself (`owner`
|
||||
* 'own', board cols 10, 11 …) or by the teammate p2 (`owner` 'mate', colOffset 8: board cols 2, 3 …).
|
||||
*/
|
||||
function field({ kind = 'unite', owner = 'own', front = [FODDER], extra = {}, p2Bonds = {}, p2Units = [] } = {}) {
|
||||
const lane = front.map((chessId, i) => ({ uid: 100 + i, kind: 'chess', chessId, row: 10, dir: 'RIGHT', ...(extra[i] || {}) }));
|
||||
const players = owner === 'own'
|
||||
? [player('p1', 0, 0, [...p1Units(), ...lane.map((u, i) => ({ ...u, col: 10 + i }))], { egirShip: bondOn(3, 10) })]
|
||||
: [player('p1', 0, 0, p1Units(), { egirShip: bondOn(3, 10) }),
|
||||
player('p2', 1, 8, [...lane.map((u, i) => ({ ...u, col: 2 + i })), ...p2Units], p2Bonds)];
|
||||
const h = makeBattle({ kind, players, autoFinish: false, timeLimit: 60, hooks: ['damaged', 'death', 'deploy', 'kill', 'layerGain'], captureNoisy: true });
|
||||
h.step();
|
||||
return h;
|
||||
}
|
||||
|
||||
test('联防: an 阿戈尔 devours a teammate\'s living operator in front — the same base ATK and block count as an own one, the 物理流失 lands, no layers', () => {
|
||||
const solo = field({ kind: 'normal' });
|
||||
const own = field();
|
||||
const mate = field({ owner: 'mate' });
|
||||
const ref = buffOf(solo.unit(1), 'bond:egir:devour');
|
||||
const fodderBase = solo.unit(100).base;
|
||||
assert.ok(ref, 'solo: 乌尔比安 devours the operator in front');
|
||||
close(ref.mods.atkFlat, fodderBase.atk, 1e-9, 'its base ATK');
|
||||
assert.equal(ref.mods.blockCnt, fodderBase.blockCnt, 'its block count');
|
||||
for (const [tag, h] of [['own', own], ['teammate\'s', mate]]) {
|
||||
const ulpia = h.unit(1), t = h.unit(100);
|
||||
assert.deepEqual(buffOf(ulpia, 'bond:egir:devour')?.mods, ref.mods, `${tag}: the solo gains`);
|
||||
close(ulpia.s.atk, own.unit(1).s.atk, 1e-6, `${tag}: 乌尔比安's ATK`);
|
||||
assert.equal(ulpia.s.blockCnt, solo.unit(1).s.blockCnt, `${tag}: the block count`);
|
||||
const d = devours(h);
|
||||
assert.deepEqual(d.map((c) => [c.source.uid, c.target.uid]), [[1, 100]], `${tag}: one mark`);
|
||||
close(d[0].amount, devours(solo)[0].amount, 1e-6, `${tag}: the same 物理流失`);
|
||||
assert.ok(!t.alive && h.hooksOf('kill').some((c) => c.victim === t && c.killer === ulpia), `${tag}: knocked out, credited to 乌尔比安`);
|
||||
assert.equal(h.b.getPlayer('p1').bonds.egirShip.layers, 10, `${tag}: 联防 adds no layers`);
|
||||
assert.equal(h.hooksOf('layerGain').length, 0, `${tag}: no layer gain at all`);
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
assert.equal(solo.b.getPlayer('p1').bonds.egirShip.layers, 10 + 1, 'solo: the devoured tier as layers');
|
||||
assert.equal(mate.unit(100).ownerId, 'p2');
|
||||
});
|
||||
|
||||
test('联防: a teammate\'s operator that entered 联防 down is devoured as if it stood — the same gains, nothing lands on it, it stays forced out', () => {
|
||||
const up = field({ owner: 'mate' });
|
||||
const down = field({ owner: 'mate', extra: { 0: { carryState: { down: true } } } });
|
||||
const ownDown = field({ extra: { 0: { carryState: { down: true } } } });
|
||||
const t = down.unit(100);
|
||||
assert.ok(!t.alive && t.removeReason === FORCED_EXIT && down.b.isDown(t), 'the teammate\'s operator lies on (10,10), forced out');
|
||||
for (const [tag, h] of [['teammate\'s', down], ['own', ownDown]]) {
|
||||
assert.deepEqual(buffOf(h.unit(1), 'bond:egir:devour')?.mods, buffOf(up.unit(1), 'bond:egir:devour').mods, `${tag} down: the gains of the standing one`);
|
||||
assert.equal(devours(h).length, 0, `${tag} down: no 物理流失 on it`);
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === h.unit(100) && c.reason === 'killed').length, 0, `${tag} down: no knock-out`);
|
||||
assert.equal(h.b.getPlayer('p1').bonds.egirShip.layers, 10, `${tag} down: no layers`);
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test('联防: the chain goes on through a teammate\'s 阿戈尔 — the gains equal the same chain of own operators; its knock-out spends its owner\'s 5-tier slot', () => {
|
||||
const own = field({ front: [GHOST, FODDER] });
|
||||
// p2 holds 5 阿戈尔 (5-tier active): 幽灵鲨 in front of 乌尔比安, four more on its board facing empty tiles
|
||||
const p2Units = [HN, MIZUKI, GLADY, DEEP].map((chessId, i) => ({ uid: 200 + i, kind: 'chess', chessId, row: 12, col: 3 + 2 * i, dir: 'RIGHT' }));
|
||||
const mate = field({ owner: 'mate', front: [GHOST, FODDER], p2Units, p2Bonds: { egirShip: bondOn(5) } });
|
||||
const ref = buffOf(own.unit(1), 'bond:egir:devour');
|
||||
close(ref.mods.atkFlat, own.unit(100).base.atk + own.unit(101).base.atk, 1e-9, 'own chain: 幽灵鲨 and, through her, the fodder');
|
||||
assert.deepEqual(buffOf(mate.unit(1), 'bond:egir:devour')?.mods, ref.mods, 'the teammate\'s chain gives the same');
|
||||
assert.deepEqual(devours(mate).map((c) => [c.source.uid, c.target.uid]), devours(own).map((c) => [c.source.uid, c.target.uid]));
|
||||
const ghost = mate.unit(100);
|
||||
assert.equal(mate.hooksOf('death').filter((c) => c.unit === ghost && c.reason === 'killed').length, 1, '幽灵鲨 knocked out by p1\'s mark');
|
||||
assert.ok(ghost.alive && mate.hooksOf('deploy').some((c) => c.unit === ghost && !c.initial), 'p2\'s 5-tier revives her at once');
|
||||
assert.ok(!own.unit(100).alive, 'p1 has no 5-tier: its own 幽灵鲨 stays down');
|
||||
assert.equal(mate.b.getPlayer('p1').bonds.egirShip.layers, 10, 'no layers');
|
||||
checkInvariants(mate.b);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// the own board through unite.js: a chain whose food is down since the own combat
|
||||
|
||||
let gdCache = null;
|
||||
const gd = () => (gdCache ??= makeMatch({ mode: 'coop', humans: 3, seed: 1, fake: true }).start().m.gd);
|
||||
|
||||
test('联防 through unite.js: food knocked out by the own combat\'s devour enters down, and 乌尔比安 gets exactly the own combat\'s base ATK and block count', () => {
|
||||
// 乌尔比安 → 幽灵鲨 → 歌蕾蒂娅 → fodder on row 10 (3 阿戈尔: no revive), 30 s own combat
|
||||
const ids = [ULPIA, GHOST, GLADY, FODDER];
|
||||
const units = ids.map((chessId, i) => ({ uid: i + 1, kind: 'chess', chessId, row: 10, col: 3 + i, dir: 'RIGHT', items: [] }));
|
||||
const bonds = { egirShip: bondOn(3, 10) };
|
||||
const own = makeBattle({ players: [player('p1', 0, 0, units, bonds)], autoFinish: false, timeLimit: 30 });
|
||||
own.runToEnd(60);
|
||||
const gain = buffOf(own.unit(1), 'bond:egir:devour').mods;
|
||||
const unitsEnd = own.result().perPlayer.p1.unitsEnd;
|
||||
assert.deepEqual(unitsEnd.filter((u) => !u.alive).map((u) => u.uid).sort(), [2, 3, 4], 'the food is down at the end');
|
||||
// the helper's 联防 input as unite.js builds it from that result
|
||||
const ps = {
|
||||
playerId: 'p1', seat: 0,
|
||||
board: new Map(units.map((u) => [`${u.row},${u.col}`, { uid: u.uid, kind: 'chess' }])),
|
||||
battleInput: ({ side, colOffset, carry }) => ({
|
||||
...player('p1', 0, colOffset, units.map((u) => ({ ...u, ...(carry.has(u.uid) ? { carryState: carry.get(u.uid) } : {}) })), bonds), side,
|
||||
}),
|
||||
};
|
||||
const m = { gd: gd(), round: 3, lastResults: new Map([['p1', { unitsEnd }]]), dispatch() {} };
|
||||
const { players } = uniteBattleOpts(m, { helpers: [ps], leaked: [] }, 60);
|
||||
assert.deepEqual(players[0].units.filter((u) => u.carryState?.down).map((u) => u.uid), [2, 3, 4]);
|
||||
const u = makeBattle({ kind: 'unite', players, autoFinish: false, timeLimit: 60 });
|
||||
u.step();
|
||||
const ulpia = u.unit(1);
|
||||
assert.deepEqual(buffOf(ulpia, 'bond:egir:devour')?.mods, gain, 'the own combat\'s gains');
|
||||
close(gain.atkFlat, [2, 3, 4].reduce((s, uid) => s + u.unit(uid).base.atk, 0), 1e-9, '幽灵鲨 + 歌蕾蒂娅 + fodder base ATK');
|
||||
assert.equal(gain.blockCnt, [2, 3, 4].reduce((s, uid) => s + u.unit(uid).base.blockCnt, 0));
|
||||
for (const uid of [2, 3, 4]) assert.ok(!u.unit(uid).alive && u.unit(uid).removeReason === FORCED_EXIT, `${uid} forced out`);
|
||||
assert.equal(u.b.getPlayer('p1').bonds.egirShip.layers, 10, 'no layers in 联防');
|
||||
checkInvariants(u.b);
|
||||
});
|
||||
@@ -165,6 +165,24 @@ test('炎国短刀: each skill activation ATK +5 % / +8 % (one multiplier), at m
|
||||
}
|
||||
});
|
||||
|
||||
test('炎国短刀: timed deployment skill adds one stack per deployment, including carry.skillActive', () => {
|
||||
const id = 'chess_char_1_19_a';
|
||||
for (const [item, per] of [[A('3_04'), 0.05], [B('3_04'), 0.08]]) {
|
||||
const h = makeBattle({ units: [{ chessId: id, row: 10, col: 4, skillIndex: 0, items: [item], carryState: { skillActive: true } }] });
|
||||
h.b.start();
|
||||
const u = h.unit(id);
|
||||
assert.equal(u.skill.activations, 1);
|
||||
close(u.s.atk, u.base.atk * (1 + per), 'one deployment stack');
|
||||
h.run(u.skill.duration + 1);
|
||||
close(u.s.atk, u.base.atk * (1 + per), 'no extra stack on end');
|
||||
h.b.retreat(u);
|
||||
assert.ok(h.b.redeploy(u, { free: true }));
|
||||
assert.equal(u.skill.activations, 2);
|
||||
close(u.s.atk, u.base.atk * (1 + per * 2), 'one more on redeployment');
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test('迅捷作战粮: on deploy SP +3 / +6, plus as much per other operator sharing a bond', () => {
|
||||
for (const [id, each] of [[A('3_05'), 3], [B('3_05'), 6]]) {
|
||||
const ops = { t_op: op('t_op', { bonds: ['swiftShip'] }), t_a: op('t_a', { bonds: ['swiftShip'] }), t_b: op('t_b', { bonds: ['yanShip'] }) };
|
||||
|
||||
@@ -444,6 +444,8 @@ test('1_18 宴 S1 分神: no attacks, block 0 (releases), DEF +def, regenerates
|
||||
assert.equal(u.s.blockCnt, 0);
|
||||
assert.equal(u.blocking.length, 0);
|
||||
assert.equal(e.blockedBy, null, 'blocked enemy released');
|
||||
h.b.dealDamage(u, e, { amount: 100, type: 'phys', isAttack: true });
|
||||
assert.equal(dealt(h, u).at(-1).type, 'phys', 'active S1 does not inherit S2 arts conversion');
|
||||
approx(u.s.def, u.base.def * (1 + bb.def), `${id} DEF`);
|
||||
approx(u.s.hpRegen, u.s.maxHp * bb.hp_recovery_per_sec_by_max_hp_ratio, 'regen');
|
||||
u.hp = u.s.maxHp * 0.3;
|
||||
@@ -462,23 +464,71 @@ test('1_18 宴 S1 分神: no attacks, block 0 (releases), DEF +def, regenerates
|
||||
}
|
||||
});
|
||||
|
||||
test('1_19 野鬃 S1 骑枪刺击 (PASSIVE): ASPD +attack_speed for `duration` s after every deployment', () => {
|
||||
test('1_18 宴 S2: deployment loses current HP once; ATK and normal-attack arts conversion end with the duration', () => {
|
||||
for (const id of pair('18')) {
|
||||
const s = rec(id, 'skchr_utage_2'), bb = s.bb;
|
||||
const h = run({ defs: { chess: noGarrison(id), enemies: { e: dummy('e') } },
|
||||
units: [entry(id, s.skillId, { row: 10, col: 4, carryState: { hpPct: 0.5, skillActive: true } })] });
|
||||
h.b.start();
|
||||
const u = h.unit(id);
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.activations, 1);
|
||||
assert.equal(u.skill.charges, 0);
|
||||
approx(u.hp, u.s.maxHp * 0.5 * (1 - bb.hp_ratio), 'one loss from carried current HP');
|
||||
approx(u.s.atk, u.base.atk * (1 + bb.atk));
|
||||
const e = h.spawn('e', { pos: [10, 8] });
|
||||
const hit = () => { h.b.dealDamage(u, e, { amount: 100, type: 'phys', isAttack: true }); return dealt(h, u).at(-1); };
|
||||
assert.equal(hit().type, 'arts');
|
||||
assert.ok(!hit().dmg.isSkill, 'normal attack marker retained');
|
||||
h.run(bb.duration / 2);
|
||||
approx(u.skill.timeLeft, bb.duration / 2);
|
||||
h.run(bb.duration / 2 + 0.1);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
approx(u.s.atk, u.base.atk);
|
||||
assert.equal(hit().type, 'phys');
|
||||
assert.deepEqual(ended(h, u).map((c) => c.reason), ['duration']);
|
||||
const hp = u.hp;
|
||||
h.run(3);
|
||||
approx(u.hp, hp, 'no second loss while staying deployed');
|
||||
h.b.retreat(u);
|
||||
assert.ok(h.b.redeploy(u, { free: true }));
|
||||
assert.equal(u.skill.activations, 2);
|
||||
approx(u.hp, u.s.maxHp * (1 - bb.hp_ratio), 'new deployment loses current HP once');
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
test('1_19 野鬃 S1 骑枪刺击: ASPD +attack_speed for `duration` s after every deployment', () => {
|
||||
for (const id of pair('19')) {
|
||||
const s = rec(id, 'skchr_wildmn_1');
|
||||
const h = run({ defs: { chess: noGarrison(id) }, units: [entry(id, s.skillId, { row: 9, col: 5 })] });
|
||||
const u = sel(h, id, s.skillId);
|
||||
assert.equal(u.skill.kind, 'passive');
|
||||
h.step();
|
||||
h.b.start();
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.active, true);
|
||||
assert.equal(u.skill.activations, 1);
|
||||
assert.equal(u.skill.charges, 0);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(started(h, u)[0].reason, 'deploy');
|
||||
approx(u.s.aspd, u.base.aspd + s.bb.attack_speed, `${id} ASPD after deployment`);
|
||||
h.run(s.duration - 0.5);
|
||||
h.run(s.duration / 2);
|
||||
approx(u.skill.timeLeft, s.duration / 2);
|
||||
assert.deepEqual(h.snapshot().units.find((t) => t[0] === u.id).slice(5, 7), [s.duration / 2, s.duration]);
|
||||
h.run(s.duration / 2 - 0.5);
|
||||
approx(u.s.aspd, u.base.aspd + s.bb.attack_speed, 'still up');
|
||||
h.run(1);
|
||||
approx(u.s.aspd, u.base.aspd, `expired after ${s.duration} s`);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.deepEqual(ended(h, u).map((c) => c.reason), ['duration']);
|
||||
h.b.kill(u, null);
|
||||
h.step();
|
||||
assert.ok(h.b.redeploy(u, { free: true }));
|
||||
h.step();
|
||||
approx(u.s.aspd, u.base.aspd + s.bb.attack_speed, 'again after the redeployment');
|
||||
assert.equal(u.skill.activations, 2);
|
||||
assert.equal(ended(h, u).length, 1, 'expired skill does not end again on death');
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -342,12 +342,20 @@ test('2_12 砾 S1 影袭: at deployment DEF +def decaying to 0 over `duration` s
|
||||
h.run(0.5);
|
||||
approx(u.s.def, u.base.def * (1 + aura + bb.def), `${id} full bonus`);
|
||||
assert.equal(u.s.shield, 0, 'no 鼠群 barrier');
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.active, true);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], bb.duration);
|
||||
h.run(1);
|
||||
approx(u.s.def, u.base.def * (1 + aura + bb.def * (bb.duration - 1) / bb.duration), 'one step down after 1 s');
|
||||
h.run(0.4);
|
||||
approx(u.s.def, u.base.def * (1 + aura + bb.def * (bb.duration - 1) / bb.duration), 'steps once per second');
|
||||
h.run(bb.duration);
|
||||
approx(u.s.def, u.base.def * (1 + aura), 'gone');
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(u.findBuff('gravel:shadow'), null);
|
||||
assert.equal(h.hooksOf('skillEnd').filter((c) => c.unit === u && c.reason === 'duration').length, 1);
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -369,19 +369,34 @@ test('3_05 斯卡蒂 S1 迅捷打击·γ型 ATK/ASPD; S2 跃浪击 ATK + for `du
|
||||
const g = makeBattle({ timeLimit: 60, units: [U(id, 'skchr_skadi_2', 10, 4)] });
|
||||
const v = g.unit(id);
|
||||
g.step();
|
||||
assert.equal(v.skill.kind, 'passive');
|
||||
assert.equal(v.skill.kind, 'duration');
|
||||
assert.equal(v.skill.active, true);
|
||||
assert.equal(v.skill.charges, 0);
|
||||
assert.equal(v.skill.ready, false);
|
||||
assert.equal(g.hooksOf('skillStart')[0].reason, 'deploy');
|
||||
approx(v.s.atk, v.base.atk * (1 + b2.atk + own));
|
||||
if (id.endsWith('_b')) {
|
||||
const left = v.skill.timeLeft;
|
||||
g.b.dealDamage(null, v, { type: 'true', amount: 1e7 });
|
||||
assert.equal(v.alive, true, 'DRE-Y revives in place during the window');
|
||||
assert.equal(v.skill.activations, 1);
|
||||
approx(v.skill.timeLeft, left, 1e-9, 'revival preserves the remaining skill time');
|
||||
}
|
||||
g.run(b2.duration - 0.5);
|
||||
approx(v.s.atk, v.base.atk * (1 + b2.atk + own));
|
||||
g.run(1);
|
||||
approx(v.s.atk, v.base.atk * (1 + own), 1e-6, 'gone after its duration');
|
||||
assert.equal(v.skill.active, false);
|
||||
assert.equal(v.skill.ready, false);
|
||||
assert.deepEqual(g.hooksOf('skillEnd').map((c) => c.reason), ['duration']);
|
||||
// redeployed ⇒ again
|
||||
g.b.dealDamage(null, v, { type: 'true', amount: 1e7 });
|
||||
if (!v.alive) {
|
||||
g.b.redeploy(v, { free: true });
|
||||
g.step();
|
||||
approx(v.s.atk, v.base.atk * (1 + b2.atk + own), 1e-6, 'every deployment');
|
||||
}
|
||||
assert.equal(v.skill.activations, 1, 'DRE-Y in-place revival does not deploy or restart S2');
|
||||
if (v.alive) g.b.retreat(v);
|
||||
assert.ok(g.b.redeploy(v, { free: true }));
|
||||
approx(v.skill.timeLeft, b2.duration);
|
||||
assert.equal(v.skill.activations, 2);
|
||||
approx(v.s.atk, v.base.atk * (1 + b2.atk + own), 1e-6, 'every deployment');
|
||||
done(g);
|
||||
}
|
||||
// DRE-X: ×atk_scale on blocked enemies; no DRE-Y revive
|
||||
|
||||
@@ -265,6 +265,13 @@ test('伊内丝 S3 独影归途: first deployment leaves a sentry and retreats;
|
||||
const sk = D(id, 2).skill, bb = sk.bb;
|
||||
const h = battle([U(id, 10, 5, 2)], { flags: { dpInit: 99, dpPerSec: 0 } });
|
||||
const u = h.unit(id);
|
||||
h.b.start();
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.active, false, 'first deployment does not activate the skill');
|
||||
assert.equal(u.skill.activations, 0);
|
||||
assert.equal(h.hooksOf('skillStart').filter((c) => c.unit === u).length, 0, 'placing a sentry emits no skillStart');
|
||||
assert.equal(u.skill.timeLeft, 0, 'first deployment places the sentry without a full effect window');
|
||||
assert.equal(u.findBuff('ines:s3'), null);
|
||||
const near = [];
|
||||
for (let i = 0; i < 3; i++) near.push(h.spawn('enemy_dummy', { pos: [10, 6] }), h.spawn('enemy_dummy', { pos: [10, 4] }));
|
||||
const far = h.spawn('enemy_dummy', { pos: [10, 8] });
|
||||
@@ -273,9 +280,14 @@ test('伊内丝 S3 独影归途: first deployment leaves a sentry and retreats;
|
||||
assert.ok(left && left.reason === 'retreat', `${id}: leaves right after the first deployment`);
|
||||
assert.ok(h.runUntil(() => u.alive && u.deployed, 2), 'redeploy timer refreshed: back at once');
|
||||
const buff = u.findBuff('ines:s3');
|
||||
assert.ok(buff, 'passive buff on the redeploy');
|
||||
assert.ok(buff, 'ATK effect on the redeploy');
|
||||
approx(buff.mods.atkPct, bb.atk, 1e-9);
|
||||
approx(buff.timeLeft, sk.duration, 0.1);
|
||||
approx(u.skill.timeLeft, sk.duration, 0.1);
|
||||
assert.equal(u.skill.active, true);
|
||||
assert.equal(u.skill.activations, 1);
|
||||
assert.equal(h.hooksOf('skillStart').filter((c) => c.unit === u).length, 1, 'the redeployment starts the skill once');
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], sk.duration);
|
||||
const recall = dmgBy(h, u, tagged('sentryRecall'));
|
||||
assert.equal(recall.length, bb.max_target, `${id}: recall hits ≤ ${bb.max_target} of 6`);
|
||||
assert.ok(recall.every((c) => near.includes(c.target)) && !recall.some((c) => c.target === far));
|
||||
@@ -287,6 +299,15 @@ test('伊内丝 S3 独影归途: first deployment leaves a sentry and retreats;
|
||||
assert.ok(hits > 0);
|
||||
assert.equal(h.b.getPlayer('p1').dp, dpAt + hits * bb.cost, 'one DP per damage while the passive lasts');
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === u).length, 1, 'only the first deployment leaves');
|
||||
h.run(sk.duration);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(u.findBuff('ines:s3'), null);
|
||||
const dpEnd = h.b.getPlayer('p1').dp;
|
||||
h.run(2);
|
||||
assert.equal(h.b.getPlayer('p1').dp, dpEnd, 'no skill DP after the actual window ends');
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === u).length, 1, 'stays deployed after the window');
|
||||
assert.deepEqual(h.hooksOf('skillEnd').filter((c) => c.unit === u).map((c) => c.reason), ['duration']);
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
@@ -611,10 +632,11 @@ test('缄默德克萨斯 S1 细雨无声: passive ATK up for its duration; hits
|
||||
const h = battle([U(id, 10, 4, 0)]);
|
||||
h.step();
|
||||
const u = h.unit(id);
|
||||
const buff = u.findBuff('texas2:drizzle');
|
||||
assert.ok(buff, `${id}: passive active on deployment`);
|
||||
approx(buff.mods.atkPct, bb.atk, 1e-9);
|
||||
assert.ok(u.findBuff('texas2:rainAtk'), 'talent 德克萨斯传统 ATK during the passive');
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.active, true);
|
||||
assert.equal(u.skill.charges, 0);
|
||||
approx(u.skill.spec.mods.atkPct, bb.atk + D(id, 0).talents[0].bb.atk, 1e-9, 'skill and 德克萨斯传统 ATK');
|
||||
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], sk.duration);
|
||||
const e = h.spawn('enemy_dummy', { pos: [10, 5] });
|
||||
h.run(3);
|
||||
assert.ok(e.s.flags.silence, 'hit target silenced (失去特殊能力)');
|
||||
@@ -623,7 +645,10 @@ test('缄默德克萨斯 S1 细雨无声: passive ATK up for its duration; hits
|
||||
assert.ok(dot.length >= 2, `a DoT tick every second although she re-hits faster (${dot.length})`);
|
||||
for (const c of dot) approx(c.amount, bb['attack@texas2_s_1[dot].dot_damage'], 1e-9, 'fixed arts per second (RES 0)');
|
||||
h.run(sk.duration);
|
||||
assert.ok(!u.findBuff('texas2:drizzle'), 'ends with the skill duration');
|
||||
assert.equal(u.skill.active, false, 'ends with the skill duration');
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(h.hooksOf('skillEnd').filter((c) => c.unit === u && c.reason === 'duration').length, 1);
|
||||
assert.ok(e.s.flags.silence && e.findBuff(`texas2:drizzleDot:${u.id}`), 'applied enemy effects keep their own duration');
|
||||
h.run(bb['attack@silence'] + 1);
|
||||
assert.ok(!e.s.flags.silence && !e.findBuff(`texas2:drizzleDot:${u.id}`), 'no new silence / DoT after the passive');
|
||||
}
|
||||
@@ -657,8 +682,8 @@ test('缄默德克萨斯 S2 阵雨连绵: deploy burst atk_scale arts + RES down
|
||||
approx(burst[0].amount, u.s.atk * bb.atk_scale * 0.5, 1e-6, 'arts burst vs RES 50');
|
||||
approx(e.s.res, 50 * (1 + bb.magic_resistance), 1e-6, 'RES down');
|
||||
assert.equal(far.s.res, 50);
|
||||
approx(u.findBuff('texas2:shower').mods.atkPct, bb.atk, 1e-9);
|
||||
approx(u.findBuff('texas2:shower').timeLeft, sk.duration, 1e-6);
|
||||
approx(u.skill.spec.mods.atkPct, bb.atk + D(id, 1).talents[0].bb.atk, 1e-9);
|
||||
approx(u.skill.timeLeft, sk.duration, 1e-6);
|
||||
h.run(2);
|
||||
const hits = dmgBy(h, u, (c) => c.dmg.isAttack);
|
||||
assert.ok(hits.length >= 2 && hits.every((c) => c.type === 'arts'), 'arts attacks');
|
||||
@@ -666,6 +691,8 @@ test('缄默德克萨斯 S2 阵雨连绵: deploy burst atk_scale arts + RES down
|
||||
for (const c of hits) per.set(c.dmg.attackId, (per.get(c.dmg.attackId) ?? 0) + 1);
|
||||
assert.ok([...per.values()].every((v) => v === 2), 'double hits');
|
||||
h.run(sk.duration);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
const n0 = dmgBy(h, u, (c) => c.dmg.isAttack).length;
|
||||
h.run(3);
|
||||
const late = dmgBy(h, u, (c) => c.dmg.isAttack).slice(n0);
|
||||
@@ -673,6 +700,57 @@ test('缄默德克萨斯 S2 阵雨连绵: deploy burst atk_scale arts + RES down
|
||||
}
|
||||
});
|
||||
|
||||
test('缄默德克萨斯 S1/S2/S3: first kill reopens a full window, including a kill inside the deployment burst', () => {
|
||||
for (const id of pair('chess_char_4_16_a')) for (const index of [0, 1, 2]) {
|
||||
const life = [];
|
||||
const h = battle([U(id, 10, 4, index)], { extra: { setup(b) {
|
||||
for (const name of ['skillStart', 'skillEnd']) b.on(name, (c) => life.push([name, c.reason]), { priority: 1000 });
|
||||
} } });
|
||||
h.b.start();
|
||||
const u = h.unit(id), sk = u.skill;
|
||||
h.run(2);
|
||||
u.hp = u.s.maxHp / 2;
|
||||
const e = h.spawn('enemy_dummy', { pos: [10, 5] });
|
||||
h.b.kill(e, u);
|
||||
assert.equal(u.mem.texasKilled, true);
|
||||
approx(u.hpRatio, 1);
|
||||
assert.equal(u.findBuff('texas2:swordplay'), null);
|
||||
assert.equal(sk.activations, 2);
|
||||
approx(sk.timeLeft, sk.duration);
|
||||
assert.equal(sk.charges, 0);
|
||||
assert.equal(sk.ready, false);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['recast']);
|
||||
h.b.kill(h.spawn('enemy_dummy', { pos: [10, 5] }), u);
|
||||
assert.equal(sk.activations, 2, 'only first kill recasts');
|
||||
h.run(sk.duration + 0.1);
|
||||
assert.equal(sk.active, false);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['recast', 'duration']);
|
||||
assert.deepEqual(h.hooksOf('skillStart').map((c) => c.reason), ['deploy', 'kill']);
|
||||
h.b.retreat(u);
|
||||
const lifeAt = life.length;
|
||||
const weak = h.spawn('enemy_dummy', { pos: [10, 5] });
|
||||
weak.hp = 1;
|
||||
assert.ok(h.b.redeploy(u, { free: true }));
|
||||
if (index === 0) h.b.kill(weak, u); // S1 has no deployment burst
|
||||
assert.equal(u.mem.texasKilled, true);
|
||||
assert.equal(sk.activations, 4, 'new deployment and first-kill recast, including synchronous burst reentry');
|
||||
assert.deepEqual(life.slice(lifeAt), [['skillStart', 'deploy'], ['skillEnd', 'recast'], ['skillStart', 'kill']]);
|
||||
assert.equal(u.findBuff('texas2:swordplay'), null);
|
||||
approx(sk.timeLeft, sk.duration);
|
||||
if (index === 2) {
|
||||
const target = h.spawn('enemy_dummy', { pos: [10, 5] });
|
||||
h.run(sk.duration + 0.1);
|
||||
const waves = new Set(dmgBy(h, u, tagged('swordRain')).filter((c) => c.target === target).map((c) => c.t));
|
||||
assert.equal(waves.size, sk.duration, 'reentrant deployment keeps a full set of rain waves');
|
||||
const n = dmgBy(h, u, tagged('swordRain')).length;
|
||||
h.run(2);
|
||||
assert.equal(dmgBy(h, u, tagged('swordRain')).length, n, 'rain stops at skill end');
|
||||
assert.ok(target.alive);
|
||||
}
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test('缄默德克萨斯 module none: no ATK +10 % when alone (EXE-Y default has it)', () => {
|
||||
const has = (m) => { const h = battle([U('chess_char_4_16_b', 10, 4, null, m)]); h.run(0.5); return !!h.unit('chess_char_4_16_b').findBuff('texas2:module'); };
|
||||
assert.equal(has(null), true);
|
||||
|
||||
@@ -1018,7 +1018,11 @@ test('6_17 耀骑士临光 S2 逐夜烁光: on deploy ATK + and 3 护盾 layers
|
||||
const u = h.unit(id);
|
||||
usesSkill(u, sid);
|
||||
h.step();
|
||||
approx(u.findBuff('nearl2:night').mods.atkPct, bb.atk);
|
||||
approx(u.skill.spec.mods.atkPct, bb.atk);
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.active, true);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], dur);
|
||||
assert.equal(u.findBuff('nearl2:shield').shieldHits, bb.times);
|
||||
h.run(bb.times * 1.2 + 1);
|
||||
const taken = h.hooksOf('damaged').filter((c) => c.target === u);
|
||||
@@ -1028,15 +1032,30 @@ test('6_17 耀骑士临光 S2 逐夜烁光: on deploy ATK + and 3 护盾 layers
|
||||
assert.ok(h.runUntil(() => !u.alive, dur + 1));
|
||||
approx(h.b.time, dur, 'withdraws when it ends', 0.05);
|
||||
approx(u.respawnAt - h.b.time, u.base.respawnTime * bb.respawn_time, 'this redeploy ×1.25', 1e-3);
|
||||
assert.equal(u.removeReason, 'retreat');
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').filter((c) => c.unit === u).map((c) => c.reason), ['duration']);
|
||||
done(h);
|
||||
|
||||
const k = run({ defs: { chess: { kaz: plain('kaz', { bonds: ['kazimierzShip'] }) } }, units: [U(id, sid, 10, 4), { chessId: 'kaz', row: 12, col: 4 }] });
|
||||
const ku = k.unit(id);
|
||||
k.step(2);
|
||||
assert.ok(ku.alive && ku.findBuff('nearl2:night'));
|
||||
assert.ok(ku.alive && ku.skill.active);
|
||||
assert.ok(k.runUntil(() => !ku.alive, dur + 1));
|
||||
approx(ku.respawnAt - k.b.time, ku.base.respawnTime, '卡西米尔 before her: no extension', 1e-3);
|
||||
done(k);
|
||||
|
||||
const dead = run({ units: [U(id, sid, 10, 4, { moduleId: 'none' })] });
|
||||
dead.step();
|
||||
const du = dead.unit(id);
|
||||
dead.b.kill(du);
|
||||
const respawnAt = du.respawnAt;
|
||||
approx(respawnAt - dead.b.time, du.base.respawnTime, 'early death keeps ordinary redeployment');
|
||||
assert.equal(du.findBuff('nearl2:shield'), null);
|
||||
dead.run(dur + 1);
|
||||
assert.equal(du.respawnAt, respawnAt, 'no delayed retreat or multiplier after death');
|
||||
assert.deepEqual(dead.hooksOf('skillEnd').filter((c) => c.unit === du).map((c) => c.reason), ['death']);
|
||||
done(dead);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -961,6 +961,10 @@ test('2_12 砾: 鼠群 barrier hp_ratio × max HP decaying over `duration` s;
|
||||
assert.equal(u.base.cost, raw(id).stats.cost + t.cost);
|
||||
h.step();
|
||||
const full = u.s.maxHp * bb.hp_ratio;
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.active, true);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.deepEqual(h.snapshot().units.find((t) => t[0] === u.id).slice(5, 7), [Math.round(u.skill.timeLeft * 10) / 10, bb.duration]);
|
||||
approx(u.s.shield, full, 'full barrier', 1e-2);
|
||||
h.run(bb.duration / 2 + 0.5);
|
||||
approx(u.s.shield, full / 2, 'half decayed', 1e-2);
|
||||
@@ -968,6 +972,9 @@ test('2_12 砾: 鼠群 barrier hp_ratio × max HP decaying over `duration` s;
|
||||
approx(u.s.shield, full / 2, 'PRTS: the capacity steps once per second', 1e-2);
|
||||
h.run(bb.duration / 2);
|
||||
assert.equal(u.s.shield, 0);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.equal(u.findBuff('gravel:rats'), null);
|
||||
done(h);
|
||||
// PRTS "受到伤害后,剩余容量根据屏障最大容量等比变化": a damaged barrier shrinks in proportion, not to the capacity
|
||||
const hd = run({ units: [{ chessId: id, row: 9, col: 5 }] });
|
||||
@@ -979,6 +986,12 @@ test('2_12 砾: 鼠群 barrier hp_ratio × max HP decaying over `duration` s;
|
||||
hd.run(bb.duration / 2 + 0.5);
|
||||
approx(ud.s.shield, cap0 * 0.4 / 2, 'remaining × capacity ratio (a plain cap would keep 40 %)', 1e-2);
|
||||
assert.ok(ud.hp === ud.s.maxHp);
|
||||
hd.b.retreat(ud);
|
||||
assert.equal(ud.findBuff('gravel:rats'), null);
|
||||
assert.equal(ud.skill.active, false);
|
||||
assert.ok(hd.b.redeploy(ud, { free: true }));
|
||||
approx(ud.skill.timeLeft, bb.duration);
|
||||
approx(ud.s.shield, cap0, 'fresh shield on redeployment');
|
||||
done(hd);
|
||||
const idb = 'chess_char_2_12_b', tb = tal(idb);
|
||||
const cheapRec = chessRec({ id: 't_cheap', stats: { cost: tb['cond.cost'] } });
|
||||
|
||||
@@ -645,7 +645,8 @@ test('缄默德克萨斯 S3 (passive): deploy burst 2 × 115 % arts + 1.5 s stun
|
||||
assert.equal(waves.size, D(id).skill.duration, 'one rain per second while the passive lasts');
|
||||
assert.ok([...waves.values()].every((n) => n === bb.max_target), '≤ 2 distinct targets');
|
||||
approx(rain[0].amount, u.base.atk * (1 + t0.atk) * bb.atk_scale, 1e-6);
|
||||
assert.equal(u.findBuff('texas2:rainAtk'), null, 'ATK bonus ends with the passive');
|
||||
assert.equal(u.skill.active, false, 'ATK bonus ends with the skill');
|
||||
approx(u.s.atk, u.base.atk);
|
||||
});
|
||||
|
||||
test('缄默德克萨斯: first kill ⇒ full heal + recast; 剑术 buff removed', () => {
|
||||
@@ -660,7 +661,8 @@ test('缄默德克萨斯: first kill ⇒ full heal + recast; 剑术 buff removed
|
||||
assert.ok(u.mem.texasKilled);
|
||||
approx(u.hpRatio, 1, 1e-3, 'full heal');
|
||||
assert.equal(u.findBuff('texas2:swordplay'), null);
|
||||
assert.ok(u.findBuff('texas2:rainAtk'), 'passive recast');
|
||||
assert.equal(u.skill.active, true, 'skill recast');
|
||||
assert.equal(u.skill.activations, 2);
|
||||
});
|
||||
|
||||
test('星熊 S2 (passive): DEF +13 % and 65 % ATK thorns; 战术装甲 negates ~25 % of hits; 重装 aura DEF +6 %', () => {
|
||||
|
||||
@@ -231,7 +231,9 @@ test('the eight 预备干员: every chess record they replace fields the stand-i
|
||||
const dp0 = h.b.players[0].dp;
|
||||
if (sk.skillType === 'PASSIVE') {
|
||||
h.step();
|
||||
assert.deepEqual(u.findBuff(`generic:passive:${sk.skillId}`)?.mods, statMods(sk.bb), `${where}: deploy buff`);
|
||||
// a deploy-timed passive runs the duration lifecycle from the deployment (#109): the skill's own mods
|
||||
assert.deepEqual([u.skill.kind, !!u.skill.spec.activateOnDeploy, u.skill.active], ['duration', true, true], `${where}: deploy-timed skill`);
|
||||
assert.deepEqual(u.findBuff(`skill:${u.id}`)?.mods, statMods(sk.bb), `${where}: deploy buff`);
|
||||
} else {
|
||||
assert.ok(h.runUntil(() => u.skill.activations > 0, 30), `${where}: the skill fires`);
|
||||
h.step();
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
// Without the extraction the web alias is drawn tinted toward the slug's own colours (render/units.js ALIAS_TINT,
|
||||
// research 07 §5.6 "a hue shift" [ASSUMED look]), so source installs still tell them apart from the plain slug.
|
||||
// 高能 / 冰爆 / 简饲源石虫 and “庞贝” always had their own models (checked in headless Chrome).
|
||||
//
|
||||
// Root cause (2026-10-04): Ark-Models *indexes* 1305_mhslim / 1305_mhslim_2 with an EMPTY `assetList` — registered,
|
||||
// never uploaded — so `arkModel()` finds nothing and the alias chain drops to enemy_1007_slime, a *different* enemy
|
||||
// rather than a variant of it. The mobile build does ship their own model (straight-alpha pages, no `pma: true` line),
|
||||
// and it is reachable, but its only public mirror is a community wiki rather than a GitHub dump: adding it would put a
|
||||
// non-GitHub host into `sources.mjs` for two models, which the reviewer of this change asked not to do. So the tinted
|
||||
// alias stays the web model, the local extraction stays the overlay, and the last block below locks both halves
|
||||
// (only these two enemies borrow a different enemy's skeleton; every asset source stays a GitHub dump).
|
||||
|
||||
import { test, describe, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -19,7 +27,8 @@ import { spawnSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { buildPlan } from '../tools/assets/plan.mjs';
|
||||
import { buildPlan, ENEMY_SPINE_ALIAS } from '../tools/assets/plan.mjs';
|
||||
import { RAW } from '../tools/assets/sources.mjs';
|
||||
import { resolveTemplate, collectLeaves } from '../tools/assets/manifest.mjs';
|
||||
import { findLocalEnemyModels, localEnemySpineMeta, loadLocalEnemySpines, LOCAL_ENEMY_SPINES_FILE, localEnemySpineGroup } from '../tools/assets/spine.mjs';
|
||||
import { indexAudio } from '../tools/assets/audio.mjs';
|
||||
@@ -174,6 +183,34 @@ describe('D3: the official slug models are an optional overlay of the web alias'
|
||||
});
|
||||
});
|
||||
|
||||
describe('D3 root cause: only the two slugs borrow a different enemy\'s skeleton, and no non-GitHub source is added', () => {
|
||||
test('plan: ENEMY_SPINE_ALIAS covers exactly the two slugs (Ark-Models indexes them with an empty assetList)', () => {
|
||||
const p = plan(undefined);
|
||||
assert.deepEqual(Object.keys(ENEMY_SPINE_ALIAS).sort(), [...SLUGS].sort(), 'exactly the slugs are aliased by hand');
|
||||
for (const id of SLUGS) assert.equal(ENEMY_SPINE_ALIAS[id], 'enemy_1007_slime', `${id}: the plain 源石虫`);
|
||||
assert.ok(p.notes.some((n) => n.includes('enemy_1305_mhslim') && /aliased/.test(n)),
|
||||
'the plan says so in its notes, so a run without the model is explainable');
|
||||
});
|
||||
|
||||
test('data/assets.json: every other spineAliasOf stays inside its own variant family', () => {
|
||||
const borrowed = [];
|
||||
for (const [id, e] of Object.entries(MANIFEST.enemies)) {
|
||||
if (!e.spineAliasOf) continue;
|
||||
const base = /^(enemy_\d+_[a-z0-9]+?)_\d+$/i.exec(id); // enemy_2001_duckmi_2 → enemy_2001_duckmi
|
||||
if (!SLUGS.includes(id) && (!base || base[1] !== e.spineAliasOf)) borrowed.push(`${id} → ${e.spineAliasOf}`);
|
||||
}
|
||||
assert.deepEqual(borrowed, [],
|
||||
'the six _2 aliases draw the base enemy their own prefab uses; only the two slugs borrow a different enemy');
|
||||
});
|
||||
|
||||
test('every asset source stays a GitHub dump (no third-party mirror for two models)', () => {
|
||||
for (const [name, url] of Object.entries(RAW)) {
|
||||
assert.match(url, /^https:\/\/raw\.githubusercontent\.com\//, `RAW.${name} is a raw.githubusercontent.com URL`);
|
||||
}
|
||||
assert.ok(!/prts|torappu/i.test(JSON.stringify(RAW)), 'no community-wiki host among the download sources');
|
||||
});
|
||||
});
|
||||
|
||||
describe('D3 client: the official model only when data/local-assets.json lists it', () => {
|
||||
const webOf = (id) => MANIFEST.enemies[id].spine; // the plain 源石虫's model (spineAliasOf enemy_1007_slime)
|
||||
|
||||
|
||||
+174
-174
@@ -42,7 +42,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1019_jshoot":[5,5,0,513,10368],"enemy_1043_zomsbr":[18,18,0,351,54677],"enemy_1299_ymkilr":[1,1,0,2251,9792]},
|
||||
"hooks": [1,35,1535,152,152,200,200,1064,24,24,3,1,0,6482,0,19,24,0,1,24,0,0,0],
|
||||
"events": {"atk":152,"deploy":8,"die":24,"dmg":200,"fx":4,"skill":4,"spawn":35,"status":1},
|
||||
"events": {"atk":152,"deploy":8,"die":24,"dmg":200,"fx":4,"skill":6,"spawn":35,"status":1},
|
||||
"fx": {"buff":1,"coin":3},
|
||||
"snaps": "6a513536 c150215b 81084fd8 9870355a f707b691 df63fdcd"
|
||||
},
|
||||
@@ -122,7 +122,7 @@
|
||||
"snaps": "b677776a 05df3ce3 3f4ea1ef 7c86ba28 c343548f 5e5789b8"
|
||||
},
|
||||
"bond-deputShip-high": {
|
||||
"about": "助力 high: tier 2 layers 999; chess_char_4_11_b chess_char_4_12_b chess_char_1_06_b chess_char_1_13_b chess_char_2_14_b chess_char_1_06_a chess_char_1_13_a chess_char_4_10_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "助力 high: tier 2 layers 999; chess_char_4_11_b chess_char_1_06_b chess_char_1_13_b chess_char_2_14_b chess_char_1_06_a chess_char_1_13_a chess_char_4_10_b chess_char_4_12_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": true,
|
||||
"kind": "normal",
|
||||
"stageId": "act2autochess_m03",
|
||||
@@ -130,8 +130,8 @@
|
||||
"round": 10,
|
||||
"seed": 1208009840,
|
||||
"waveId": "act1autochess_h03",
|
||||
"time": 101.97,
|
||||
"ticks": 3059,
|
||||
"time": 98.27,
|
||||
"ticks": 2948,
|
||||
"reason": "cleared",
|
||||
"killed": 8,
|
||||
"total": 8,
|
||||
@@ -139,27 +139,27 @@
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 1,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":129773,"bossDmg":0,"heal":7536,"deaths":0,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":129773,"bossDmg":0,"heal":7124,"deaths":0,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,5,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,6,0,14088,0,0,8,1],
|
||||
["p1",3,"op","chess_char_1_06_b",1,"uniequip_002_vendla",1051.4,1,7,4,47025,6060,5773,45,3],
|
||||
["p1",4,"op","chess_char_1_13_b",1,"uniequip_002_podego",815.5,1,9,1,36853,0,0,38,5],
|
||||
["p1",5,"op","chess_char_2_14_b",1,"uniequip_002_flower",1544,1,10,0,0,160,506,22,2],
|
||||
["p1",6,"op","chess_char_1_06_a",1,null,1137,1,11,1,21655,1572,1226,40,1],
|
||||
["p1",7,"op","chess_char_1_13_a",1,null,913,1,4,0,870,0,0,1,1],
|
||||
["p1",8,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,8,1,9282,95,31,7,2],
|
||||
["p1",2,"op","chess_char_1_06_b",1,"uniequip_002_vendla",1002.81,1,7,2,42201,5880,5545,41,3],
|
||||
["p1",3,"op","chess_char_1_13_b",1,"uniequip_002_podego",815.5,1,9,1,34934,0,0,35,5],
|
||||
["p1",4,"op","chess_char_2_14_b",1,"uniequip_002_flower",1544,1,10,0,0,0,399,19,1],
|
||||
["p1",5,"op","chess_char_1_06_a",1,null,1137,1,11,1,21655,1548,1148,40,1],
|
||||
["p1",6,"op","chess_char_1_13_a",1,null,913,1,4,0,0,0,0,0,0],
|
||||
["p1",7,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,8,1,12717,64,0,9,2],
|
||||
["p1",8,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,6,2,18266,31,31,10,1],
|
||||
["p1",9,"token","token_10041_cathy_catsld",null,null,100,1,12,0,0,0,0,0,null],
|
||||
["p1",10,"token","token_10041_cathy_catsld",null,null,100,1,13,0,0,0,0,0,null],
|
||||
["-","~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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1005_yokai_2":[4,4,0,96,5357],"enemy_1005_yokai_3":[2,2,0,1022,69120],"enemy_9009_acfort":[2,2,0,709,55296]},
|
||||
"hooks": [1,21,3059,230,230,369,369,2366,8,8,15,12,0,11504,115,0,8,0,1,7,0,0,0],
|
||||
"events": {"atk":230,"deploy":10,"die":8,"dmg":369,"fx":37,"heal":78,"skill":27,"spawn":21,"status":84},
|
||||
"fx": {"beam":2,"counter":16,"pull":1,"slippery":2,"taunt":4,"tornado":1,"tornadoPulse":5,"zone":6},
|
||||
"snaps": "f7c1fb30 eb023e29 d1fffc22 c886d0aa 5bf78694 5f3df034 3d54c073 581471d0 d97ecde2 8c045b67 57772151"
|
||||
"enemies": {"enemy_1005_yokai_2":[4,4,0,96,5357],"enemy_1005_yokai_3":[2,2,0,862,69120],"enemy_9009_acfort":[2,2,0,685,55296]},
|
||||
"hooks": [1,21,2948,217,217,357,357,2132,8,8,13,11,0,11234,107,0,8,0,1,7,0,0,0],
|
||||
"events": {"atk":217,"deploy":10,"die":8,"dmg":357,"fx":36,"heal":74,"skill":24,"spawn":21,"status":80},
|
||||
"fx": {"beam":2,"counter":16,"pull":1,"slippery":2,"taunt":4,"tornado":1,"tornadoPulse":5,"zone":5},
|
||||
"snaps": "f66ffc26 e4eea262 37ff890d e451ff33 28b10f94 47d55e15 7cf8b2aa 7d1d7e17 11c2118a 7599f2fd"
|
||||
},
|
||||
"bond-egirShip-low": {
|
||||
"about": "阿戈尔 low: tier 1 layers 1; chess_char_1_04_a chess_char_3_16_a chess_char_2_07_a chess_char_3_18_a chess_char_3_05_a chess_char_3_14_a chess_char_3_17_a chess_char_3_19_a token_10028_vigil_wolf",
|
||||
@@ -236,17 +236,17 @@
|
||||
["p1",1,"op","chess_char_1_04_b",1,"uniequip_002_udflow",32931.36,1,2,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_2_07_b",1,"uniequip_002_ghost",27707.68,1,3,0,0,4635,4635,0,0],
|
||||
["p1",3,"op","chess_char_3_05_b",2,"uniequip_002_skadi",18673.58,1,11,11,135708,36837,0,45,1],
|
||||
["p1",4,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,17,0,0,1619,0,0,0],
|
||||
["p1",4,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,17,0,0,1619,0,0,2],
|
||||
["p1",5,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",20727.84,1,6,4,186331,0,0,12,2],
|
||||
["p1",6,"op","chess_char_3_09_b",1,"uniequip_002_haini",17395.56,1,7,0,23243,0,0,20,1],
|
||||
["p1",7,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,8,4,34187,0,0,35,15],
|
||||
["p1",8,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,1,0,0,0,0,0,0]
|
||||
],
|
||||
"enemies": {"enemy_1229_darmy":[10,10,0,4638,104509],"enemy_1229_darmy_2":[7,7,0,18088,95800],"enemy_9007_acelem":[2,2,0,4609,179159]},
|
||||
"hooks": [1,29,2221,148,148,256,276,251,20,21,19,19,0,4154,485,13,19,0,1,20,5,3,0],
|
||||
"events": {"atk":167,"deploy":10,"die":21,"dmg":276,"fx":34,"layer":5,"skill":37,"spawn":27,"status":132},
|
||||
"hooks": [1,29,2221,148,148,256,276,251,20,21,21,20,0,4154,485,13,19,0,1,20,5,3,0],
|
||||
"events": {"atk":167,"deploy":10,"die":21,"dmg":276,"fx":34,"layer":5,"skill":41,"spawn":27,"status":132},
|
||||
"fx": {"beam":2,"blink":1,"burst":3,"devour":4,"dilemma":2,"frostNova":15,"garrison":5,"swordStorm":2},
|
||||
"snaps": "066fec91 ab36cdb7 ec262f21 c49cc94d 7299d69b c5c80506 eb36b938 9265751a"
|
||||
"snaps": "066fec91 aec41fca b75071e0 478fe370 a68e442a 1b0b0907 cc527129 08c4150f"
|
||||
},
|
||||
"bond-emptyShip-low": {
|
||||
"about": "协防干员 low: tier 1 layers 1; chess_char_4_01_a chess_char_4_04_a chess_char_2_14_a chess_char_3_10_a chess_char_3_20_a chess_char_3_21_a chess_char_4_02_a chess_char_4_06_a",
|
||||
@@ -316,12 +316,12 @@
|
||||
],
|
||||
"enemies": {"enemy_1045_hammer_2":[2,1,1,8552,38604],"enemy_1061_zomshd":[11,11,0,983,179484]},
|
||||
"hooks": [1,23,4184,327,327,641,632,9770,13,14,16,15,0,6375,15,29,13,1,1,15,0,0,2],
|
||||
"events": {"atk":422,"deploy":8,"die":13,"dmg":622,"fx":104,"heal":22,"leak":1,"skill":31,"spawn":23,"status":44},
|
||||
"events": {"atk":422,"deploy":8,"die":13,"dmg":622,"fx":104,"heal":22,"leak":1,"skill":33,"spawn":23,"status":44},
|
||||
"fx": {"block":7,"dodge":2,"flame":1,"garrisonGrant":1,"riposte":1,"rockslide":7,"shieldBreak":10,"slash":40,"thorns":33,"truesilver":2},
|
||||
"snaps": "48d2a0bc 7a5c72a3 37963453 9c6110e3 fcf46ff8 04b08d2c c03bf731 3792e3d9 e4639ebf decf37ce a41ea7f2 bd40f194 8e5c993d 300fcece"
|
||||
},
|
||||
"bond-indomShip-low": {
|
||||
"about": "不屈 low: tier 1 layers 1; chess_char_1_20_a chess_char_4_11_a chess_char_4_12_a chess_char_2_12_a chess_char_4_09_a chess_char_4_10_a chess_char_4_13_a chess_char_4_14_a token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "不屈 low: tier 1 layers 1; chess_char_1_20_a chess_char_4_11_a chess_char_2_12_a chess_char_4_09_a chess_char_4_10_a chess_char_4_12_a chess_char_4_13_a chess_char_4_14_a token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "normal",
|
||||
"stageId": "act1autochess_m03",
|
||||
@@ -329,38 +329,38 @@
|
||||
"round": 5,
|
||||
"seed": 1441167629,
|
||||
"waveId": "act1autochess_05",
|
||||
"time": 95.97,
|
||||
"ticks": 2879,
|
||||
"time": 67.97,
|
||||
"ticks": 2039,
|
||||
"reason": "cleared",
|
||||
"killed": 17,
|
||||
"killed": 18,
|
||||
"total": 18,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 20,
|
||||
"rngDraws": 14,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":17,"total":18,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":86959,"bossDmg":0,"heal":1523,"deaths":5,"layerGains":{"indomShip":2}}
|
||||
"p1": {"killed":18,"total":18,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":86976,"bossDmg":0,"heal":1661,"deaths":3,"layerGains":{"indomShip":2}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_1_20_a",1,null,1627.2,1,4,0,623,571,0,37,0],
|
||||
["p1",2,"op","chess_char_4_11_a",1,null,0,0,5,0,68,2585,0,3,1],
|
||||
["p1",3,"op","chess_char_4_12_a",2,null,0,0,7,0,11547,3277,1523,33,2],
|
||||
["p1",4,"op","chess_char_2_12_a",1,null,1107,1,34,0,881,1107,0,13,0],
|
||||
["p1",5,"op","chess_char_4_09_a",1,null,2044.67,1,10,8,40549,93,0,15,2],
|
||||
["p1",6,"op","chess_char_4_10_a",1,null,1029.36,1,6,7,17689,259,0,17,1],
|
||||
["p1",7,"op","chess_char_4_13_a",1,null,0,0,9,2,10229,1587,0,21,4],
|
||||
["p1",8,"op","chess_char_4_14_a",1,null,1276,1,35,0,5373,1276,0,8,1],
|
||||
["p1",1,"op","chess_char_1_20_a",1,null,577.4,1,5,0,421,1621,0,26,0],
|
||||
["p1",2,"op","chess_char_4_11_a",1,null,0,0,6,0,0,3002,0,0,1],
|
||||
["p1",3,"op","chess_char_2_12_a",1,null,1107,1,34,1,393,1107,0,10,2],
|
||||
["p1",4,"op","chess_char_4_09_a",1,null,1978.85,1,9,9,39308,159,0,13,1],
|
||||
["p1",5,"op","chess_char_4_10_a",1,null,1288,1,7,4,22387,0,0,17,1],
|
||||
["p1",6,"op","chess_char_4_12_a",2,null,989.98,1,10,0,0,2425,1661,0,0],
|
||||
["p1",7,"op","chess_char_4_13_a",1,null,0,0,11,0,6895,1587,0,13,2],
|
||||
["p1",8,"op","chess_char_4_14_a",1,null,1276,1,4,4,17572,0,0,12,5],
|
||||
["p1",9,"token","token_10041_cathy_catsld",null,null,100,1,12,0,0,0,0,0,null],
|
||||
["p1",10,"token","token_10041_cathy_catsld",null,null,100,1,13,0,0,0,0,0,null],
|
||||
["-","~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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1158_divman":[15,15,0,2849,57024],"enemy_1160_hvyslr":[2,1,1,5740,23023],"enemy_1161_tidmag":[1,1,0,2166,6912]},
|
||||
"hooks": [1,33,2879,213,213,305,307,1042,22,23,11,12,0,8131,210,8,18,1,1,22,1,0,9],
|
||||
"events": {"atk":256,"deploy":12,"die":22,"dmg":307,"fx":48,"layer":1,"leak":1,"skill":22,"spawn":31,"status":99},
|
||||
"fx": {"dilemma":2,"displace":7,"dodge":9,"explosion":1,"frostNova":4,"garrison":1,"levitate":7,"overclock":1,"pull":1,"shield":2,"slippery":1,"splash":2,"tornado":2,"tornadoPulse":8},
|
||||
"snaps": "8bec34f2 edddc261 3a83c8ff fe539554 b9c685a0 fc37ba44 27c41a48 755c7acf f9c428e6 8ba53346"
|
||||
"enemies": {"enemy_1158_divman":[15,15,0,2119,57024],"enemy_1160_hvyslr":[2,2,0,5090,23040],"enemy_1161_tidmag":[1,1,0,2691,6912]},
|
||||
"hooks": [1,32,2039,172,172,261,269,1137,21,21,12,12,0,5972,102,9,18,0,1,21,1,1,3],
|
||||
"events": {"atk":222,"deploy":11,"die":21,"dmg":269,"fx":26,"layer":1,"skill":24,"spawn":31,"status":80},
|
||||
"fx": {"burst":1,"dilemma":1,"dodge":3,"explosion":5,"frostNova":2,"garrison":1,"levitate":7,"overclock":1,"shield":2,"slippery":1,"splash":2},
|
||||
"snaps": "910f43b9 691d4675 7aa329ef 265d3e05 a0f84586 b0546a70 a29372ef"
|
||||
},
|
||||
"bond-indomShip-high": {
|
||||
"about": "不屈 high: tier 2 layers 999; chess_char_1_20_b chess_char_3_16_b chess_char_4_23_b chess_char_5_01_b chess_char_2_12_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
|
||||
@@ -374,28 +374,28 @@
|
||||
"time": 190,
|
||||
"ticks": 5700,
|
||||
"reason": "timeout",
|
||||
"killed": 14,
|
||||
"killed": 13,
|
||||
"total": 22,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 298,
|
||||
"rngDraws": 306,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":14,"total":22,"perfect":false,"leaks":8,"leaksUncounted":0,"coins":0,"dmg":263129,"bossDmg":0,"heal":70003,"deaths":19,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":13,"total":22,"perfect":false,"leaks":9,"leaksUncounted":0,"coins":0,"dmg":304172,"bossDmg":0,"heal":65632,"deaths":22,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_1_20_b",1,"uniequip_002_liskam",61.47,1,48,2,37022,31443,7160,94,6],
|
||||
["p1",2,"op","chess_char_3_16_b",1,"uniequip_002_snakek",3100,1,5,0,0,0,2643,0,0],
|
||||
["p1",1,"op","chess_char_1_20_b",1,"uniequip_002_liskam",2892,1,50,2,31083,30784,5313,80,7],
|
||||
["p1",2,"op","chess_char_3_16_b",1,"uniequip_002_snakek",3100,1,5,0,0,0,1569,0,0],
|
||||
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,7,0,0,0,0,0,0],
|
||||
["p1",4,"op","chess_char_5_01_b",1,"uniequip_002_excu2",423.76,1,43,8,145703,33989,15901,154,8],
|
||||
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1929.76,1,42,1,15616,31872,9053,196,0],
|
||||
["p1",6,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,49,3,64788,4346,4371,63,4],
|
||||
["p1",7,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,47,0,0,7103,6480,0,2],
|
||||
["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,46,0,0,11712,24394,34,4]
|
||||
["p1",4,"op","chess_char_5_01_b",1,"uniequip_002_excu2",1354.58,1,52,8,196467,35988,16517,200,9],
|
||||
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1268.06,1,44,1,15616,31872,8570,196,4],
|
||||
["p1",6,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,48,2,61006,4225,3289,58,3],
|
||||
["p1",7,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,49,0,0,7103,6023,0,2],
|
||||
["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,47,0,0,11712,24351,33,4]
|
||||
],
|
||||
"enemies": {"enemy_1166_dusbr_2":[14,12,2,28217,144069],"enemy_1170_dushld_2":[4,2,2,45909,73715],"enemy_9011_acrefr":[4,0,2,46339,45345]},
|
||||
"hooks": [1,49,5700,831,831,1190,1169,4295,33,39,24,26,81,4034,210,59,22,6,1,33,0,0,21],
|
||||
"events": {"atk":1224,"deploy":27,"die":33,"dmg":1166,"fx":163,"heal":167,"leak":6,"skill":47,"spawn":30,"status":541},
|
||||
"fx": {"bandage":4,"dodge":21,"extraAttack":42,"garrisonGrant":1,"mote":39,"redistribute":30,"revive":16,"reweave":2,"sandChains":4,"shield":4},
|
||||
"snaps": "bcca108c b78f4774 704b4447 e8becbbe 55de3ed6 7a40bda1 9e89e327 6796cf0f 31697842 2e290595 675d935c 576bc3f1 49c78987 a3206632 6bd55a9d e6a7dcae 9f209030 137f71a3 4b456e75"
|
||||
"enemies": {"enemy_1166_dusbr_2":[14,11,3,27513,136496],"enemy_1170_dushld_2":[4,1,3,36563,52797],"enemy_9011_acrefr":[4,1,2,57607,114879]},
|
||||
"hooks": [1,52,5700,834,834,1143,1124,3859,35,43,29,28,102,4003,202,60,22,8,1,35,0,0,19],
|
||||
"events": {"atk":1176,"deploy":30,"die":35,"dmg":1121,"fx":177,"heal":193,"leak":8,"skill":57,"spawn":30,"status":530},
|
||||
"fx": {"bandage":4,"dodge":19,"extraAttack":54,"garrisonGrant":1,"mote":40,"redistribute":30,"revive":19,"reweave":2,"sandChains":3,"shield":4,"spGift":1},
|
||||
"snaps": "a5613700 ec9cb900 84379a83 c02cd949 c7444986 f9e23c1c e0aa99f1 eeea552b 24eb1e39 15dcee36 e5d5538f fbefb93b 04cf81c1 90a2259d 859536f6 b9c7eade fb8513f0 ac9b1324 54084832"
|
||||
},
|
||||
"bond-investShip-low": {
|
||||
"about": "投资人 low: tier 1 layers 1; chess_char_4_17_a chess_char_4_18_a chess_char_5_14_a chess_char_4_19_a chess_char_3_04_a chess_char_4_16_a chess_char_2_19_a chess_char_4_20_a",
|
||||
@@ -422,7 +422,7 @@
|
||||
["p1",3,"op","chess_char_5_14_a",1,null,1388,1,5,0,8599,895,731,29,5],
|
||||
["p1",4,"op","chess_char_4_19_a",2,null,487.76,1,8,2,3429,1072,0,21,1],
|
||||
["p1",5,"op","chess_char_3_04_a",1,null,667.08,1,9,4,17229,2310,0,29,0],
|
||||
["p1",6,"op","chess_char_4_16_a",2,null,1246,1,42,4,11741,1246,0,20,0],
|
||||
["p1",6,"op","chess_char_4_16_a",2,null,1246,1,42,4,11741,1246,0,20,3],
|
||||
["p1",7,"op","chess_char_2_19_a",1,null,0,0,6,4,9565,1021,0,24,1],
|
||||
["p1",8,"op","chess_char_4_20_a",2,null,1009.8,1,7,3,12298,207,0,17,1],
|
||||
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
|
||||
@@ -445,10 +445,10 @@
|
||||
["p1","~16","token","token_10031_swire2_gdtrap",null,null,1000,1,45,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1158_divman":[15,15,0,1399,44550],"enemy_1160_hvyslr":[2,2,0,3890,18000],"enemy_1161_tidmag":[1,1,0,1463,5400]},
|
||||
"hooks": [1,45,2498,223,223,306,307,478,21,33,9,10,0,6503,44,10,18,0,1,22,0,0,9],
|
||||
"events": {"atk":234,"deploy":25,"die":33,"dmg":303,"fx":89,"skill":18,"spawn":44,"status":70},
|
||||
"hooks": [1,45,2498,223,223,306,307,478,21,33,12,12,0,6503,44,10,18,0,1,22,0,0,9],
|
||||
"events": {"atk":234,"deploy":25,"die":33,"dmg":303,"fx":89,"skill":28,"spawn":44,"status":70},
|
||||
"fx": {"aoe":6,"coin":19,"dodge":9,"explode":13,"featherArrow":1,"flame":1,"garrisonGrant":1,"revive":1,"riposte":7,"rockslide":1,"shieldBreak":4,"summon":16,"swordRain":6,"swordStorm":3,"zone":1},
|
||||
"snaps": "df1c09b6 c5c9f7d2 71bce589 e2853803 32ae9ac8 34b10ae9 82dae7e0 86ec8e63 94c9fbcb"
|
||||
"snaps": "1ecc8642 832be356 348fd8fd 002fcccf 012b93d9 f75ce414 18574635 facf960a abfbfba2"
|
||||
},
|
||||
"bond-investShip-high": {
|
||||
"about": "投资人 high: tier 1 layers 999; chess_char_5_14_b chess_char_5_05_b chess_char_5_06_b chess_char_3_04_b chess_char_5_07_b chess_char_2_19_b chess_char_5_02_b chess_char_5_03_b",
|
||||
@@ -484,7 +484,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1229_darmy":[10,10,0,0,48384],"enemy_1229_darmy_2":[7,7,0,0,44352],"enemy_1275_dwlock_2":[3,3,0,3570,48384],"enemy_5601_entlec":[2,2,0,0,5786]},
|
||||
"hooks": [1,33,2143,108,108,161,354,291,22,23,7,5,42,3991,0,3,22,0,1,62,0,0,0],
|
||||
"events": {"atk":118,"deploy":8,"die":23,"dmg":199,"fx":55,"skill":12,"spawn":33,"status":3},
|
||||
"events": {"atk":118,"deploy":8,"die":23,"dmg":199,"fx":55,"skill":14,"spawn":33,"status":3},
|
||||
"fx": {"anchor":2,"aoe":1,"beam":1,"candle":2,"coin":4,"downed":1,"ember":1,"revive":1,"splash":42},
|
||||
"snaps": "62c7bcd4 674c8844 6a9bd237 91a64467 cba2cbbe e9220cd3 1da987ee fb3307c8"
|
||||
},
|
||||
@@ -514,7 +514,7 @@
|
||||
["p1",2,"op","chess_char_4_23_a",2,null,2179,1,6,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_4_22_a",2,null,2022,1,8,4,26393,0,0,19,1],
|
||||
["p1",4,"op","chess_char_1_19_a",1,null,0,0,9,0,15120,4341,0,35,1],
|
||||
["p1",5,"op","chess_char_2_12_a",1,null,0,0,10,1,24825,8504,3348,59,0],
|
||||
["p1",5,"op","chess_char_2_12_a",1,null,0,0,10,1,24825,8504,3348,59,1],
|
||||
["p1",6,"op","chess_char_4_21_a",1,null,1271,1,4,0,0,0,5770,35,1],
|
||||
["p1",7,"op","chess_char_4_24_a",1,null,1726,1,5,3,8044,0,0,12,2],
|
||||
["p1",8,"op","chess_char_4_25_a",2,null,140.06,1,11,0,0,4598,4369,0,1],
|
||||
@@ -523,10 +523,10 @@
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1116_liprr":[11,11,0,4898,69696],"enemy_1116_liprr_2":[4,4,0,179,34560],"enemy_1121_lifbos":[1,1,0,8539,23040]},
|
||||
"hooks": [1,27,2084,242,242,384,384,1800,18,18,7,7,0,7493,12,17,16,0,1,18,1,0,0],
|
||||
"events": {"atk":265,"deploy":8,"die":18,"dmg":376,"fx":59,"heal":48,"layer":1,"skill":13,"spawn":27,"status":49},
|
||||
"hooks": [1,27,2084,242,242,384,384,1800,18,18,8,7,0,7493,12,17,16,0,1,18,1,0,0],
|
||||
"events": {"atk":265,"deploy":8,"die":18,"dmg":376,"fx":59,"heal":48,"layer":1,"skill":15,"spawn":27,"status":49},
|
||||
"fx": {"displace":16,"garrison":1,"healField":1,"liberate":4,"redistribute":15,"reweave":1,"sandChains":2,"shield":1,"slash":17,"truesilver":1},
|
||||
"snaps": "0bfff00d 05c25821 afccda40 f76042f7 01a83e51 376ea44f e7830c09"
|
||||
"snaps": "ad09e96f 6bdb7b5b 6eeee792 1fcaf8dd 4f9ad0eb 979b5a35 added99f"
|
||||
},
|
||||
"bond-kazimierzShip-high": {
|
||||
"about": "卡西米尔 high: tier 2 layers 999; chess_char_2_18_b chess_char_3_12_b chess_char_5_08_b chess_char_4_19_b chess_char_1_19_b chess_char_2_12_b chess_char_3_17_b chess_char_5_10_b",
|
||||
@@ -553,17 +553,17 @@
|
||||
["p1",3,"op","chess_char_5_08_b",2,"uniequip_002_horn",605.79,1,7,10,69588,4063,1893,52,2],
|
||||
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,8,0,20635,0,0,16,2],
|
||||
["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,9,3,85930,1971,0,51,2],
|
||||
["p1",6,"op","chess_char_2_12_b",1,"uniequip_002_gravel",0,0,37,2,45298,4646,0,40,0],
|
||||
["p1",6,"op","chess_char_2_12_b",1,"uniequip_002_gravel",0,0,37,2,45298,4646,0,40,2],
|
||||
["p1",7,"op","chess_char_3_17_b",1,"uniequip_002_shotst",3441.6,1,4,7,84143,0,0,29,3],
|
||||
["p1",8,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,6,0,2897,0,0,10,2],
|
||||
["-","~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]
|
||||
],
|
||||
"enemies": {"enemy_10124_uashld_2":[2,2,0,4508,124416],"enemy_1043_zomsbr":[23,23,0,20,147057],"enemy_1061_zomshd":[5,5,0,4181,132516]},
|
||||
"hooks": [1,41,2585,237,237,430,545,3521,33,33,13,13,0,5133,998,10,30,0,1,46,0,0,5],
|
||||
"events": {"atk":237,"deploy":9,"die":33,"dmg":425,"fx":55,"skill":24,"spawn":40,"status":123},
|
||||
"hooks": [1,41,2585,237,237,430,545,3521,33,33,15,13,0,5133,998,10,30,0,1,46,0,0,5],
|
||||
"events": {"atk":237,"deploy":9,"die":33,"dmg":425,"fx":55,"skill":28,"spawn":40,"status":123},
|
||||
"fx": {"aoe":25,"displace":15,"dodge":5,"flame":2,"overload":2,"riposte":1,"shield":2,"volley":3},
|
||||
"snaps": "8c049284 94ec5278 5b651787 1fd27218 f0da3e8f e10280c8 9a24dc65 cc785d71 f60edecd"
|
||||
"snaps": "5a72bba9 0659d371 b8310d82 76b71b85 cf872b16 b13b69ba 59b7c64f 9c4213f3 8327e347"
|
||||
},
|
||||
"bond-kjeragShip-low": {
|
||||
"about": "谢拉格 low: tier 1 layers 1; chess_char_1_02_a chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_2_05_a chess_char_3_11_a chess_char_4_26_a chess_char_3_01_a",
|
||||
@@ -608,7 +608,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1000_gopro_2":[19,19,0,0,29070],"enemy_1325_cbgpro":[5,5,0,0,17100],"enemy_1325_cbgpro_2":[3,3,0,0,12420]},
|
||||
"hooks": [1,47,1315,63,63,120,120,0,27,35,3,3,0,5130,1,0,27,0,1,27,1,0,0],
|
||||
"events": {"atk":63,"deploy":17,"die":35,"dmg":120,"fx":50,"layer":1,"skill":6,"spawn":47,"status":2},
|
||||
"events": {"atk":63,"deploy":17,"die":35,"dmg":120,"fx":50,"layer":1,"skill":8,"spawn":47,"status":2},
|
||||
"fx": {"buff":1,"coin":12,"explode":8,"garrison":1,"garrisonGrant":1,"spGift":14,"strike":2,"summon":9,"volley":2},
|
||||
"snaps": "907cbf77 fce5049b cd63c811 19d26f73 4b87ef36"
|
||||
},
|
||||
@@ -876,7 +876,7 @@
|
||||
"snaps": "ae3e9ee4 e795ca48 af286313 5aa3428e 6e5316b8 959e25b3 4a8f8f76 e74c3a44 496ccf66 d9ba6888 55757f23 aa695997 2dac6dad 8ad5fbdb 83692eb8 8299c089"
|
||||
},
|
||||
"bond-preciShip-low": {
|
||||
"about": "精准 low: tier 1 layers 1; chess_char_4_11_a chess_char_4_12_a chess_char_4_04_a chess_char_4_07_a chess_char_4_09_a chess_char_1_09_a chess_char_1_17_a chess_char_4_10_a token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "精准 low: tier 1 layers 1; chess_char_4_11_a chess_char_4_04_a chess_char_4_07_a chess_char_4_09_a chess_char_1_09_a chess_char_1_17_a chess_char_4_10_a chess_char_4_12_a token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "normal",
|
||||
"stageId": "act1autochess_m01",
|
||||
@@ -884,39 +884,39 @@
|
||||
"round": 5,
|
||||
"seed": 2452926770,
|
||||
"waveId": "act1autochess_05",
|
||||
"time": 53.4,
|
||||
"ticks": 1602,
|
||||
"time": 54,
|
||||
"ticks": 1620,
|
||||
"reason": "cleared",
|
||||
"killed": 21,
|
||||
"total": 21,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 24,
|
||||
"rngDraws": 21,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":21,"total":21,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":73125,"bossDmg":0,"heal":0,"deaths":1,"layerGains":{"visiShip":4}}},
|
||||
"players": {"p1":{"killed":21,"total":21,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":73125,"bossDmg":0,"heal":0,"deaths":0,"layerGains":{"visiShip":4}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_4_11_a",1,null,2144,1,6,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_12_a",2,null,1754,1,7,2,10949,0,0,16,1],
|
||||
["p1",3,"op","chess_char_4_04_a",1,null,1760,1,8,7,14485,0,0,40,2],
|
||||
["p1",4,"op","chess_char_4_07_a",1,null,0,0,10,2,9734,1738,0,24,6],
|
||||
["p1",5,"op","chess_char_4_09_a",1,null,2137.6,1,11,8,26928,0,0,15,2],
|
||||
["p1",6,"op","chess_char_1_09_a",1,null,1770,1,4,0,0,0,0,0,0],
|
||||
["p1",7,"op","chess_char_1_17_a",1,null,1139,1,5,0,0,0,0,0,0],
|
||||
["p1",8,"op","chess_char_4_10_a",1,null,1288,1,9,2,11029,0,0,12,1],
|
||||
["p1",9,"token","token_10041_cathy_catsld",null,null,100,1,13,0,0,0,0,0,null],
|
||||
["p1",10,"token","token_10041_cathy_catsld",null,null,100,1,12,0,0,0,0,0,null],
|
||||
["p1",2,"op","chess_char_4_04_a",1,null,1760,1,7,4,7013,0,0,26,2],
|
||||
["p1",3,"op","chess_char_4_07_a",1,null,840,1,8,2,6582,898,0,21,4],
|
||||
["p1",4,"op","chess_char_4_09_a",1,null,2137.6,1,10,10,32312,0,0,17,2],
|
||||
["p1",5,"op","chess_char_1_09_a",1,null,1770,1,4,0,0,0,0,0,0],
|
||||
["p1",6,"op","chess_char_1_17_a",1,null,1139,1,5,0,0,0,0,0,0],
|
||||
["p1",7,"op","chess_char_4_10_a",1,null,1288,1,9,2,9397,0,0,11,1],
|
||||
["p1",8,"op","chess_char_4_12_a",2,null,1754,1,11,3,17821,0,0,19,1],
|
||||
["p1",9,"token","token_10041_cathy_catsld",null,null,100,1,12,0,0,0,0,0,null],
|
||||
["p1",10,"token","token_10041_cathy_catsld",null,null,100,1,13,0,0,0,0,0,null],
|
||||
["-","~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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,0,27000],"enemy_1251_lysyta":[15,15,0,24,37125],"enemy_1328_cbjedi":[1,1,0,1714,9000]},
|
||||
"hooks": [1,34,1602,113,113,162,157,0,22,22,12,9,0,6783,35,3,21,0,1,22,2,0,0],
|
||||
"events": {"atk":133,"deploy":10,"die":22,"dmg":157,"fx":65,"layer":2,"skill":21,"spawn":34,"status":48},
|
||||
"fx": {"charge":6,"dilemma":2,"displace":10,"garrison":2,"levitate":11,"pull":1,"shadowWeave":16,"shieldBreak":5,"slippery":1,"splash":3,"stealth":2,"tornado":1,"tornadoPulse":5},
|
||||
"snaps": "e57ceca7 ff94bb6d b8280d4d 705592d2 6381d0d2 8d1a1548"
|
||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,0,27000],"enemy_1251_lysyta":[15,15,0,0,37125],"enemy_1328_cbjedi":[1,1,0,898,9000]},
|
||||
"hooks": [1,34,1620,96,96,157,152,0,21,21,10,7,0,6646,33,1,21,0,1,21,2,0,0],
|
||||
"events": {"atk":119,"deploy":10,"die":21,"dmg":152,"fx":61,"layer":2,"skill":17,"spawn":34,"status":46},
|
||||
"fx": {"charge":4,"dilemma":2,"displace":14,"garrison":2,"levitate":11,"pull":1,"shadowWeave":11,"shieldBreak":5,"slippery":1,"splash":2,"stealth":2,"tornado":1,"tornadoPulse":5},
|
||||
"snaps": "c27c0600 1d169021 02c1eb1d 8ea4ebf1 57022adf 39e3da22"
|
||||
},
|
||||
"bond-preciShip-high": {
|
||||
"about": "精准 high: tier 2 layers 999; chess_char_4_11_b chess_char_4_12_b chess_char_4_09_b chess_char_1_09_b chess_char_1_17_b chess_char_2_01_b chess_char_4_10_b chess_char_4_13_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "精准 high: tier 2 layers 999; chess_char_4_11_b chess_char_4_09_b chess_char_1_09_b chess_char_1_17_b chess_char_2_01_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": true,
|
||||
"kind": "normal",
|
||||
"stageId": "act1autochess_m04",
|
||||
@@ -931,29 +931,29 @@
|
||||
"total": 24,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 19,
|
||||
"rngDraws": 21,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":144083,"bossDmg":0,"heal":0,"deaths":2,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":144293,"bossDmg":0,"heal":1227,"deaths":2,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_4_11_b",1,"uniequip_002_cathy",1261.53,1,8,0,0,2143,0,0,1],
|
||||
["p1",2,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,6,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",3992.8,1,9,0,17552,0,0,7,1],
|
||||
["p1",4,"op","chess_char_1_09_b",1,"uniequip_002_caper",3876.25,1,4,10,33806,0,0,13,1],
|
||||
["p1",5,"op","chess_char_1_17_b",1,"uniequip_002_indigo",1356,1,5,0,0,0,0,5,1],
|
||||
["p1",6,"op","chess_char_2_01_b",1,"uniequip_002_excu",0,0,11,6,38016,2146,0,6,1],
|
||||
["p1",7,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,7,5,52591,0,0,7,1],
|
||||
["p1",8,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,10,3,2118,1989,0,0,3],
|
||||
["p1",1,"op","chess_char_4_11_b",1,"uniequip_002_cathy",1459,1,8,0,0,1837,0,0,1],
|
||||
["p1",2,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",3992.8,1,6,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_1_09_b",1,"uniequip_002_caper",3876.25,1,4,12,50704,0,0,12,1],
|
||||
["p1",4,"op","chess_char_1_17_b",1,"uniequip_002_indigo",1356,1,5,0,0,0,0,8,1],
|
||||
["p1",5,"op","chess_char_2_01_b",1,"uniequip_002_excu",0,0,11,6,38016,2146,0,6,1],
|
||||
["p1",6,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,7,5,53097,0,0,7,1],
|
||||
["p1",7,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,9,0,948,1227,1227,2,1],
|
||||
["p1",8,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,10,1,1527,1989,0,0,1],
|
||||
["p1",9,"token","token_10041_cathy_catsld",null,null,100,1,12,0,0,0,0,0,null],
|
||||
["p1",10,"token","token_10041_cathy_catsld",null,null,100,1,13,0,0,0,0,0,null],
|
||||
["-","~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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1019_jshoot_2":[7,7,0,4441,21773],"enemy_1299_ymkilr_2":[3,3,0,1837,38016],"enemy_1422_lrsldr_2":[14,14,0,0,88704]},
|
||||
"hooks": [1,37,2071,84,84,151,151,0,26,26,9,8,0,4304,189,10,24,0,1,26,0,0,0],
|
||||
"events": {"atk":85,"deploy":10,"die":26,"dmg":151,"fx":17,"skill":17,"spawn":37,"status":13},
|
||||
"fx": {"crit":2,"dilemma":1,"frostNova":3,"levitate":7,"overclock":1,"slippery":1,"strike":2},
|
||||
"snaps": "88ecbc01 8a1ee02f cd042d3b 73363111 6ff4e2d8 de909066 a4a9c65d"
|
||||
"enemies": {"enemy_1019_jshoot_2":[7,7,0,3373,21773],"enemy_1299_ymkilr_2":[3,3,0,1837,38016],"enemy_1422_lrsldr_2":[14,14,0,0,88704]},
|
||||
"hooks": [1,37,2071,84,84,166,166,663,26,26,7,6,0,4290,103,10,24,0,1,26,0,0,0],
|
||||
"events": {"atk":84,"deploy":10,"die":26,"dmg":166,"fx":21,"skill":13,"spawn":37,"status":10},
|
||||
"fx": {"crit":2,"frostNova":1,"levitate":7,"overclock":1,"pull":1,"slippery":1,"strike":2,"tornado":1,"tornadoPulse":5},
|
||||
"snaps": "1c35197a b5a550c2 449f2d84 3e5d7032 3c466668 15903287 c2362cab"
|
||||
},
|
||||
"bond-raidShip-low": {
|
||||
"about": "突袭 low: tier 1 layers 1; chess_char_4_17_a chess_char_4_18_a chess_char_2_09_a chess_char_4_19_a chess_char_1_18_a chess_char_4_13_a chess_char_4_14_a chess_char_4_20_a",
|
||||
@@ -964,14 +964,14 @@
|
||||
"round": 5,
|
||||
"seed": 806507299,
|
||||
"waveId": "act1autochess_05",
|
||||
"time": 50.23,
|
||||
"ticks": 1507,
|
||||
"time": 53.47,
|
||||
"ticks": 1604,
|
||||
"reason": "cleared",
|
||||
"killed": 17,
|
||||
"total": 17,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 21,
|
||||
"rngDraws": 22,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {
|
||||
@@ -983,28 +983,28 @@
|
||||
"coins": 0,
|
||||
"dmg": 126144,
|
||||
"bossDmg": 0,
|
||||
"heal": 2669,
|
||||
"heal": 2419,
|
||||
"deaths": 0,
|
||||
"layerGains": {"preciShip":4,"raidShip":1}
|
||||
}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_4_17_a",1,null,2849,1,3,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_18_a",1,null,4587.2,1,5,1,10682,19,19,12,2],
|
||||
["p1",2,"op","chess_char_4_18_a",1,null,4085.2,1,5,2,12767,521,19,15,2],
|
||||
["p1",3,"op","chess_char_2_09_a",1,null,2047.24,1,17,3,21927,0,0,26,1],
|
||||
["p1",4,"op","chess_char_4_19_a",2,null,1195.44,1,9,2,23164,365,0,30,1],
|
||||
["p1",5,"op","chess_char_1_18_a",1,null,1706.55,1,32,7,45410,7067,2650,53,0],
|
||||
["p1",6,"op","chess_char_4_13_a",1,null,1587,1,4,0,7729,0,0,12,4],
|
||||
["p1",4,"op","chess_char_4_19_a",2,null,1101.88,1,9,2,24100,458,0,32,2],
|
||||
["p1",5,"op","chess_char_1_18_a",1,null,3087.71,1,14,5,40597,4537,2400,48,3],
|
||||
["p1",6,"op","chess_char_4_13_a",1,null,1587,1,4,0,8671,0,0,14,4],
|
||||
["p1",7,"op","chess_char_4_14_a",1,null,1276,1,6,0,0,0,0,0,0],
|
||||
["p1",8,"op","chess_char_4_20_a",2,null,1217,1,8,4,17232,0,0,16,0],
|
||||
["p1",8,"op","chess_char_4_20_a",2,null,1217,1,8,5,18081,0,0,17,0],
|
||||
["-","~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]
|
||||
],
|
||||
"enemies": {"enemy_1116_liprr":[11,11,0,350,69696],"enemy_1116_liprr_2":[4,4,0,757,34560],"enemy_1118_lidbox_2":[2,2,0,19,21888]},
|
||||
"hooks": [1,32,1507,195,195,229,226,85,17,22,8,11,0,4625,227,18,17,0,1,17,2,0,8],
|
||||
"events": {"atk":208,"deploy":13,"die":22,"dmg":211,"fx":42,"heal":54,"layer":2,"skill":15,"spawn":27,"status":65},
|
||||
"fx": {"aoe":5,"blink":5,"dodge":8,"flame":1,"frostNova":4,"garrison":2,"garrisonGrant":1,"liberate":2,"riposte":8,"rockslide":2,"shieldBreak":4},
|
||||
"snaps": "c2c19547 f7f121f8 342b4210 25ce41d3 2fc3e7fa"
|
||||
"enemies": {"enemy_1116_liprr":[11,11,0,443,69696],"enemy_1116_liprr_2":[4,4,0,757,34560],"enemy_1118_lidbox_2":[2,2,0,521,21888]},
|
||||
"hooks": [1,30,1604,201,201,235,230,80,17,20,12,11,0,4816,246,18,17,0,1,17,2,0,8],
|
||||
"events": {"atk":214,"deploy":11,"die":20,"dmg":215,"fx":40,"heal":49,"layer":2,"skill":25,"spawn":27,"status":63},
|
||||
"fx": {"aoe":3,"blink":3,"dodge":8,"flame":2,"frostNova":4,"garrison":2,"garrisonGrant":1,"liberate":3,"riposte":8,"rockslide":2,"shieldBreak":4},
|
||||
"snaps": "f6e5416c 58911567 9a06225b ca1af778 00aa1906 6c2de306"
|
||||
},
|
||||
"bond-raidShip-high": {
|
||||
"about": "突袭 high: tier 1 layers 999; chess_char_4_17_b chess_char_4_18_b chess_char_2_09_b chess_char_4_19_b chess_char_1_18_b chess_char_4_14_b chess_char_4_20_b chess_char_4_21_b",
|
||||
@@ -1015,8 +1015,8 @@
|
||||
"round": 10,
|
||||
"seed": 3301807152,
|
||||
"waveId": "act1autochess_h03",
|
||||
"time": 61.43,
|
||||
"ticks": 1843,
|
||||
"time": 61.5,
|
||||
"ticks": 1845,
|
||||
"reason": "cleared",
|
||||
"killed": 24,
|
||||
"total": 24,
|
||||
@@ -1025,26 +1025,26 @@
|
||||
"rngDraws": 0,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":508861,"bossDmg":0,"heal":4200,"deaths":0,"layerGains":{"preciShip":8}}
|
||||
"p1": {"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":508861,"bossDmg":0,"heal":4030,"deaths":0,"layerGains":{"preciShip":8}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,4,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,5,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",23974.92,1,46,4,98103,0,0,15,2],
|
||||
["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",23974.92,1,45,4,122836,1469,180,19,2],
|
||||
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,10,0,0,0,0,0,0],
|
||||
["p1",5,"op","chess_char_1_18_b",1,"uniequip_002_utage",18169.4,1,40,20,404123,21798,4200,60,0],
|
||||
["p1",6,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,0,0,0,0,0],
|
||||
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,0,6635,0,0,6,0],
|
||||
["p1",5,"op","chess_char_1_18_b",1,"uniequip_002_utage",18333.94,1,30,20,366686,16890,3850,55,8],
|
||||
["p1",6,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,7235,0,0,5,3],
|
||||
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,0,12105,0,0,13,0],
|
||||
["p1",8,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,9,0,0,0,0,0,0],
|
||||
["-","~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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1010_demon":[7,7,0,2359,130637],"enemy_1422_lrsldr_2":[14,14,0,0,191601],"enemy_1425_lrcmra_2":[3,3,0,2669,186624]},
|
||||
"hooks": [1,46,1843,96,96,97,107,75,24,35,2,10,0,1717,0,16,24,0,1,24,1,0,0],
|
||||
"events": {"atk":98,"deploy":19,"die":35,"dmg":101,"fx":23,"heal":60,"layer":1,"skill":3,"spawn":35,"status":27},
|
||||
"fx": {"aoe":10,"blink":11,"garrison":1,"garrisonGrant":1},
|
||||
"snaps": "2edb5682 ac7f24ca 074fdc6d 19a401d3 da20371c 72e9aa89 fabecb2b"
|
||||
"enemies": {"enemy_1010_demon":[7,7,0,2139,130637],"enemy_1422_lrsldr_2":[14,14,0,0,191601],"enemy_1425_lrcmra_2":[3,3,0,2803,186624]},
|
||||
"hooks": [1,45,1845,107,107,111,119,74,24,34,13,12,0,2316,0,16,24,0,1,24,1,0,0],
|
||||
"events": {"atk":109,"deploy":18,"die":34,"dmg":113,"fx":23,"heal":58,"layer":1,"skill":27,"spawn":35,"status":9},
|
||||
"fx": {"aoe":8,"blink":10,"explosion":3,"garrison":1,"garrisonGrant":1},
|
||||
"snaps": "1131be33 27e0b982 bfaeaa73 76dfd40e 79e7f64e f4827c69 435ba556"
|
||||
},
|
||||
"bond-sargonShip-low": {
|
||||
"about": "萨尔贡 low: tier 1 layers 1; chess_char_2_08_a chess_char_1_12_a chess_char_4_22_a chess_char_2_06_a chess_char_4_21_a chess_char_4_25_a chess_char_4_26_a chess_char_3_01_a",
|
||||
@@ -1150,7 +1150,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1010_demon":[3,3,0,7516,25920],"enemy_1010_demon_2":[1,1,0,6145,17280],"enemy_1071_dftman":[15,15,0,880,48384]},
|
||||
"hooks": [1,28,2257,235,235,291,315,0,24,24,4,4,0,4413,27,28,19,0,1,24,0,0,0],
|
||||
"events": {"atk":235,"deploy":9,"die":24,"dmg":315,"fx":48,"skill":8,"spawn":28,"status":53},
|
||||
"events": {"atk":235,"deploy":9,"die":24,"dmg":315,"fx":48,"skill":12,"spawn":28,"status":53},
|
||||
"fx": {"aoe":7,"coin":4,"crit":6,"dp":1,"strike":29,"summon":1},
|
||||
"snaps": "d4f0d969 5a4a40d8 3c484b76 b9530121 cbe87f7f d5bacb4f 94dfe238 7e0baa1c"
|
||||
},
|
||||
@@ -1188,7 +1188,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1158_divman":[20,20,0,6116,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1507,59720]},
|
||||
"hooks": [1,39,2230,313,313,469,473,2,29,29,6,5,0,3754,38,2,26,0,1,31,3,0,0],
|
||||
"events": {"atk":314,"deploy":11,"die":29,"dmg":473,"fx":29,"heal":2,"layer":3,"skill":11,"spawn":37,"status":75},
|
||||
"events": {"atk":314,"deploy":11,"die":29,"dmg":473,"fx":29,"heal":2,"layer":3,"skill":13,"spawn":37,"status":75},
|
||||
"fx": {"aoe":3,"bondProc":9,"crit":5,"dp":4,"garrison":3,"wolfShadow":2,"wolfShadowLost":3},
|
||||
"snaps": "d1ead0d3 cda7c00c ccc1aada c0883850 2ba76678 a36bad1d 085c7f88 f7baafc7"
|
||||
},
|
||||
@@ -1440,7 +1440,7 @@
|
||||
"snaps": "b3fec5d9 59ebb356 6d90ccd4 672b65be b58aec4b 60fa7afc 4d71ceab 1048946f 06425012 99d5d7f3 b20b2b93"
|
||||
},
|
||||
"bond-suntShip-low": {
|
||||
"about": "绝技 low: tier 1 layers 1; chess_char_1_02_b chess_char_4_17_a chess_char_4_18_a chess_char_4_12_a chess_char_4_16_a chess_char_1_01_b chess_char_4_13_a chess_char_4_14_a",
|
||||
"about": "绝技 low: tier 1 layers 1; chess_char_1_02_b chess_char_4_17_a chess_char_4_18_a chess_char_4_16_a chess_char_1_01_b chess_char_4_12_a chess_char_4_13_a chess_char_4_14_a",
|
||||
"fast": false,
|
||||
"kind": "normal",
|
||||
"stageId": "act2autochess_m03",
|
||||
@@ -1455,27 +1455,27 @@
|
||||
"total": 21,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 8,
|
||||
"rngDraws": 19,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":21,"total":21,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":92198,"bossDmg":0,"heal":396,"deaths":2,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":21,"total":21,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":93600,"bossDmg":0,"heal":468,"deaths":1,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_1_02_b",1,"uniequip_002_yak",3154,1,4,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_17_a",1,null,2818.99,1,5,2,1418,30,0,4,0],
|
||||
["p1",3,"op","chess_char_4_18_a",1,null,4587.2,1,8,3,8181,0,0,9,1],
|
||||
["p1",4,"op","chess_char_4_12_a",2,null,0,0,10,5,17855,2150,396,17,1],
|
||||
["p1",5,"op","chess_char_4_16_a",2,null,1246,1,33,0,6418,1246,0,26,0],
|
||||
["p1",6,"op","chess_char_1_01_b",1,"uniequip_002_inside",1421,1,6,7,23924,0,0,32,1],
|
||||
["p1",7,"op","chess_char_4_13_a",1,null,1587,1,7,2,6599,0,0,18,5],
|
||||
["p1",8,"op","chess_char_4_14_a",1,null,1276,1,9,2,27804,0,0,14,4],
|
||||
["p1",2,"op","chess_char_4_17_a",1,null,2698.96,1,5,4,8462,150,0,18,0],
|
||||
["p1",3,"op","chess_char_4_18_a",1,null,4587.2,1,8,7,16386,15,15,17,2],
|
||||
["p1",4,"op","chess_char_4_16_a",2,null,0,0,10,3,13468,1699,453,25,2],
|
||||
["p1",5,"op","chess_char_1_01_b",1,"uniequip_002_inside",1421,1,6,6,26692,0,0,42,2],
|
||||
["p1",6,"op","chess_char_4_12_a",2,null,1754,1,7,0,0,0,0,0,0],
|
||||
["p1",7,"op","chess_char_4_13_a",1,null,1587,1,9,1,11465,0,0,28,6],
|
||||
["p1",8,"op","chess_char_4_14_a",1,null,1276,1,11,0,17127,0,0,14,3],
|
||||
["-","~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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,1739,34560],"enemy_1251_lysyta":[15,15,0,121,47520],"enemy_1328_cbjedi":[1,1,0,1566,11520]},
|
||||
"hooks": [1,33,1604,143,143,216,211,276,23,23,12,13,14,6222,470,20,21,0,1,23,0,0,0],
|
||||
"events": {"atk":143,"deploy":9,"die":23,"dmg":208,"fx":57,"skill":24,"spawn":32,"status":42},
|
||||
"fx": {"displace":26,"explosion":4,"frostNova":5,"pull":1,"rockslide":1,"shieldBreak":8,"sp":2,"swordStorm":2,"thorns":2,"tornado":1,"tornadoPulse":5},
|
||||
"snaps": "8bc49724 100254d9 abd4d59e e5605c1e 302243e9 045c0db6"
|
||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,453,34560],"enemy_1251_lysyta":[15,15,0,229,47520],"enemy_1328_cbjedi":[1,1,0,1182,11520]},
|
||||
"hooks": [1,32,1604,172,172,234,225,9,23,22,15,14,20,5887,502,14,21,0,1,23,0,0,0],
|
||||
"events": {"atk":172,"deploy":8,"die":22,"dmg":219,"fx":50,"heal":2,"skill":31,"spawn":32,"status":98},
|
||||
"fx": {"block":4,"explosion":3,"frostNova":6,"rockslide":2,"shieldBreak":11,"swordRain":12,"swordStorm":2,"thorns":10},
|
||||
"snaps": "241cb7e7 200c57ef 07026ad0 06c49f0b 308a5472 31b270f1"
|
||||
},
|
||||
"bond-suntShip-high": {
|
||||
"about": "绝技 high: tier 2 layers 999; chess_char_1_02_b chess_char_1_04_b chess_char_4_19_a chess_char_1_01_b chess_char_1_03_b chess_char_1_06_b chess_char_4_20_a chess_char_4_21_a",
|
||||
@@ -1548,7 +1548,7 @@
|
||||
"snaps": "5fab4324 4504ae6f ce2c7b3f 273640e9 88622454"
|
||||
},
|
||||
"bond-swiftShip-high": {
|
||||
"about": "迅捷 high: tier 1 layers 999; chess_char_4_11_b chess_char_4_12_b chess_char_1_19_b chess_char_4_07_b chess_char_4_09_b chess_char_1_01_b chess_char_4_06_b chess_char_4_10_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "迅捷 high: tier 1 layers 999; chess_char_4_11_b chess_char_1_19_b chess_char_4_07_b chess_char_4_09_b chess_char_1_01_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": true,
|
||||
"kind": "normal",
|
||||
"stageId": "act2autochess_m01",
|
||||
@@ -1556,33 +1556,33 @@
|
||||
"round": 10,
|
||||
"seed": 1175070139,
|
||||
"waveId": "act1autochess_h03",
|
||||
"time": 102.8,
|
||||
"ticks": 3084,
|
||||
"time": 108.37,
|
||||
"ticks": 3251,
|
||||
"reason": "cleared",
|
||||
"killed": 24,
|
||||
"killed": 23,
|
||||
"total": 24,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 222,
|
||||
"rngDraws": 271,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":235584,"bossDmg":0,"heal":815,"deaths":1,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":23,"total":24,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":219573,"bossDmg":0,"heal":742,"deaths":4,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,4,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_12_b",2,"uniequip_002_glady",1351.48,1,6,3,26379,1695,815,30,4],
|
||||
["p1",3,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,7,1,13581,1971,0,41,3],
|
||||
["p1",4,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,5,0,0,0,0,0,0],
|
||||
["p1",5,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,8,7,94293,0,0,37,5],
|
||||
["p1",6,"op","chess_char_1_01_b",1,"uniequip_002_inside",1421,1,1,2,15429,0,0,67,4],
|
||||
["p1",7,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,9,50453,0,0,102,3],
|
||||
["p1",8,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,3,2,35449,0,0,21,2],
|
||||
["p1",1,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,4,0,0,4098,0,0,1],
|
||||
["p1",2,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,6,1,4054,1971,0,20,1],
|
||||
["p1",3,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,7,0,1332,2383,0,10,10],
|
||||
["p1",4,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,5,5,65655,0,0,24,3],
|
||||
["p1",5,"op","chess_char_1_01_b",1,"uniequip_002_inside",1421,1,1,2,17948,0,0,76,5],
|
||||
["p1",6,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,10,67651,0,0,110,3],
|
||||
["p1",7,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,3,2,36325,0,0,22,2],
|
||||
["p1",8,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,8,3,26608,2974,742,16,2],
|
||||
["p1",9,"token","token_10041_cathy_catsld",null,null,100,1,9,0,0,0,0,0,null],
|
||||
["p1",10,"token","token_10041_cathy_catsld",null,null,100,1,10,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1010_demon":[7,7,0,0,60480],"enemy_1422_lrsldr_2":[14,14,0,0,88704],"enemy_1425_lrcmra_2":[3,3,0,3666,86400]},
|
||||
"hooks": [1,34,3084,303,303,546,546,438,25,25,21,18,67,4354,126,3,24,0,1,25,0,0,0],
|
||||
"events": {"atk":351,"deploy":10,"die":25,"dmg":546,"fx":142,"skill":39,"spawn":34,"status":201},
|
||||
"fx": {"crit":45,"dilemma":5,"displace":17,"garrisonGrant":1,"levitate":20,"pull":4,"quadShot":2,"slippery":2,"sp":17,"splash":5,"tornado":4,"tornadoPulse":20},
|
||||
"snaps": "9b63f336 925188d7 6ea278e3 95d08962 b1f0ec9e 54a4f92e 6a3a048f ec134a87 227825d7 a8d37494 90671c04"
|
||||
"enemies": {"enemy_1010_demon":[7,7,0,1205,60480],"enemy_1422_lrsldr_2":[14,14,0,74,88704],"enemy_1425_lrcmra_2":[3,2,1,10147,70389]},
|
||||
"hooks": [1,34,3251,301,301,557,557,400,27,28,27,25,76,3567,117,10,24,1,1,27,0,0,0],
|
||||
"events": {"atk":349,"deploy":10,"die":27,"dmg":557,"fx":146,"leak":1,"skill":52,"spawn":34,"status":196},
|
||||
"fx": {"charge":10,"crit":53,"dilemma":3,"displace":16,"garrisonGrant":1,"levitate":21,"overclock":1,"pull":1,"quadShot":2,"slippery":2,"sp":23,"splash":7,"tornado":1,"tornadoPulse":5},
|
||||
"snaps": "52481b26 a970d314 2696635b 9c15cc8e 501817d0 a8faf661 7e05b7cf ffceee93 c505d863 f99ccd20 1c4895d8"
|
||||
},
|
||||
"bond-victoriaShip-low": {
|
||||
"about": "维多利亚 low: tier 1 layers 1; chess_char_3_06_a chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_3_05_a chess_char_1_06_a chess_char_2_05_a chess_char_2_10_a",
|
||||
@@ -1634,7 +1634,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1116_liprr":[11,11,0,305,69696],"enemy_1116_liprr_2":[4,4,0,429,34560],"enemy_1118_lidbox_2":[2,2,0,9762,21888]},
|
||||
"hooks": [1,44,1985,210,210,367,394,52,18,34,6,3,0,6985,15,16,17,0,1,18,0,1,0],
|
||||
"events": {"atk":210,"deploy":25,"die":34,"dmg":394,"fx":114,"heal":42,"skill":9,"spawn":44,"status":48},
|
||||
"events": {"atk":210,"deploy":25,"die":34,"dmg":394,"fx":114,"heal":42,"skill":11,"spawn":44,"status":48},
|
||||
"fx": {"aoe":10,"burst":1,"coin":20,"counter":5,"explode":16,"liberate":3,"overload":1,"strike":40,"summon":17,"taunt":1},
|
||||
"snaps": "3a6154e2 bbed839c a9f7e3a7 0a1cc28d bec91f1f 6279cb1e 7645ebd2"
|
||||
},
|
||||
@@ -1737,8 +1737,8 @@
|
||||
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,3,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,4,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_3_03_b",1,"uniequip_002_swire",1898,1,5,6,24070,0,0,23,1],
|
||||
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",157.8,1,8,4,31620,1960,0,27,3],
|
||||
["p1",5,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,31,4,46126,3321,83,40,0],
|
||||
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",157.8,1,8,4,31547,1960,0,27,3],
|
||||
["p1",5,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,31,4,46199,3321,83,40,4],
|
||||
["p1",6,"op","chess_char_2_02_b",1,"uniequip_002_silent",1614,1,6,0,0,1084,3098,8,2],
|
||||
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,4,35502,1704,0,22,1],
|
||||
["p1",8,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,10,0,0,2478,2168,14,1],
|
||||
@@ -1747,10 +1747,10 @@
|
||||
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1234_dsubrl_2":[2,2,0,9847,32256],"enemy_1433_dsbasi_2":[16,16,0,700,105062]},
|
||||
"hooks": [1,32,2169,163,163,249,300,41,22,23,8,9,0,2963,17,18,18,0,1,22,0,0,12],
|
||||
"events": {"atk":167,"deploy":12,"die":23,"dmg":300,"fx":94,"heal":24,"skill":15,"spawn":29,"status":31},
|
||||
"hooks": [1,32,2169,163,163,249,300,41,22,23,12,11,0,2963,17,18,18,0,1,22,0,0,12],
|
||||
"events": {"atk":167,"deploy":12,"die":23,"dmg":300,"fx":94,"heal":24,"skill":25,"spawn":29,"status":31},
|
||||
"fx": {"dodge":12,"featherArrow":1,"flame":3,"garrisonGrant":1,"healField":1,"pulse":43,"riposte":10,"summon":2,"swordRain":17,"swordStorm":4},
|
||||
"snaps": "d4da3ac7 da4fa3ba f837253c 7102ead4 bb579ed6 fb16fd1f a232d050 343c58c6"
|
||||
"snaps": "849e61b3 b054e595 7736283f c3ff5997 f8df75cd d3a1062a ce887350 42803555"
|
||||
},
|
||||
"bond-yanShip-low": {
|
||||
"about": "炎 low: tier 1 layers 1; chess_char_3_16_a chess_char_3_03_a chess_char_3_18_a chess_char_1_03_a chess_char_2_04_a chess_char_3_14_a chess_char_3_17_a chess_char_3_19_a token_10028_vigil_wolf",
|
||||
@@ -1839,7 +1839,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1019_jshoot_2":[7,7,0,1075,47029],"enemy_1381_winman":[9,9,0,0,129890],"enemy_1389_winbab_2":[5,5,0,5215,87091]},
|
||||
"hooks": [1,46,2270,120,120,188,222,1034,22,30,11,9,5,5355,1,4,21,0,1,23,0,0,0],
|
||||
"events": {"atk":120,"deploy":22,"die":30,"dmg":222,"fx":44,"heal":26,"skill":20,"spawn":46,"status":2},
|
||||
"events": {"atk":120,"deploy":22,"die":30,"dmg":222,"fx":44,"heal":26,"skill":24,"spawn":46,"status":2},
|
||||
"fx": {"coin":14,"downed":1,"explode":8,"splash":5,"summon":11,"yanyouFlame":3,"yanyouSummon":2},
|
||||
"snaps": "b2b94c31 1e5707be a1b26ab3 10db4b56 bcd4d5e3 2c5a291d 646238a4 8471a2e0"
|
||||
}
|
||||
|
||||
@@ -2095,7 +2095,7 @@
|
||||
"enemy_1200_msfjin": [11,8,3,0,246]
|
||||
},
|
||||
"hooks": [1,314,6144,1470,1470,1969,2082,0,212,305,25,30,0,33234,6218,91,290,93,1,212,0,0,11],
|
||||
"events": {"atk":1512,"deploy":22,"die":212,"dmg":2076,"fx":273,"leak":93,"skill":48,"spawn":304,"status":488},
|
||||
"events": {"atk":1512,"deploy":22,"die":212,"dmg":2076,"fx":273,"leak":93,"skill":64,"spawn":304,"status":488},
|
||||
"fx": {"aoe":56,"displace":1,"dodge":22,"dp":63,"slash":52,"summon":79},
|
||||
"snaps": "cf051c06 587dcba3 94ed6adb bba4fd81 815661ad cbf17744 a8d0c19b 32b88916 1e8bde83 51a1d669 b023b185 df725cf4 bf4681aa 2c372e04 1a001766 3d81d3d6 7f8effe5 11826d80 b4fb05d0 0eed6d6c 65f6503d"
|
||||
},
|
||||
@@ -2134,7 +2134,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1019_jshoot_2":[57,0,57,29550,4113],"enemy_1299_ymkilr_2":[27,0,27,14576,26756],"enemy_1422_lrsldr_2":[39,0,39,4739,45577]},
|
||||
"hooks": [1,157,6557,277,277,283,291,7,33,156,4,24,0,6272,192,29,123,123,1,33,0,0,0],
|
||||
"events": {"atk":278,"deploy":34,"die":33,"dmg":269,"fx":19,"heal":7,"leak":123,"skill":8,"spawn":135,"status":92},
|
||||
"events": {"atk":278,"deploy":34,"die":33,"dmg":269,"fx":19,"heal":7,"leak":123,"skill":39,"spawn":135,"status":92},
|
||||
"fx": {"aoe":2,"displace":15,"spGain":2},
|
||||
"snaps": "21be44ba 23e43266 29788d55 2f1037e9 363ad836 051606c5 26acdfeb 8dc54607 7a7c6a86 7ebe59b3 d1661fdb 203d434c 2e82423b f7e9ffc1 7a1afa0e 8998df31 0ca38cb6 f3ac5677 87bcc7cf 0f5f3879 0e082cac 62241cef"
|
||||
}
|
||||
|
||||
+313
-246
@@ -126,7 +126,7 @@
|
||||
"snaps": "dc0d56b5 5ea2fc96 22a7c614 a844181b da3c3f7d 949d5dbc f7646a53 01f0d227 f3885c32 030b669b 1fb58b64 44930d09 d25240d2 f8eafa88 14aa67a5 9dfef3d2 753642cc"
|
||||
},
|
||||
"boss-boss_2-pair": {
|
||||
"about": "假想敌:铳 pair pool 3000000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_12_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "假想敌:铳 pair pool 3000000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "boss",
|
||||
"stageId": "act2autochess_m04",
|
||||
@@ -137,45 +137,78 @@
|
||||
"time": 200,
|
||||
"ticks": 6000,
|
||||
"reason": "forced",
|
||||
"killed": 7,
|
||||
"killed": 8,
|
||||
"total": 8,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": 2362809,
|
||||
"rngDraws": 412,
|
||||
"bossHpLeft": 2350858,
|
||||
"rngDraws": 752,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":595986,"bossDmg":293941,"heal":23195,"deaths":1,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":813515,"bossDmg":339023,"heal":3410,"deaths":3,"layerGains":{}}
|
||||
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":687741,"bossDmg":360696,"heal":26057,"deaths":0,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":822679,"bossDmg":285212,"heal":6813,"deaths":2,"layerGains":{}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,1,211687,1042,4,71,3],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,5584,15859,339,52,1],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,30371,2465,513,60,18],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,3270,0,0,32,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,76910,0,1721,39,8],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,73126,0,0,53,5],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,1,27999,2122,811,52,2],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,41,0,125628,1611,0,40,14],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,19758,25,3],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,41410,1483,49,37,2],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,10,0,0,4718,933,0,2],
|
||||
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,12,0,45745,0,0,58,4],
|
||||
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,51,1,265397,879,451,286,8],
|
||||
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,14,0,2138,2476,93,10,6],
|
||||
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,50,0,89813,6040,587,178,0],
|
||||
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1749.68,1,16,0,79565,1028,0,70,5],
|
||||
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,1672,0,0,16,1],
|
||||
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,1,90491,0,0,66,3],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,18,0,106763,1346,1346,159,44],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,20,3,131930,0,0,65,15],
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,181915,1042,0,71,3],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,11,0,24014,19274,205,73,1],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,32112,1028,124,26,17],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,5410,0,0,33,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,109631,0,7204,57,10],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,87026,0,0,44,4],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,0,19587,0,0,38,1],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,19,3,195138,1308,1308,70,23],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,17167,27,3],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,32909,1393,49,35,3],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,48,0,35,3833,460,1,2],
|
||||
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,44,1,254443,511,511,218,8],
|
||||
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2144.97,1,8,0,2891,1249,1011,21,7],
|
||||
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,47,2,117728,5484,1501,158,12],
|
||||
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,10,0,57785,0,0,54,3],
|
||||
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,1,201569,0,0,183,3],
|
||||
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,0,0,0,0,0],
|
||||
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,18,0,49124,3331,3331,46,4],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,41,1,58765,1989,0,93,25],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,80338,0,0,49,15],
|
||||
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
|
||||
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1005_yokai_2":[6,6,0,3827,62190],"enemy_1430_lrrook":[2,1,1,0,714488],"enemy_9017_achunt":[2,0,0,37242,637191]},
|
||||
"hooks": [1,49,6000,1371,1382,2556,2589,3595,11,27,145,150,22,41877,1544,0,10,1,1,12,0,10,1],
|
||||
"events": {"atk":1420,"deploy":39,"die":26,"dmg":2584,"fx":535,"heal":58,"leak":1,"skill":285,"spawn":32,"status":544},
|
||||
"fx": {"aoe":36,"appear":2,"blink":15,"burst":10,"catShield":1,"charge":6,"counter":22,"crit":10,"dilemma":5,"disappear":2,"dodge":1,"explosion":15,"frostNova":44,"iceSpike":41,"overclock":2,"pull":4,"ripple":16,"sentry":6,"sermon":1,"shadowWeave":10,"shield":6,"slippery":3,"soul":3,"sp":10,"stealth":8,"swordRain":87,"swordStorm":11,"tornado":4,"tornadoPulse":20,"weightless":5,"zone":129},
|
||||
"snaps": "b55c9e85 7a53e855 bd415e40 c6d98e32 079e14b9 388a2ed2 1a3c6a26 858ad1f9 3ec7c9ee 77777386 0b9072d2 2975a009 e34e5cff b1c370dc 67cec109 fefbe0f8 43db203a 256d921c 02e85967 13b012d9"
|
||||
"enemies": {"enemy_1005_yokai_2":[6,6,0,2907,62190],"enemy_1430_lrrook":[2,2,0,0,802449],"enemy_9017_achunt":[2,0,0,37534,649142]},
|
||||
"hooks": [1,46,6000,1326,1334,3080,3125,4772,12,22,145,142,30,41390,1023,0,10,0,1,12,0,12,1],
|
||||
"events": {"atk":1360,"deploy":36,"die":22,"dmg":3119,"fx":674,"heal":100,"skill":287,"spawn":32,"status":493},
|
||||
"fx": {
|
||||
"aoe": 43,
|
||||
"appear": 2,
|
||||
"blink": 12,
|
||||
"burst": 12,
|
||||
"catShield": 1,
|
||||
"charge": 7,
|
||||
"counter": 30,
|
||||
"crit": 100,
|
||||
"dilemma": 3,
|
||||
"disappear": 2,
|
||||
"dodge": 1,
|
||||
"explosion": 15,
|
||||
"frostNova": 25,
|
||||
"garrisonGrant": 1,
|
||||
"iceSpike": 33,
|
||||
"overclock": 2,
|
||||
"pull": 4,
|
||||
"quadShot": 2,
|
||||
"ripple": 13,
|
||||
"sentry": 3,
|
||||
"sermon": 1,
|
||||
"shadowWeave": 8,
|
||||
"shield": 7,
|
||||
"soul": 5,
|
||||
"sp": 9,
|
||||
"stealth": 8,
|
||||
"swordRain": 106,
|
||||
"swordStorm": 12,
|
||||
"tornado": 4,
|
||||
"tornadoPulse": 20,
|
||||
"weightless": 4,
|
||||
"zone": 179
|
||||
},
|
||||
"snaps": "3cd698b1 c4c10bef daadee84 5c3c1c12 96ba3e74 96f0d391 80d19d1f 30a2db6e 6243ea15 3f0082c4 c13bd213 98f0e36a 2c94c94c 4baf1af3 acdcb24d c81946fb e1f31d51 fcc2b87d bf80310a 917c2427"
|
||||
},
|
||||
"boss-boss_2-solo": {
|
||||
"about": "假想敌:铳 solo pool 750000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
|
||||
@@ -210,7 +243,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,9852,107495],"enemy_1430_lrrook":[2,0,2,0,58068],"enemy_9017_achunt":[1,0,0,20757,137787]},
|
||||
"hooks": [1,21,6000,385,385,1021,1424,4322,8,10,25,24,0,11667,66,5,8,2,1,12,0,3,1],
|
||||
"events": {"atk":400,"deploy":13,"die":8,"dmg":1420,"fx":263,"heal":39,"leak":2,"skill":48,"spawn":18,"status":410},
|
||||
"events": {"atk":400,"deploy":13,"die":8,"dmg":1420,"fx":263,"heal":39,"leak":2,"skill":52,"spawn":18,"status":410},
|
||||
"fx": {"bandage":2,"block":3,"burst":3,"charge":7,"dodge":1,"featherArrow":1,"flame":5,"garrisonGrant":1,"healField":1,"jungleSoul":3,"mote":102,"redistribute":45,"reweave":3,"riposte":1,"rockslide":2,"sandChains":3,"sandChainsCharged":2,"shieldBreak":9,"slash":55,"spGift":5,"thorns":6,"truesilver":3},
|
||||
"snaps": "c067d0c5 9f4c5a58 85fccb2d c89eed8f 09312c88 452b8138 ee2ad798 da21566b cb07f503 e1edca84 9e18d3aa dbfd29d7 66bcd79a 979872f8 086680fd 722ac503 c2d3bae4 480a95f5 140b0229 df277c2a"
|
||||
},
|
||||
@@ -335,7 +368,7 @@
|
||||
"snaps": "165e5292 c4adc9b7 d9efbe56 b04b7078 02908f60 4fd4ad05 065d2593 63d56b7a a53e0b0e e1fd3f2a b6534bb2 cac54dd4 80457fc1 f44190f2 fba20cd8 4fb50167 41e708fa"
|
||||
},
|
||||
"boss-boss_4-pair": {
|
||||
"about": "盐风主教昆图斯 pair pool 4200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_12_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "盐风主教昆图斯 pair pool 4200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "boss",
|
||||
"stageId": "act2autochess_m02",
|
||||
@@ -346,83 +379,81 @@
|
||||
"time": 200,
|
||||
"ticks": 6000,
|
||||
"reason": "forced",
|
||||
"killed": 12,
|
||||
"killed": 10,
|
||||
"total": 14,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": 2972244,
|
||||
"rngDraws": 460,
|
||||
"bossHpLeft": 3202998,
|
||||
"rngDraws": 670,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":12,"total":14,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1255814,"bossDmg":425605,"heal":54784,"deaths":15,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1012343,"bossDmg":802151,"heal":6103,"deaths":12,"layerGains":{}}
|
||||
"p1": {"killed":10,"total":14,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":929216,"bossDmg":303862,"heal":30593,"deaths":19,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1007145,"bossDmg":693141,"heal":3732,"deaths":15,"layerGains":{}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,47,2,133309,8083,269,20,1],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,50,1,18681,9522,580,15,2],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,54,0,33839,12822,1552,24,7],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,51,0,37009,13601,1966,34,2],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",804.59,1,55,3,316362,3438,10048,106,14],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",269.13,1,56,0,121735,10803,421,39,4],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1197.87,1,19,0,155484,4336,225,209,6],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1147.01,1,57,2,294282,2529,454,59,20],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1047.42,1,7,0,0,4920,38994,66,4],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1263.87,1,15,0,145114,4336,275,71,3],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,46,0,0,6742,0,0,1],
|
||||
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,45,0,25731,7033,2569,10,1],
|
||||
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3402.43,1,26,0,411317,2671,233,308,9],
|
||||
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,44,0,14571,5178,97,17,5],
|
||||
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",915.83,1,27,1,165010,13734,586,194,0],
|
||||
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,52,1,138696,7139,369,47,5],
|
||||
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,53,0,12367,3470,348,16,1],
|
||||
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1159.66,1,12,1,85418,4336,467,68,4],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,20,1,113586,5757,1001,153,39],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1194.87,1,58,0,45645,2490,433,14,9],
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,53,2,117363,8141,327,18,1],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,51,0,17733,11157,815,15,2],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,62,0,55117,10500,760,8,10],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",752.57,1,54,0,9532,18845,873,11,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",804.59,1,67,1,323528,4299,3100,82,14],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",270.47,1,58,1,85423,10413,575,38,4],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,19,0,97856,2109,24,118,3],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,3,3,121166,1980,369,42,14],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,55,0,0,4347,23631,39,3],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1265.2,1,61,2,101498,2701,120,49,3],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,47,0,0,6984,241,0,1],
|
||||
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",1587.75,1,49,1,187019,4777,120,200,7],
|
||||
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",1820.42,1,64,0,23276,5661,228,32,7],
|
||||
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1220.73,1,63,0,110018,6776,140,176,5],
|
||||
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1413.53,1,56,0,242751,5255,264,54,5],
|
||||
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,57,0,179403,3739,617,159,3],
|
||||
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1983,1,65,0,0,3629,120,0,0],
|
||||
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2100.38,1,60,0,64416,3658,1000,74,4],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1680.9,1,59,0,54090,2834,537,54,16],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1194.87,1,66,0,146172,2521,464,69,14],
|
||||
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
|
||||
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1010_demon":[4,4,0,9842,200612],"enemy_1438_dspred":[10,8,2,25539,839788],"enemy_1521_dslily":[1,0,0,89761,1227756]},
|
||||
"hooks": [1,56,6000,1367,1479,2977,3105,7373,40,43,137,136,8,38476,1606,17,15,2,1,42,0,37,7],
|
||||
"events": {"atk":1587,"deploy":41,"die":41,"dmg":3100,"fx":759,"heal":246,"leak":2,"skill":272,"spawn":37,"status":934},
|
||||
"enemies": {"enemy_1010_demon":[4,4,0,3991,200612],"enemy_1438_dspred":[10,6,4,14373,738747],"enemy_1521_dslily":[1,0,0,94162,997002]},
|
||||
"hooks": [1,65,6000,1201,1270,2930,3054,5119,44,50,117,115,15,31431,1146,14,15,4,1,46,0,30,4],
|
||||
"events": {"atk":1435,"deploy":50,"die":46,"dmg":3045,"fx":727,"heal":166,"leak":4,"skill":232,"spawn":37,"status":796},
|
||||
"fx": {
|
||||
"aoe": 34,
|
||||
"appear": 12,
|
||||
"aoe": 28,
|
||||
"appear": 8,
|
||||
"blink": 2,
|
||||
"burst": 37,
|
||||
"charge": 5,
|
||||
"counter": 8,
|
||||
"crit": 10,
|
||||
"burst": 30,
|
||||
"charge": 7,
|
||||
"counter": 15,
|
||||
"crit": 74,
|
||||
"dilemma": 5,
|
||||
"disappear": 12,
|
||||
"displace": 24,
|
||||
"dodge": 7,
|
||||
"explosion": 9,
|
||||
"frostNova": 39,
|
||||
"disappear": 8,
|
||||
"displace": 28,
|
||||
"dodge": 4,
|
||||
"explosion": 14,
|
||||
"frostNova": 16,
|
||||
"grow": 1,
|
||||
"iceSpike": 163,
|
||||
"levitate": 2,
|
||||
"iceSpike": 88,
|
||||
"overclock": 1,
|
||||
"pull": 1,
|
||||
"ripple": 22,
|
||||
"pull": 4,
|
||||
"quadShot": 3,
|
||||
"ripple": 17,
|
||||
"rockfall": 18,
|
||||
"sentry": 1,
|
||||
"sentry": 2,
|
||||
"sermon": 2,
|
||||
"shadowWeave": 1,
|
||||
"shield": 3,
|
||||
"slippery": 4,
|
||||
"soul": 9,
|
||||
"sp": 8,
|
||||
"splash": 1,
|
||||
"stealth": 9,
|
||||
"swordRain": 29,
|
||||
"swordStorm": 3,
|
||||
"shadowWeave": 5,
|
||||
"shield": 5,
|
||||
"soul": 5,
|
||||
"sp": 11,
|
||||
"stealth": 7,
|
||||
"swordRain": 27,
|
||||
"swordStorm": 5,
|
||||
"tentacle": 6,
|
||||
"tide": 5,
|
||||
"tornado": 1,
|
||||
"tornadoPulse": 5,
|
||||
"tornado": 4,
|
||||
"tornadoPulse": 20,
|
||||
"weightless": 4,
|
||||
"zone": 256
|
||||
"zone": 248
|
||||
},
|
||||
"snaps": "c1c7217a 86efd97d 51b1b9fa ce151b29 6599ccfa 8cf091f9 637a0e81 2fabff0e 83588130 dd2824f5 bad2b5b0 fcf15857 0c842834 9ab9e7fb cc4469ab 5bab359d ccfc5635 30457b93 119c5f24 0aabb086"
|
||||
"snaps": "d5cfa048 05581e84 c8799f99 16719fc9 baac34eb 215b818c 39438feb 8ce78fdb 57634639 a6449f74 25f63a7a e196b737 ddc7d8ac 4a1bf12f 4c54ac5b 953c2875 7a3caa77 6805c13f 133ed228 6d33c7b6"
|
||||
},
|
||||
"boss-boss_4-solo": {
|
||||
"about": "盐风主教昆图斯 solo pool 1050000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
|
||||
@@ -459,7 +490,7 @@
|
||||
],
|
||||
"enemies": {"enemy_1010_demon_2":[2,2,0,7219,107495],"enemy_1438_dspred":[5,2,3,27889,136924],"enemy_1521_dslily":[1,0,0,68800,413498]},
|
||||
"hooks": [1,23,6000,568,568,962,1070,7648,8,11,25,24,0,13395,133,10,8,3,1,8,0,31,0],
|
||||
"events": {"atk":763,"deploy":13,"die":8,"dmg":1052,"fx":336,"heal":102,"leak":3,"skill":49,"spawn":20,"status":523},
|
||||
"events": {"atk":763,"deploy":13,"die":8,"dmg":1052,"fx":336,"heal":102,"leak":3,"skill":51,"spawn":20,"status":523},
|
||||
"fx": {"bandage":1,"block":18,"burst":31,"camouflage":1,"featherArrow":5,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":68,"redistribute":45,"reweave":3,"rockfall":18,"rockslide":4,"sandChains":6,"shieldBreak":18,"slash":46,"spGift":3,"tentacle":6,"thorns":50,"tide":5,"truesilver":3},
|
||||
"snaps": "889b80ca 2554f68d 135727ee c74793bb 39ae3d9e 2f63625f 35d2d1d0 ab151905 58401106 8d34f3b8 1bed3837 592255cb 58e436d2 7cfc39b2 dd3f9d1c 6d83e822 e5e306e4 07c6dca8 5430994c 32254003"
|
||||
},
|
||||
@@ -608,7 +639,7 @@
|
||||
"snaps": "1b2c287a 675f6516 fe7fec9f 7befc28b 5055ef2d f5a1b7d4 597e1735 157dd534 488e94fe b43c89ae cc11a2ec b2d6ae70 81dc7d36"
|
||||
},
|
||||
"boss-boss_6-pair": {
|
||||
"about": "阿利斯泰尔,帝国余晖 pair pool 3980000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_12_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "阿利斯泰尔,帝国余晖 pair pool 3980000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "boss",
|
||||
"stageId": "act1autochess_m07",
|
||||
@@ -622,49 +653,84 @@
|
||||
"killed": 4,
|
||||
"total": 6,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": 2744355,
|
||||
"rngDraws": 136,
|
||||
"bossHpLeft": 2704841,
|
||||
"rngDraws": 598,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":957592,"bossDmg":517994,"heal":5866,"deaths":6,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":855506,"bossDmg":717652,"heal":1349,"deaths":13,"layerGains":{}}
|
||||
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1067324,"bossDmg":606787,"heal":7410,"deaths":3,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1192744,"bossDmg":668371,"heal":775,"deaths":8,"layerGains":{}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,2,128856,0,0,35,2],
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,1,137921,0,0,31,2],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,1,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,5,0,51547,0,0,0,15],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,0,15412,4549,4549,16,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,2,335190,0,1317,71,12],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,206485,0,0,75,6],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,40,0,19031,3258,0,21,1],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,41,0,146104,3222,0,50,16],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,42,0,1934,3220,0,0,0],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,53033,0,0,40,2],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,38,0,0,12227,333,0,1],
|
||||
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,2,0,0,0,0,0,0],
|
||||
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,46,0,137499,9146,872,177,6],
|
||||
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,10,0,0,0,0,0,0],
|
||||
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",527.19,1,53,0,51762,8463,144,167,0],
|
||||
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,49,0,170880,5555,0,71,4],
|
||||
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,0,0,0,0,0],
|
||||
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,288314,0,0,93,4],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,50,0,126161,3978,0,113,29],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,51,0,80890,4878,0,27,5],
|
||||
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null],
|
||||
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null]
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,5,1,42476,0,0,0,14],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,1,25797,1781,1781,17,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,310098,0,3003,72,14],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,209427,0,0,73,6],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,47,0,31874,1629,0,38,2],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,48,0,251573,1611,0,86,24],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,44,0,1934,3190,2627,3,1],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,56225,0,0,43,2],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,42,0,0,12187,268,0,1],
|
||||
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",2016.1,1,50,0,132143,10153,457,228,6],
|
||||
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,6,0,0,0,0,0,0],
|
||||
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,45,0,42786,8389,49,192,4],
|
||||
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,242636,0,0,117,6],
|
||||
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,487364,0,0,140,2],
|
||||
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,0,0,0,0,0],
|
||||
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,16,0,67574,0,0,168,6],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,46,0,134081,1989,0,125,31],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,49,0,86161,3252,0,33,6],
|
||||
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
|
||||
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {
|
||||
"enemy_10028_vtswd": [4,4,0,0,0],
|
||||
"enemy_10029_vtshld": [2,2,0,0,0],
|
||||
"enemy_10030_vtwand": [2,2,0,0,0],
|
||||
"enemy_10085_hllevi_2": [2,0,2,0,376840],
|
||||
"enemy_1010_demon": [4,4,0,16776,200612],
|
||||
"enemy_9032_aclionk": [1,0,0,41719,1235645]
|
||||
"enemy_10030_vtwand": [1,1,0,0,0],
|
||||
"enemy_10085_hllevi_2": [2,0,2,0,784297],
|
||||
"enemy_1010_demon": [4,4,0,13968,200612],
|
||||
"enemy_9032_aclionk": [1,0,0,30213,1275159]
|
||||
},
|
||||
"hooks": [1,51,6000,971,978,1548,1639,1575,31,35,104,105,0,31121,1258,4,15,2,1,27,0,8,1],
|
||||
"events": {"atk":1064,"deploy":36,"die":33,"dmg":1624,"fx":475,"heal":8,"leak":2,"skill":205,"spawn":37,"status":769},
|
||||
"fx": {"aoe":39,"appear":4,"blink":2,"burst":8,"crit":1,"dilemma":4,"disappear":4,"displace":14,"dodge":1,"equip":10,"explosion":5,"frostNova":29,"garrisonGrant":1,"iceSpike":17,"overclock":1,"revive":2,"ripple":20,"sentry":3,"shadowWeave":3,"shield":1,"slippery":4,"soul":2,"sp":17,"stealth":6,"sword":2,"swordRain":20,"swordStorm":5,"telegraph":14,"vest":2,"weightless":6,"zone":228},
|
||||
"snaps": "2e35c9af 1d134604 3072cfc9 ea16c0d0 3a75e4ec 87fad341 313be83c 0fbc2152 4662b897 2958cd1d 9a5096ce f6f29ba2 24f9b887 2f7f1188 064e6615 559057ce 81d9836a 8e384066 ece9a960 8ef465fa"
|
||||
"hooks": [1,49,6000,1361,1380,2376,2393,818,22,26,128,124,0,39025,1440,4,14,2,1,18,0,4,1],
|
||||
"events": {"atk":1441,"deploy":35,"die":24,"dmg":2375,"fx":660,"heal":25,"leak":2,"skill":252,"spawn":36,"status":926},
|
||||
"fx": {
|
||||
"aoe": 46,
|
||||
"appear": 4,
|
||||
"blink": 2,
|
||||
"burst": 4,
|
||||
"crit": 88,
|
||||
"dilemma": 6,
|
||||
"disappear": 4,
|
||||
"displace": 19,
|
||||
"dodge": 1,
|
||||
"equip": 8,
|
||||
"explosion": 6,
|
||||
"frostNova": 31,
|
||||
"garrisonGrant": 1,
|
||||
"iceSpike": 34,
|
||||
"overclock": 1,
|
||||
"pull": 6,
|
||||
"quadShot": 2,
|
||||
"revive": 2,
|
||||
"ripple": 18,
|
||||
"sentry": 3,
|
||||
"shadowWeave": 3,
|
||||
"shield": 1,
|
||||
"soul": 4,
|
||||
"sp": 14,
|
||||
"stealth": 6,
|
||||
"sword": 3,
|
||||
"swordRain": 15,
|
||||
"swordStorm": 4,
|
||||
"telegraph": 14,
|
||||
"tornado": 6,
|
||||
"tornadoPulse": 30,
|
||||
"vest": 2,
|
||||
"weightless": 6,
|
||||
"zone": 266
|
||||
},
|
||||
"snaps": "71ea5c09 c637fdb1 ef8eff23 dafd1b16 870d3a92 c8bef56e ef385349 8b0f67e6 f4ecdaae c2e41218 843a4c25 28a91424 99e64585 bf703aff 37e6b10f a0d2f168 c9b4c589 01338b90 c29679c2 2b5a6bd0"
|
||||
},
|
||||
"boss-boss_6-solo": {
|
||||
"about": "阿利斯泰尔,帝国余晖 solo pool 995000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
|
||||
@@ -713,7 +779,7 @@
|
||||
"enemy_9032_aclionk": [1,0,0,40445,102806]
|
||||
},
|
||||
"hooks": [1,47,6000,190,190,281,479,1232,32,34,21,21,0,7024,68,2,20,2,1,23,0,0,2],
|
||||
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"fx":90,"leak":2,"skill":42,"spawn":36,"status":187},
|
||||
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"fx":90,"leak":2,"skill":44,"spawn":36,"status":187},
|
||||
"fx": {"dodge":2,"equip":12,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":9,"riposte":2,"rockslide":1,"sandChains":4,"slash":24,"summon":2,"sword":1,"telegraph":11,"truesilver":4,"vest":5},
|
||||
"snaps": "0bc7ed75 fd621b74 943602ec 1ce73978 7eacd037 6f2ca0e5 5c055acf 20ca0a27 1c880376 72dc5f46 b8f2dcf8 62ed54d6 fb3d8d3c 49c21d20 99bfce59 3443456c fa01dd99 a8e70810 51b46f88 c9b8fd8c"
|
||||
},
|
||||
@@ -828,7 +894,7 @@
|
||||
"snaps": "fb57a457 3421e2b8 dbddb577 aef7b23c d91bd1fa 3b8774f3 e60348ab e4ca3c93 a3da29b6 ff5e9594 61b7b3b6 fe058645 c5dfd928 9f980ca6 e767bfd0 564e23cc be96a5c2 f867fc1e fa98a005 402593b3"
|
||||
},
|
||||
"hidden-boss_8-pair": {
|
||||
"about": "假想敌:胄 pair pool 7200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_12_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "假想敌:胄 pair pool 7200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "hidden",
|
||||
"stageId": "act1autochess_m05",
|
||||
@@ -842,90 +908,91 @@
|
||||
"killed": 14,
|
||||
"total": 18,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": 6037678,
|
||||
"rngDraws": 613,
|
||||
"bossHpLeft": 6093249,
|
||||
"rngDraws": 678,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":14,"total":18,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1809106,"bossDmg":812936,"heal":51857,"deaths":19,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":605650,"bossDmg":349386,"heal":8020,"deaths":26,"layerGains":{}}
|
||||
"p1": {"killed":14,"total":18,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1631955,"bossDmg":556895,"heal":40979,"deaths":16,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":712904,"bossDmg":548802,"heal":7371,"deaths":22,"layerGains":{}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,79,3,271515,30808,624,34,2],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,80,0,1319,16539,571,8,1],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",3988,1,85,1,26291,12825,3248,6,6],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,66,0,13133,24424,2166,9,2],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",2340,1,90,3,821462,2340,19307,93,13],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",0,0,76,3,399625,12328,635,28,3],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,71,0,17983,7235,719,8,2],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1248.8,1,72,2,202716,5518,323,20,7],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,64,0,0,11634,24266,20,2],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",0,0,82,1,55061,10170,0,16,2],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,91,0,0,3288,644,0,0],
|
||||
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,77,1,12912,4604,140,9,1],
|
||||
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",2149,1,89,2,26514,15781,71,63,5],
|
||||
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,65,0,5587,12946,4426,9,4],
|
||||
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,93,1,86724,17473,858,23,0],
|
||||
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,69,1,146718,12638,460,31,3],
|
||||
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,81,1,177775,4640,344,141,3],
|
||||
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,18,0,100154,7626,531,60,2],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,84,0,44145,4466,488,43,14],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,60,0,5121,3311,59,3,1],
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,3,6,303727,22484,384,33,2],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,7,0,9900,9238,344,9,1],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",3988,1,81,2,60038,14145,3817,35,10],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,78,0,29874,23536,239,30,3],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1277.62,1,73,1,527300,3402,17766,98,9],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",0,0,71,1,87427,11850,264,21,2],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",3258,1,80,0,3102,3731,473,4,1],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",3222,1,82,2,346270,3566,344,38,16],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",3220,1,88,0,4066,8508,17348,15,2],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",3390,1,77,2,260251,6780,0,23,2],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",360.32,1,72,0,0,6510,0,0,2],
|
||||
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",2149,1,83,2,12140,16725,87,82,5],
|
||||
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,64,0,14723,13084,4743,15,6],
|
||||
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1540.38,1,85,1,79696,16385,45,26,10],
|
||||
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,61,0,90500,10324,259,37,3],
|
||||
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,91,2,309879,1561,0,146,2],
|
||||
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",878.05,1,87,0,68641,5875,409,72,3],
|
||||
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,76,0,46791,3707,1475,38,2],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,65,0,20579,4017,39,35,10],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,66,1,69955,3567,315,25,5],
|
||||
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
|
||||
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {
|
||||
"enemy_10027_vtsk": [6,2,4,143925,888736],
|
||||
"enemy_10027_vtsk": [6,2,2,121147,924905],
|
||||
"enemy_1005_yokai": [4,4,0,0,1600],
|
||||
"enemy_1229_darmy_2": [8,8,0,2986,294231],
|
||||
"enemy_9013_acstmk_2": [1,0,0,52887,1162322],
|
||||
"enemy_9014_acstma": [6,5,0,7032,17081],
|
||||
"enemy_9015_acstmb": [3,2,0,13762,73414],
|
||||
"enemy_9016_acstmr": [8,7,0,0,51]
|
||||
"enemy_1229_darmy_2": [8,8,0,4691,294231],
|
||||
"enemy_9013_acstmk_2": [1,0,0,43305,1106751],
|
||||
"enemy_9014_acstma": [5,4,0,7224,30080],
|
||||
"enemy_9015_acstmb": [3,2,0,12628,13124],
|
||||
"enemy_9016_acstmr": [8,7,0,0,54]
|
||||
},
|
||||
"hooks": [1,91,6000,739,741,2760,2868,5716,74,79,73,82,3,18333,838,15,36,4,1,76,0,4,8],
|
||||
"events": {"atk":932,"deploy":55,"die":75,"dmg":2867,"fx":601,"heal":173,"leak":4,"skill":146,"spawn":58,"status":652},
|
||||
"hooks": [1,89,6000,891,892,2797,3003,4207,66,69,96,92,9,23645,681,20,35,2,1,69,0,9,3],
|
||||
"events": {"atk":1042,"deploy":54,"die":67,"dmg":2998,"fx":569,"heal":157,"leak":2,"skill":188,"spawn":57,"status":603},
|
||||
"fx": {
|
||||
"aoe": 19,
|
||||
"aoe": 34,
|
||||
"appear": 7,
|
||||
"beam": 46,
|
||||
"blink": 17,
|
||||
"burst": 4,
|
||||
"beam": 45,
|
||||
"blink": 14,
|
||||
"burst": 9,
|
||||
"charge": 12,
|
||||
"counter": 3,
|
||||
"crit": 72,
|
||||
"counter": 9,
|
||||
"crit": 83,
|
||||
"dilemma": 3,
|
||||
"disappear": 7,
|
||||
"dodge": 8,
|
||||
"explode": 8,
|
||||
"explosion": 1,
|
||||
"frostNova": 14,
|
||||
"iceSpike": 5,
|
||||
"palsy": 1,
|
||||
"phase": 1,
|
||||
"pull": 1,
|
||||
"quadShot": 3,
|
||||
"dodge": 3,
|
||||
"explode": 3,
|
||||
"explosion": 5,
|
||||
"frostNova": 10,
|
||||
"iceSpike": 4,
|
||||
"overclock": 2,
|
||||
"palsy": 2,
|
||||
"phase": 4,
|
||||
"pull": 2,
|
||||
"quadShot": 2,
|
||||
"revive": 2,
|
||||
"ripple": 5,
|
||||
"sentry": 5,
|
||||
"ripple": 9,
|
||||
"sentry": 4,
|
||||
"sermon": 1,
|
||||
"shadowWeave": 6,
|
||||
"shadowWeave": 10,
|
||||
"shell": 8,
|
||||
"shield": 3,
|
||||
"slippery": 2,
|
||||
"soul": 4,
|
||||
"sp": 10,
|
||||
"spGain": 3,
|
||||
"shield": 6,
|
||||
"slippery": 3,
|
||||
"soul": 9,
|
||||
"sp": 6,
|
||||
"spGain": 5,
|
||||
"stealth": 5,
|
||||
"summon": 4,
|
||||
"swordRain": 74,
|
||||
"swordStorm": 11,
|
||||
"swordRain": 57,
|
||||
"swordStorm": 10,
|
||||
"telegraph": 6,
|
||||
"tornado": 1,
|
||||
"tornadoPulse": 5,
|
||||
"weightless": 3,
|
||||
"zone": 211
|
||||
"tornado": 2,
|
||||
"tornadoPulse": 10,
|
||||
"weightless": 2,
|
||||
"zone": 150
|
||||
},
|
||||
"snaps": "22e07f1e 41a16486 3c5d78bd b5778e6c 952c561f b5d14c3c a800523c a6825729 603a09dc 5fd03161 96efcc3e 9f13862f 6541683d 1e15b931 1d5feabf d90fbdae 230b1290 c9c16dce e05a47fd 09bdfad0"
|
||||
"snaps": "3795cfd4 7f3862ea 8a202402 eeabbd1c cee0f0ab 5eafb35e f8a05875 1ed26b9a c9fdd9f1 e83ff1eb 77018d16 9bc0b42e 220cb648 f306f10d e8c3af47 3520d118 79f41539 410fee0c 3d280f53 466b98d1"
|
||||
},
|
||||
"hidden-boss_8-solo": {
|
||||
"about": "假想敌:胄 solo pool 1800000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
|
||||
@@ -973,7 +1040,7 @@
|
||||
"enemy_9016_acstmr": [7,7,0,0,25]
|
||||
},
|
||||
"hooks": [1,54,6000,267,267,494,540,687,39,46,13,13,0,5501,23,3,27,7,1,33,0,0,0],
|
||||
"events": {"atk":302,"deploy":23,"die":39,"dmg":534,"fx":159,"heal":53,"leak":7,"skill":25,"spawn":41,"status":85},
|
||||
"events": {"atk":302,"deploy":23,"die":39,"dmg":534,"fx":159,"heal":53,"leak":7,"skill":27,"spawn":41,"status":85},
|
||||
"fx": {"beam":30,"blink":9,"block":6,"charge":5,"explode":8,"garrisonGrant":1,"healField":4,"mote":3,"phase":1,"reweave":1,"rockslide":4,"sandChains":1,"shell":7,"shieldBreak":6,"slash":57,"spGift":4,"summon":5,"telegraph":3,"thorns":1,"truesilver":3},
|
||||
"snaps": "d1aaefe9 f284f922 d74a8f1d 9602e109 b0305cfc 7b811d98 c4991959 ad45bb9a c0314dcb 5738a584 6b481a5e e722f224 6c75c6d1 4ab59316 634e74cd 6b7487d0 c53dedc9 5845fde7 ed4678e2 1504d879"
|
||||
},
|
||||
@@ -1141,7 +1208,7 @@
|
||||
"snaps": "3c1045a0 9d7d77a9 a1302e26 19ce8f60 d773ac92 c1604114 ce3ff643 dc9c2381 ee91c31a 0e7f8e4d 9af2b87d fb2021c1 ee17f723 6cc85d21 ddbddcf1 fef4d0f5"
|
||||
},
|
||||
"hidden-boss_10-pair": {
|
||||
"about": "假想敌:管 pair pool 7600000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_12_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"about": "假想敌:管 pair pool 7600000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
|
||||
"fast": false,
|
||||
"kind": "hidden",
|
||||
"stageId": "act1autochess_m03",
|
||||
@@ -1155,85 +1222,85 @@
|
||||
"killed": 12,
|
||||
"total": 12,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": 6540517,
|
||||
"rngDraws": 469,
|
||||
"bossHpLeft": 6814341,
|
||||
"rngDraws": 1008,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":422920,"bossDmg":252016,"heal":10011,"deaths":1,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":971066,"bossDmg":807466,"heal":4490,"deaths":6,"layerGains":{}}
|
||||
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":382222,"bossDmg":214972,"heal":8209,"deaths":1,"layerGains":{}},
|
||||
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":737941,"bossDmg":570688,"heal":8845,"deaths":5,"layerGains":{}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,5,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,9,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_5_14_b",1,"none",3489.5,1,15,0,0,0,0,0,0],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,17,2,16620,5835,2257,36,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,2,183087,0,4523,19,5],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,0,62072,2958,3,55,5],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,51,2,112106,2847,273,221,6],
|
||||
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,17,1,19443,5825,2138,42,1],
|
||||
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,3,158666,0,4974,24,5],
|
||||
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,2,60570,981,0,64,6],
|
||||
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,48,0,98183,3032,116,230,6],
|
||||
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,3,0,0,0,0,0,0],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,7,0,0,0,2955,4,2],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,13,0,49035,0,0,30,3],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,48,0,0,4098,0,0,1],
|
||||
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,46,0,7998,2420,188,13,1],
|
||||
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,30,0,316123,0,0,249,8],
|
||||
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,47,0,1824,6232,1228,4,2],
|
||||
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,38,0,29495,2204,585,341,0],
|
||||
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1162.48,1,20,8,347065,3041,1426,122,6],
|
||||
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,10,1,10469,40,40,13,2],
|
||||
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,18,0,99350,416,416,111,4],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,22,1,110921,608,607,200,49],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,4,0,47822,0,0,16,5],
|
||||
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,7,0,0,0,981,1,1],
|
||||
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,13,0,45360,0,0,30,3],
|
||||
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,6,0,0,0,0,0,0],
|
||||
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",2149,1,51,0,114057,6080,385,174,6],
|
||||
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",213.43,1,52,0,3840,7314,855,1,1],
|
||||
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,31,0,108979,0,0,249,2],
|
||||
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1233.93,1,18,6,202897,2261,717,84,5],
|
||||
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,12,2,160082,0,0,271,4],
|
||||
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1249.33,1,20,0,9408,1072,657,7,2],
|
||||
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1282.27,1,22,0,50178,7180,6230,122,5],
|
||||
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,4,1,30506,0,0,37,12],
|
||||
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,8,1,57995,0,0,26,6],
|
||||
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,23,0,0,0,0,0,null],
|
||||
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,24,0,0,0,0,0,null],
|
||||
["-","~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]
|
||||
],
|
||||
"enemies": {
|
||||
"enemy_1402_tgshd_2": [2,2,0,7907,173864],
|
||||
"enemy_9010_acpupp": [2,2,0,5741,160520],
|
||||
"enemy_9021_acduml_2": [1,0,0,0,1059483],
|
||||
"enemy_9022_acdumm": [1,0,0,759,0],
|
||||
"enemy_9023_acdums": [12,12,0,10079,120]
|
||||
"enemy_1402_tgshd_2": [2,2,0,5170,173864],
|
||||
"enemy_9010_acpupp": [2,2,0,5184,160520],
|
||||
"enemy_9021_acduml_2": [1,0,0,343,785659],
|
||||
"enemy_9022_acdumm": [1,0,0,453,0],
|
||||
"enemy_9023_acdums": [12,12,0,16949,120]
|
||||
},
|
||||
"hooks": [1,49,6000,1262,1392,2332,2445,4079,23,24,100,98,0,33262,1840,8,18,0,1,25,0,3,0],
|
||||
"events": {"atk":1368,"deploy":29,"die":24,"dmg":2436,"fx":879,"heal":65,"skill":197,"spawn":42,"status":665},
|
||||
"hooks": [1,50,6000,1176,1325,2523,2675,5575,22,24,65,60,0,30221,1487,5,18,0,1,24,0,5,0],
|
||||
"events": {"atk":1269,"deploy":30,"die":24,"dmg":2664,"fx":984,"heal":48,"skill":125,"spawn":42,"status":569},
|
||||
"fx": {
|
||||
"appear": 2,
|
||||
"beam": 13,
|
||||
"blink": 1,
|
||||
"burst": 3,
|
||||
"charge": 2,
|
||||
"crit": 11,
|
||||
"dilemma": 6,
|
||||
"disappear": 2,
|
||||
"displace": 4,
|
||||
"beam": 12,
|
||||
"blink": 2,
|
||||
"burst": 5,
|
||||
"charge": 1,
|
||||
"crit": 130,
|
||||
"dilemma": 5,
|
||||
"ember": 2,
|
||||
"explode": 178,
|
||||
"explosion": 5,
|
||||
"expose": 200,
|
||||
"frostNova": 49,
|
||||
"iceSpike": 185,
|
||||
"overclock": 1,
|
||||
"explode": 191,
|
||||
"explosion": 6,
|
||||
"expose": 181,
|
||||
"frostNova": 12,
|
||||
"garrisonGrant": 1,
|
||||
"iceSpike": 195,
|
||||
"levitate": 4,
|
||||
"phase": 33,
|
||||
"pull": 1,
|
||||
"pull": 4,
|
||||
"quadShot": 4,
|
||||
"ripple": 30,
|
||||
"sentry": 1,
|
||||
"shadowWeave": 1,
|
||||
"sentry": 3,
|
||||
"shadowWeave": 5,
|
||||
"shield": 1,
|
||||
"shieldBreak": 2,
|
||||
"slippery": 4,
|
||||
"sp": 2,
|
||||
"stealth": 8,
|
||||
"shieldBreak": 1,
|
||||
"slippery": 2,
|
||||
"sp": 1,
|
||||
"splash": 2,
|
||||
"stealth": 6,
|
||||
"summon": 8,
|
||||
"swordRain": 14,
|
||||
"swordRain": 7,
|
||||
"swordStorm": 2,
|
||||
"telegraph": 2,
|
||||
"tornado": 1,
|
||||
"tornadoPulse": 5,
|
||||
"weightless": 5,
|
||||
"tornado": 5,
|
||||
"tornadoPulse": 20,
|
||||
"weightless": 6,
|
||||
"zone": 95
|
||||
},
|
||||
"snaps": "7c28a7f2 4ae4beb3 a4073390 75a1e14a e9c32c98 b8f590e1 12f86dd5 5b09053f 629ea5ed 067999f7 d600d102 c2eccdd9 1877d4e1 225869f7 279142c0 13833543 77802a58 3eeb404a 40d29a70 ff38818c"
|
||||
"snaps": "2d13cea7 f06b6ad4 1d7dfd15 e852f80b 0b756fcb 070248ab 0b86ce84 646bfdc9 b14a05d3 e9cf92b6 d36049ba a96f982d 8e26903b 1c8b3d68 986a0718 a884edff 84fc9d78 132274c4 2cbd5800 af396c48"
|
||||
},
|
||||
"hidden-boss_10-solo": {
|
||||
"about": "假想敌:管 solo pool 1900000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
|
||||
@@ -1274,7 +1341,7 @@
|
||||
"enemy_9023_acdums": [10,10,0,38449,100]
|
||||
},
|
||||
"hooks": [1,26,6000,602,602,1242,1839,4975,13,14,27,24,0,13945,59,10,15,1,1,17,0,11,0],
|
||||
"events": {"atk":703,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":151,"leak":1,"skill":51,"spawn":25,"status":257},
|
||||
"events": {"atk":703,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":151,"leak":1,"skill":53,"spawn":25,"status":257},
|
||||
"fx": {"bandage":4,"beam":15,"burst":11,"ember":2,"explode":189,"featherArrow":7,"garrisonGrant":1,"healField":2,"jungleSoul":5,"mote":61,"phase":30,"redistribute":43,"reweave":3,"rockslide":2,"sandChainsCharged":1,"shieldBreak":15,"slash":52,"spGift":7,"summon":8,"telegraph":2,"truesilver":3},
|
||||
"snaps": "94ed0146 7aa5e4e9 a874864d 30318786 bdfb16b6 6918b2ce 7730d01a d9e4bd51 7ee58629 31867138 dca52206 d53f4ec5 d7497fdf b13092f9 af40e2ec fbf47558 faca1653 c3c78c84 013d42c0 76aed4b1"
|
||||
},
|
||||
|
||||
+35
-35
@@ -592,7 +592,7 @@
|
||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":2992890,"gold":139,"itemsEquipped":12,"kills":333,"leaks":10,"lpLost":9,"merges":5,"perfectRounds":11,"refreshes":23},
|
||||
"stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":3012080,"gold":139,"itemsEquipped":12,"kills":333,"leaks":10,"lpLost":9,"merges":5,"perfectRounds":11,"refreshes":23},
|
||||
"lineup": ["3_19_b@11,7[2_04_e_b]","5_12_a@11,8","4_10_a@10,5[1_02_e_b+2_01_e_a]","6_06_a@9,4","1_08_b@9,5","6_16_a@9,7","5_20_a@9,8[3_05_e_a+3_02_e_b]","3_13_b@9,9"],
|
||||
"bonds": ["arcaneShip 2/86","miraShip 1/12","preciShip 0/10","sargonShip 0/10","siracusaShip 1/110","skillfulShip 1/36","soloShip 1/6","suntShip 1/0","swiftShip 0/32"]
|
||||
},
|
||||
@@ -622,7 +622,7 @@
|
||||
"combat": "client",
|
||||
"boost": null,
|
||||
"setup": {"stageId":"act1autochess_m04","bossId":"boss_4","hiddenBossId":"boss_10","factions":["FLY","TIMES","REFLECTION"]},
|
||||
"end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":114800},
|
||||
"end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":116300},
|
||||
"rng": {"setup":25,"shop":391,"waves":0,"draft":73,"bots":13595,"meta":11},
|
||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||
"rounds": [
|
||||
@@ -659,7 +659,7 @@
|
||||
{
|
||||
"round": 7,
|
||||
"ai_0": [1,25,10,0,0,4,8,"7dbd16ef",18,"kazimierzShip 1/2, kjeragShip 0/2, raidShip 1/3, skillfulShip 1/3, soloShip 1/3, steadShip 1/5, swiftShip 1/2",28,28,0,51],
|
||||
"ai_1": [1,24,10,0,2,4,8,"b6785496",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",28,28,0,53]
|
||||
"ai_1": [1,24,10,0,2,4,8,"6a9b4c34",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",24,28,4,53]
|
||||
},
|
||||
{
|
||||
"round": 8,
|
||||
@@ -703,7 +703,7 @@
|
||||
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
||||
"trophies": 3,
|
||||
"reward": 298,
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2447629,"gold":126,"itemsEquipped":8,"kills":204,"leaks":86,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2480376,"gold":126,"itemsEquipped":8,"kills":208,"leaks":86,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
|
||||
"lineup": [],
|
||||
"bonds": ["kazimierzShip 0/45","kjeragShip 0/2","miraShip 0/7","preciShip 0/8","raidShip 0/42","skillfulShip 0/9","soloShip 0/3","steadShip 0/11","swiftShip 0/38","yanShip 0/6"]
|
||||
},
|
||||
@@ -717,7 +717,7 @@
|
||||
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||
"trophies": 2,
|
||||
"reward": 255,
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1355731,"gold":114,"itemsEquipped":11,"kills":160,"leaks":73,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":19},
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1339854,"gold":114,"itemsEquipped":11,"kills":156,"leaks":77,"lpLost":33,"merges":3,"perfectRounds":4,"refreshes":19},
|
||||
"lineup": [],
|
||||
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/59","sargonShip 0/4","siracusaShip 0/8","skillfulShip 0/6","swiftShip 0/23"]
|
||||
}
|
||||
@@ -763,7 +763,7 @@
|
||||
},
|
||||
{
|
||||
"round": 5,
|
||||
"ai_0": [1,23,11,0,0,3,8,"59856422",20,"deputShip 1/12, kazimierzShip 0/2, skillfulShip 1/2, swiftShip 1/6",47,51,4,33],
|
||||
"ai_0": [1,23,11,0,0,3,8,"59856422",20,"deputShip 1/12, kazimierzShip 0/2, skillfulShip 1/2, swiftShip 1/6",49,51,2,33],
|
||||
"ai_1": [1,45,10,0,0,3,8,"a37755ee",27,"deputShip 2/6, kazimierzShip 0/7, swiftShip 1/21",53,53,0,32],
|
||||
"ai_2": [1,27,9,0,2,3,8,"c25b67c7",22,"deputShip 1/4, siracusaShip 1/4, soloShip 1/14, yanShip 0/1",53,53,0,31]
|
||||
},
|
||||
@@ -806,13 +806,13 @@
|
||||
{
|
||||
"round": 12,
|
||||
"ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/16, kazimierzShip 0/2, kjeragShip 0/61, miraShip 0/12, siracusaShip 0/6, skillfulShip 0/69, soloShip 0/28, steadShip 0/55, suntShip 0/16, swiftShip 0/26",14,57,43,122],
|
||||
"ai_1": [1,4,13,0,0,6,8,"fedd02aa",227,"deputShip 2/48, indomShip 1/23, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 1/39, soloShip 1/28, steadShip 1/16, suntShip 0/16, swiftShip 0/25, victoriaShip 1/73",17,57,40,113],
|
||||
"ai_1": [1,4,13,0,0,6,8,"8d26c229",219,"deputShip 2/48, indomShip 1/19, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 1/39, soloShip 1/28, steadShip 1/16, suntShip 0/16, swiftShip 0/25, victoriaShip 1/69",18,57,39,113],
|
||||
"ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",null,null,null,101]
|
||||
},
|
||||
{
|
||||
"round": 13,
|
||||
"ai_0": [0,0,0,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/16, kazimierzShip 0/2, kjeragShip 0/61, miraShip 0/12, siracusaShip 0/6, skillfulShip 0/69, soloShip 0/28, steadShip 0/55, suntShip 0/16, swiftShip 0/26",null,null,null,122],
|
||||
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/52, indomShip 0/27, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 0/43, soloShip 0/32, steadShip 0/26, suntShip 0/16, swiftShip 0/25, victoriaShip 0/89",5,26,21,124],
|
||||
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/52, indomShip 0/23, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 0/43, soloShip 0/32, steadShip 0/26, suntShip 0/16, swiftShip 0/25, victoriaShip 0/85",5,26,21,124],
|
||||
"ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",null,null,null,101]
|
||||
}
|
||||
],
|
||||
@@ -827,7 +827,7 @@
|
||||
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||
"trophies": 2,
|
||||
"reward": 255,
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4492000,"gold":122,"itemsEquipped":17,"kills":275,"leaks":72,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":15},
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4511751,"gold":122,"itemsEquipped":17,"kills":277,"leaks":70,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":15},
|
||||
"lineup": [],
|
||||
"bonds": ["deputShip 0/44","indomShip 0/16","kazimierzShip 0/2","kjeragShip 0/61","miraShip 0/12","siracusaShip 0/6","skillfulShip 0/69","soloShip 0/28","steadShip 0/55","suntShip 0/16","swiftShip 0/26"]
|
||||
},
|
||||
@@ -841,9 +841,9 @@
|
||||
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
|
||||
"trophies": 3,
|
||||
"reward": 298,
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3467151,"gold":124,"itemsEquipped":13,"kills":274,"leaks":102,"lpLost":51,"merges":3,"perfectRounds":5,"refreshes":20},
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3464200,"gold":124,"itemsEquipped":13,"kills":273,"leaks":101,"lpLost":51,"merges":3,"perfectRounds":5,"refreshes":20},
|
||||
"lineup": [],
|
||||
"bonds": ["deputShip 0/52","indomShip 0/27","kazimierzShip 0/11","kjeragShip 0/5","skillfulShip 0/43","soloShip 0/32","steadShip 0/26","suntShip 0/16","swiftShip 0/25","victoriaShip 0/89"]
|
||||
"bonds": ["deputShip 0/52","indomShip 0/23","kazimierzShip 0/11","kjeragShip 0/5","skillfulShip 0/43","soloShip 0/32","steadShip 0/26","suntShip 0/16","swiftShip 0/25","victoriaShip 0/85"]
|
||||
},
|
||||
"ai_2": {
|
||||
"alive": false,
|
||||
@@ -934,7 +934,7 @@
|
||||
{
|
||||
"round": 9,
|
||||
"ai_0": [1,26,12,0,0,4,8,"fc454676",169,"deputShip 1/47, kjeragShip 0/2, preciShip 2/40, sargonShip 1/44, steadShip 1/38",30,30,0,74],
|
||||
"ai_1": [1,45,12,0,0,4,8,"13fccde5",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",26,30,4,70],
|
||||
"ai_1": [1,45,12,0,0,4,8,"fc8e5237",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",29,30,1,70],
|
||||
"ai_2": [1,28,12,0,0,4,8,"6965081a",70,"sargonShip 0/8, siracusaShip 1/58, swiftShip 1/12, visiShip 1/0, yanShip 0/1",30,30,0,72],
|
||||
"ai_3": [1,31,13,0,0,4,8,"5f784848",153,"deputShip 1/16, emptyShip 1/12, kjeragShip 1/30, preciShip 1/72, siracusaShip 0/3, swiftShip 1/23, yanShip 0/1",30,30,0,76]
|
||||
},
|
||||
@@ -978,7 +978,7 @@
|
||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
||||
"trophies": 3,
|
||||
"reward": 250,
|
||||
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1695990,"gold":140,"itemsEquipped":25,"kills":328,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
|
||||
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1671568,"gold":140,"itemsEquipped":25,"kills":325,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
|
||||
"lineup": ["6_08_a@11,6[3_03_e_a+3_02_e_a]","6_12_a@11,7[4_08_e_a+6_07_e_b]","2_14_b@10,3","2_17_b@10,4[4_02_e_b+5_01_e_a]","2_08_a@9,3[4_04_e_a+1_01_e_b]","6_06_a@9,4[3_08_e_a+3_01_e_b]","1_06_b@9,8","5_02_b@9,9[5_02_e_a+3_06_e_b]"],
|
||||
"bonds": ["deputShip 1/61","emptyShip 1/6","kjeragShip 0/7","preciShip 2/158","sargonShip 1/172","steadShip 1/88"]
|
||||
},
|
||||
@@ -992,7 +992,7 @@
|
||||
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
||||
"trophies": 3,
|
||||
"reward": 250,
|
||||
"stats": {"activatedLayers":207,"bossDamage":12338,"dmgDealt":1288377,"gold":129,"itemsEquipped":20,"kills":237,"leaks":46,"lpLost":0,"merges":6,"perfectRounds":9,"refreshes":20},
|
||||
"stats": {"activatedLayers":207,"bossDamage":12384,"dmgDealt":1296113,"gold":129,"itemsEquipped":20,"kills":240,"leaks":43,"lpLost":0,"merges":6,"perfectRounds":9,"refreshes":20},
|
||||
"lineup": ["2_14_b@12,3","5_10_a@11,6[4_03_e_b+1_02_e_a]","6_01_a@11,7[3_05_e_a]","5_10_a@11,8[5_09_e_a+4_08_e_b]","6_14_a@10,3","4_16_a@10,8[4_11_e_a+4_04_e_b]","6_05_a@9,8[1_05_e_a+5_03_e_a]","2_16_a@9,9[5_02_e_a+4_05_e_b]"],
|
||||
"bonds": ["deputShip 1/96","emptyShip 1/23","preciShip 1/14","sargonShip 0/2","siracusaShip 1/74","steadShip 0/4"]
|
||||
},
|
||||
@@ -1006,7 +1006,7 @@
|
||||
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||
"trophies": 3,
|
||||
"reward": 250,
|
||||
"stats": {"activatedLayers":258,"bossDamage":70366,"dmgDealt":1649146,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
|
||||
"stats": {"activatedLayers":258,"bossDamage":70321,"dmgDealt":1649095,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
|
||||
"lineup": ["4_25_a@12,3","1_07_b@11,7[1_01_e_a+1_05_e_a]","5_20_a@11,8[3_02_e_a+2_01_e_b]","6_18_a@10,3[2_01_e_b+4_03_e_b]","4_10_a@10,7[4_03_e_b]","3_18_b@9,4[3_07_e_b+3_11_e_a]","6_05_a@9,8","2_16_b@9,9[5_01_e_a+4_02_e_a]"],
|
||||
"bonds": ["sargonShip 0/8","siracusaShip 2/234","swiftShip 1/24","yanShip 0/1"]
|
||||
},
|
||||
@@ -1143,7 +1143,7 @@
|
||||
"title": null,
|
||||
"trophies": 1,
|
||||
"reward": 149,
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":822006,"gold":62,"itemsEquipped":5,"kills":63,"leaks":34,"lpLost":31,"merges":2,"perfectRounds":3,"refreshes":5},
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":820341,"gold":62,"itemsEquipped":5,"kills":63,"leaks":34,"lpLost":31,"merges":2,"perfectRounds":3,"refreshes":5},
|
||||
"lineup": [],
|
||||
"bonds": ["arcaneShip 0/2","deputShip 0/6","kazimierzShip 0/27","kjeragShip 0/2","preciShip 0/2","sargonShip 0/8","siracusaShip 0/6","skillfulShip 0/4","soloShip 0/2","steadShip 0/2","swiftShip 0/34"]
|
||||
},
|
||||
@@ -1187,7 +1187,7 @@
|
||||
"combat": "server",
|
||||
"boost": null,
|
||||
"setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]},
|
||||
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":23,"bossPoolLeft":0,"virtualMs":121400},
|
||||
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":25,"bossPoolLeft":0,"virtualMs":121400},
|
||||
"rng": {"setup":25,"shop":535,"waves":0,"draft":34,"bots":18101,"meta":103},
|
||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||
"rounds": [
|
||||
@@ -1248,12 +1248,12 @@
|
||||
},
|
||||
{
|
||||
"round": 12,
|
||||
"ai_0": [1,15,12,0,2,6,8,"ac215c06",147,"arcaneShip 1/22, indomShip 0/8, kjeragShip 0/7, miraShip 1/10, preciShip 0/7, siracusaShip 2/98, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",33,46,13,118],
|
||||
"ai_0": [1,15,12,0,2,6,8,"ac215c06",147,"arcaneShip 1/22, indomShip 0/8, kjeragShip 0/7, miraShip 1/10, preciShip 0/7, siracusaShip 2/98, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",35,46,11,118],
|
||||
"ai_1": [1,17,12,0,0,6,8,"c4e0303a",121,"arcaneShip 1/24, emptyShip 1/0, indomShip 0/8, kjeragShip 0/4, preciShip 2/69, siracusaShip 1/28, soloShip 1/0, steadShip 0/4, victoriaShip 0/8, visiShip 0/8",46,46,0,115]
|
||||
},
|
||||
{
|
||||
"round": 13,
|
||||
"ai_0": [1,6,14,0,0,6,8,"953e06bc",186,"arcaneShip 1/31, indomShip 0/8, kjeragShip 0/7, miraShip 1/13, preciShip 0/7, siracusaShip 2/125, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",30,39,9,132],
|
||||
"ai_0": [1,8,14,0,0,6,8,"953e06bc",186,"arcaneShip 1/31, indomShip 0/8, kjeragShip 0/7, miraShip 1/13, preciShip 0/7, siracusaShip 2/125, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",32,39,7,132],
|
||||
"ai_1": [1,17,12,0,0,6,8,"eaf7de57",159,"arcaneShip 2/34, emptyShip 1/0, indomShip 0/8, kjeragShip 0/4, preciShip 2/87, siracusaShip 1/38, soloShip 1/0, steadShip 0/4, victoriaShip 0/8, visiShip 0/8",39,39,0,126]
|
||||
}
|
||||
],
|
||||
@@ -1263,12 +1263,12 @@
|
||||
"victory": true,
|
||||
"roundsPassed": 14,
|
||||
"eliminatedRound": null,
|
||||
"lp": 6,
|
||||
"lp": 8,
|
||||
"bandId": "band_doberm",
|
||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":217,"bossDamage":427385,"dmgDealt":3482385,"gold":143,"itemsEquipped":20,"kills":242,"leaks":42,"lpLost":24,"merges":2,"perfectRounds":7,"refreshes":25},
|
||||
"stats": {"activatedLayers":217,"bossDamage":394870,"dmgDealt":3465584,"gold":143,"itemsEquipped":20,"kills":247,"leaks":38,"lpLost":22,"merges":2,"perfectRounds":7,"refreshes":25},
|
||||
"lineup": ["1_17_a@11,6[1_02_e_a+1_05_e_a]","3_19_a@11,7[3_05_e_b+5_01_e_a]","5_20_a@10,3[3_02_e_a+3_03_e_b]","4_10_a@10,4[4_03_e_a+1_01_e_b]","3_18_a@10,7[6_09_e_a+3_11_e_b]","1_08_b@10,8[4_11_e_a+3_08_e_a]","6_05_a@9,8","4_16_a@9,9"],
|
||||
"bonds": ["arcaneShip 1/37","indomShip 0/8","kjeragShip 0/7","miraShip 1/13","preciShip 1/7","raidShip 1/0","siracusaShip 2/143","soloShip 1/11","steadShip 0/2","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"]
|
||||
},
|
||||
@@ -1282,7 +1282,7 @@
|
||||
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":193,"bossDamage":247615,"dmgDealt":3734549,"gold":138,"itemsEquipped":20,"kills":269,"leaks":12,"lpLost":11,"merges":3,"perfectRounds":11,"refreshes":25},
|
||||
"stats": {"activatedLayers":193,"bossDamage":280130,"dmgDealt":3810535,"gold":138,"itemsEquipped":20,"kills":267,"leaks":12,"lpLost":11,"merges":3,"perfectRounds":11,"refreshes":25},
|
||||
"lineup": ["4_25_a@12,3","6_12_a@11,6[2_01_e_a+1_05_e_b]","1_17_b@11,7[4_08_e_a+1_01_e_a]","5_02_a@10,3","4_10_a@10,7[3_01_e_b+1_02_e_b]","3_18_a@10,8","5_12_a@9,8[1_05_e_a]","1_08_a@9,9[4_10_e_a+5_07_e_a]"],
|
||||
"bonds": ["arcaneShip 2/49","emptyShip 1/0","indomShip 0/8","kjeragShip 0/8","preciShip 2/87","siracusaShip 1/57","soloShip 1/0","steadShip 0/8","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8"]
|
||||
}
|
||||
@@ -1352,7 +1352,7 @@
|
||||
},
|
||||
{
|
||||
"round": 13,
|
||||
"ai_0": [1,399,12,0,2,6,8,"5091cd4e",1944,"arcaneShip 2/360, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/308, investShip 0/300, kazimierzShip 0/308, kjeragShip 0/300, lateranoShip 1/310, maniShip 0/300, miraShip 0/300, preciShip 0/302, raidShip 0/300, sargonShip 0/300, siracusaShip 1/332, skillfulShip 1/335, soloShip 1/307, steadShip 0/300, suntShip 1/300, swiftShip 0/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",35,35,0,150]
|
||||
"ai_0": [1,399,12,0,2,6,8,"5091cd4e",1938,"arcaneShip 2/360, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/308, investShip 0/300, kazimierzShip 0/308, kjeragShip 0/300, lateranoShip 1/304, maniShip 0/300, miraShip 0/300, preciShip 0/302, raidShip 0/300, sargonShip 0/300, siracusaShip 1/332, skillfulShip 1/335, soloShip 1/307, steadShip 0/300, suntShip 1/300, swiftShip 0/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",35,35,0,150]
|
||||
}
|
||||
],
|
||||
"players": {
|
||||
@@ -1366,9 +1366,9 @@
|
||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||
"trophies": 0,
|
||||
"reward": 425,
|
||||
"stats": {"activatedLayers":2283,"bossDamage":1145000,"dmgDealt":3047851,"gold":175,"itemsEquipped":26,"kills":366,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":35},
|
||||
"stats": {"activatedLayers":2277,"bossDamage":1145000,"dmgDealt":3047851,"gold":175,"itemsEquipped":26,"kills":366,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":35},
|
||||
"lineup": ["4_02_b@12,7[3_08_e_a+3_03_e_b]","6_01_a@12,8[3_02_e_a]","6_16_a@11,5[1_05_e_b+3_03_e_a]","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_b@10,4[4_07_e_a+1_02_e_b]","6_05_a@10,5","4_01_b@10,6[6_03_e_a+4_06_e_a]","1_08_b@9,9"],
|
||||
"bonds": ["arcaneShip 2/395","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/310","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/353","skillfulShip 0/343","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
|
||||
"bonds": ["arcaneShip 2/395","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/304","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/353","skillfulShip 0/343","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1545,22 +1545,22 @@
|
||||
{
|
||||
"round": 10,
|
||||
"h_0": [1,26,12,0,0,5,8,"59b986b7",73,"arcaneShip 1/10, kazimierzShip 1/19, preciShip 1/14, raidShip 0/48, skillfulShip 1/6, soloShip 1/3, swiftShip 1/21, yanShip 0/1",31,31,0,87],
|
||||
"ai_0": [1,22,12,0,0,5,8,"fa71d132",198,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/91, preciShip 1/25, sargonShip 0/2, skillfulShip 1/16, suntShip 1/16, swiftShip 1/50",31,31,0,88]
|
||||
"ai_0": [1,22,12,0,0,5,8,"fa71d132",191,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/84, preciShip 1/25, sargonShip 0/2, skillfulShip 1/16, suntShip 1/16, swiftShip 1/50",31,31,0,88]
|
||||
},
|
||||
{
|
||||
"round": 11,
|
||||
"h_0": [1,22,12,0,0,5,8,"b7278144",146,"arcaneShip 0/10, kazimierzShip 1/34, preciShip 2/19, raidShip 1/61, skillfulShip 0/9, soloShip 1/6, suntShip 1/0, swiftShip 1/26, yanShip 0/1",21,25,4,99],
|
||||
"ai_0": [1,12,12,0,0,5,8,"e240abda",236,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/99, preciShip 1/33, raidShip 1/5, sargonShip 0/2, skillfulShip 1/21, suntShip 1/16, swiftShip 1/57, visiShip 1/5",14,25,11,100]
|
||||
"ai_0": [1,17,12,0,0,5,8,"e240abda",229,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/92, preciShip 1/33, raidShip 1/5, sargonShip 0/2, skillfulShip 1/21, suntShip 1/16, swiftShip 1/57, visiShip 1/5",20,25,5,100]
|
||||
},
|
||||
{
|
||||
"round": 12,
|
||||
"h_0": [1,15,12,0,0,6,8,"955db8af",172,"arcaneShip 0/10, kazimierzShip 1/42, preciShip 2/24, raidShip 1/69, skillfulShip 0/9, soloShip 1/6, suntShip 1/0, swiftShip 1/31, yanShip 0/1",24,31,7,111],
|
||||
"ai_0": [1,2,12,0,2,6,8,"3c27819a",274,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/107, preciShip 1/41, raidShip 1/10, sargonShip 0/2, skillfulShip 1/26, suntShip 1/16, swiftShip 1/64, visiShip 1/10",18,31,13,113]
|
||||
"ai_0": [1,7,12,0,2,6,8,"3c27819a",267,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/100, preciShip 1/41, raidShip 1/10, sargonShip 0/2, skillfulShip 1/26, suntShip 1/16, swiftShip 1/64, visiShip 1/10",18,31,13,113]
|
||||
},
|
||||
{
|
||||
"round": 13,
|
||||
"h_0": [1,5,12,0,0,6,8,"1bd1e6d0",244,"arcaneShip 1/10, kazimierzShip 1/54, preciShip 2/34, raidShip 1/105, skillfulShip 0/9, soloShip 0/6, suntShip 1/0, swiftShip 1/41, yanShip 0/1",20,57,37,122],
|
||||
"ai_0": [0,0,14,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 0/115, preciShip 0/49, raidShip 0/15, sargonShip 0/2, skillfulShip 0/31, suntShip 0/16, swiftShip 0/71, visiShip 0/15",15,57,42,126]
|
||||
"ai_0": [0,0,14,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 0/108, preciShip 0/49, raidShip 0/15, sargonShip 0/2, skillfulShip 0/31, suntShip 0/16, swiftShip 0/71, visiShip 0/15",15,57,42,126]
|
||||
}
|
||||
],
|
||||
"players": {
|
||||
@@ -1588,9 +1588,9 @@
|
||||
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
|
||||
"trophies": 3,
|
||||
"reward": 280,
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2105193,"gold":126,"itemsEquipped":15,"kills":227,"leaks":67,"lpLost":31,"merges":3,"perfectRounds":9,"refreshes":23},
|
||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2118906,"gold":126,"itemsEquipped":15,"kills":233,"leaks":61,"lpLost":26,"merges":3,"perfectRounds":9,"refreshes":23},
|
||||
"lineup": [],
|
||||
"bonds": ["arcaneShip 0/2","deputShip 0/18","kazimierzShip 0/8","lateranoShip 0/115","preciShip 0/49","raidShip 0/15","sargonShip 0/2","skillfulShip 0/31","suntShip 0/16","swiftShip 0/71","visiShip 0/15"]
|
||||
"bonds": ["arcaneShip 0/2","deputShip 0/18","kazimierzShip 0/8","lateranoShip 0/108","preciShip 0/49","raidShip 0/15","sargonShip 0/2","skillfulShip 0/31","suntShip 0/16","swiftShip 0/71","visiShip 0/15"]
|
||||
}
|
||||
},
|
||||
"standIns": {
|
||||
@@ -1672,12 +1672,12 @@
|
||||
{
|
||||
"round": 12,
|
||||
"h_0": [1,27,16,0,2,6,8,"3cdb76eb",388,"arcaneShip 0/2, indomShip 2/16, preciShip 2/141, sargonShip 1/179, skillfulShip 0/8, steadShip 0/51, suntShip 1/0, victoriaShip 0/19, visiShip 1/52",33,33,0,122],
|
||||
"ai_0": [1,28,12,0,0,6,8,"547fcf92",67,"sargonShip 0/8, siracusaShip 1/16, skillfulShip 1/18, soloShip 1/0, suntShip 2/0, yanShip 1/33",15,33,18,112]
|
||||
"ai_0": [1,28,12,0,0,6,8,"547fcf92",67,"sargonShip 0/8, siracusaShip 1/16, skillfulShip 1/18, soloShip 1/0, suntShip 2/0, yanShip 1/33",16,33,17,112]
|
||||
},
|
||||
{
|
||||
"round": 13,
|
||||
"h_0": [1,27,16,0,0,6,8,"d0f5bd71",508,"arcaneShip 0/2, indomShip 1/16, preciShip 2/173, sargonShip 1/239, skillfulShip 0/8, steadShip 0/51, suntShip 1/0, swiftShip 1/12, victoriaShip 0/19, visiShip 1/68",50,50,0,138],
|
||||
"ai_0": [1,28,12,0,0,6,8,"d2ff3200",90,"sargonShip 0/8, siracusaShip 1/24, skillfulShip 1/24, soloShip 1/0, suntShip 2/0, victoriaShip 0/5, yanShip 1/42",31,50,19,125]
|
||||
"ai_0": [1,28,12,0,0,6,8,"d2ff3200",90,"sargonShip 0/8, siracusaShip 1/24, skillfulShip 1/24, soloShip 1/0, suntShip 2/0, victoriaShip 0/5, yanShip 1/42",32,50,18,125]
|
||||
}
|
||||
],
|
||||
"players": {
|
||||
@@ -1691,7 +1691,7 @@
|
||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":534,"bossDamage":572474,"dmgDealt":4854054,"gold":154,"itemsEquipped":23,"kills":351,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":23},
|
||||
"stats": {"activatedLayers":534,"bossDamage":572474,"dmgDealt":4818483,"gold":154,"itemsEquipped":23,"kills":349,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":23},
|
||||
"lineup": ["5_21_b@12,8[3_07_e_b+1_05_e_a]","5_22_a@11,7","4_20_b@11,8","5_02_a@10,5[5_01_e_a+6_01_e_a]","6_05_a@10,6[2_01_e_a+4_08_e_a]","6_06_a@10,8[1_01_e_a+3_04_e_a]","4_07_b@9,8[5_03_e_a+4_11_e_b]","4_23_b@9,9[4_04_e_a]"],
|
||||
"bonds": ["arcaneShip 0/2","indomShip 1/16","preciShip 2/173","sargonShip 1/239","skillfulShip 0/8","steadShip 0/51","suntShip 1/0","swiftShip 1/22","victoriaShip 0/19","visiShip 1/84"]
|
||||
},
|
||||
@@ -1705,7 +1705,7 @@
|
||||
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":113,"bossDamage":215026,"dmgDealt":2608447,"gold":137,"itemsEquipped":20,"kills":245,"leaks":48,"lpLost":0,"merges":6,"perfectRounds":8,"refreshes":13},
|
||||
"stats": {"activatedLayers":113,"bossDamage":215026,"dmgDealt":2640057,"gold":137,"itemsEquipped":20,"kills":247,"leaks":46,"lpLost":0,"merges":6,"perfectRounds":8,"refreshes":13},
|
||||
"lineup": ["6_05_a@12,8[3_07_e_a+6_09_e_a]","2_04_b@11,7","1_07_b@11,8","6_11_a@10,6","3_03_b@10,8[3_11_e_b+2_04_e_a]","5_02_a@9,7[1_02_e_b]","1_08_b@9,8[4_06_e_a+1_05_e_a]","3_02_b@9,9[4_10_e_a+4_11_e_a]"],
|
||||
"bonds": ["maniShip 1/0","preciShip 1/0","sargonShip 1/8","siracusaShip 1/24","skillfulShip 1/30","soloShip 1/0","suntShip 2/0","victoriaShip 0/10","yanShip 1/51"]
|
||||
}
|
||||
|
||||
+313
-344
File diff suppressed because it is too large.
Load diff
+45
-33
@@ -46,7 +46,7 @@
|
||||
["p1",2,"op","chess_char_4_09_a",0,null,0,0,1,2,32698,2984,0,17,1],
|
||||
["p1",3,"op","chess_char_3_05_a",2,null,2505,1,74,2,29899,11886,0,45,2],
|
||||
["p1",4,"op","chess_char_3_18_a",2,null,0,0,7,1,14915,3668,0,36,1],
|
||||
["p1",5,"op","chess_char_4_04_a",1,null,497.45,1,91,2,12814,6576,0,82,0],
|
||||
["p1",5,"op","chess_char_4_04_a",1,null,497.45,1,91,2,12814,6576,0,82,10],
|
||||
["p1",6,"op","chess_char_4_07_a",1,null,0,0,75,1,18472,13600,0,43,1],
|
||||
["p1",7,"op","chess_char_4_16_a",1,null,0,0,88,0,3482,9543,0,103,0],
|
||||
["p1",8,"op","chess_char_3_01_a",2,null,1271,1,5,0,19777,0,0,141,2],
|
||||
@@ -68,10 +68,10 @@
|
||||
"enemy_1119_vofsd": [12,2,8,13284,62929],
|
||||
"enemy_1121_lifbos_2": [9,1,7,11244,38577]
|
||||
},
|
||||
"hooks": [1,91,5100,870,870,1441,1438,3866,41,82,15,33,0,18850,26,37,50,30,1,42,5,0,6],
|
||||
"events": {"atk":1013,"bounty":2,"deploy":41,"die":52,"dmg":1438,"fx":62,"heal":35,"layer":5,"leak":30,"skill":28,"spawn":62,"status":126},
|
||||
"hooks": [1,91,5100,870,870,1441,1438,3866,41,82,25,33,0,18850,26,37,50,30,1,42,5,0,6],
|
||||
"events": {"atk":1013,"bounty":2,"deploy":41,"die":52,"dmg":1438,"fx":62,"heal":35,"layer":5,"leak":30,"skill":47,"spawn":62,"status":126},
|
||||
"fx": {"beam":4,"blink":11,"dodge":6,"dp":5,"garrison":5,"liberate":14,"slash":16,"telegraph":1},
|
||||
"snaps": "c7e5e922 eab4c908 ad04ab78 0424dbc9 6eff6ee3 2b3bded9 3edb2c2b 02ffd9b9 6202ecfa f7f93735 4f3faeff 59df56c7 3b5b29d0 e6bceddc 4d139a38 ea4edd9b b54828d9"
|
||||
"snaps": "dec536bc cbfb0e19 608df77d 4bc1be00 31528b18 50c15974 172a64b1 80ed52af 452e6c6e 2cbbaf73 d2893b65 71598545 0ff0a232 8497b8ca 56857b5a 512860f1 25c11ae9"
|
||||
},
|
||||
"standin-02": {
|
||||
"about": "normal band 5 as stand-ins: chess_char_4_17_a→char_610_acfend chess_char_4_18_a→char_602_cdfend chess_char_5_05_a→char_602_cdfend chess_char_4_19_a→char_608_acpion chess_char_5_01_a→char_600_cpione chess_char_4_22_a→char_609_acguad chess_char_4_23_a→char_609_acguad chess_char_5_06_a→char_609_acguad chess_char_4_20_a→char_603_csnipe chess_char_4_24_a→char_604_ccast chess_char_5_02_a→char_604_ccast chess_char_5_03_a→char_612_accast",
|
||||
@@ -173,7 +173,7 @@
|
||||
"enemy_9011_acrefr": [12,2,3,23060,125251]
|
||||
},
|
||||
"hooks": [1,93,5700,1154,1154,1775,1770,135,58,77,25,27,0,28761,3090,58,74,19,1,58,0,0,8],
|
||||
"events": {"atk":1247,"bounty":4,"deploy":19,"die":58,"dmg":1767,"fx":170,"heal":124,"leak":19,"skill":50,"spawn":86,"status":614},
|
||||
"events": {"atk":1247,"bounty":4,"deploy":19,"die":58,"dmg":1767,"fx":170,"heal":124,"leak":19,"skill":56,"spawn":86,"status":614},
|
||||
"fx": {"aoe":60,"displace":1,"dodge":18,"dp":30,"phase":1,"slash":48,"sp":2,"spGain":10},
|
||||
"snaps": "dbcb5cec 56a8a8f6 e8d97a5d d2594a66 b6f753a4 78a2f35e 80349f15 2cd292dd d7240cae 4f120b33 69d90058 45d5da9a ae0b244b 17b17251 4df0cf30 f2c7c569 baa94919 07ba89e9 e4a68e4d"
|
||||
},
|
||||
@@ -290,7 +290,7 @@
|
||||
"enemy_1387_winshd": [30,1,29,15960,140112]
|
||||
},
|
||||
"hooks": [1,134,6430,620,620,988,988,6,29,128,8,24,0,14868,2437,28,104,97,1,29,1,0,0],
|
||||
"events": {"atk":651,"deploy":27,"die":31,"dmg":901,"fx":58,"heal":3,"layer":1,"leak":97,"skill":16,"spawn":114,"status":514},
|
||||
"events": {"atk":651,"deploy":27,"die":31,"dmg":901,"fx":58,"heal":3,"layer":1,"leak":97,"skill":47,"spawn":114,"status":514},
|
||||
"fx": {"aoe":5,"blink":2,"camouflage":1,"displace":22,"dodge":3,"dp":2,"explode":6,"garrison":1,"garrisonGrant":2,"slash":8,"zone":6},
|
||||
"snaps": "8d7b3223 b6dafa64 3940bc8e fd79b9e2 e5e85618 6fcfc479 90958489 71bfbb91 d12c224c cd2c9c7f 4a9fff33 68e48a86 4b97e027 5f8e8dad da62239a 8601c0dc 73717b6d 9e4f784f 818e7ed1 c1f2a855 33118eab 9bafc0bc"
|
||||
},
|
||||
@@ -303,31 +303,43 @@
|
||||
"round": 10,
|
||||
"seed": 1442657168,
|
||||
"waveId": "act1autochess_h03",
|
||||
"time": 182.77,
|
||||
"ticks": 5483,
|
||||
"time": 182.73,
|
||||
"ticks": 5482,
|
||||
"reason": "cleared",
|
||||
"killed": 42,
|
||||
"killed": 46,
|
||||
"total": 98,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 309,
|
||||
"rngDraws": 295,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {"killed":42,"total":98,"perfect":false,"leaks":56,"leaksUncounted":0,"coins":8,"dmg":577212,"bossDmg":0,"heal":19789,"deaths":32,"layerGains":{"raidShip":26}}
|
||||
"p1": {
|
||||
"killed": 46,
|
||||
"total": 98,
|
||||
"perfect": false,
|
||||
"leaks": 52,
|
||||
"leaksUncounted": 0,
|
||||
"coins": 8,
|
||||
"dmg": 621924,
|
||||
"bossDmg": 0,
|
||||
"heal": 27685,
|
||||
"deaths": 32,
|
||||
"layerGains": {"raidShip":26,"visiShip":4}
|
||||
}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_3_12_b",2,"none",1904,1,142,10,142532,5468,880,20,3],
|
||||
["p1",2,"op","chess_char_4_09_b",0,"none",0,0,6,7,58726,3510,0,13,1],
|
||||
["p1",3,"op","chess_char_3_05_b",2,"none",0,0,93,2,26911,9128,0,18,2],
|
||||
["p1",4,"op","chess_char_3_18_b",2,"none",0,0,92,3,34127,9062,2680,27,2],
|
||||
["p1",5,"op","chess_char_4_04_b",1,"none",0,0,141,0,16294,10620,0,30,0],
|
||||
["p1",6,"op","chess_char_4_07_b",1,"none",0,0,96,0,28260,10676,0,25,1],
|
||||
["p1",7,"op","chess_char_4_16_b",1,"none",0,0,139,4,24443,16559,0,44,0],
|
||||
["p1",8,"op","chess_char_3_01_b",2,"none",0,0,132,1,21262,3020,0,27,1],
|
||||
["p1",9,"op","chess_char_3_04_b",2,"none",0,0,4,0,0,6998,16229,17,2],
|
||||
["p1",10,"op","chess_char_3_21_b",2,"none",0,0,114,12,159580,7911,0,50,1],
|
||||
["p1",11,"op","chess_char_4_02_b",1,"none",0,0,2,3,47003,1483,0,29,1],
|
||||
["p1",12,"op","chess_char_4_13_b",1,"none",0,0,99,0,18075,3262,0,24,2]
|
||||
["p1",1,"op","chess_char_3_12_b",2,"none",1904,1,138,10,143498,5544,956,21,3],
|
||||
["p1",2,"op","chess_char_4_09_b",0,"none",0,0,6,9,109862,3510,0,21,1],
|
||||
["p1",3,"op","chess_char_3_05_b",2,"none",0,0,93,2,26416,9128,0,18,2],
|
||||
["p1",4,"op","chess_char_3_18_b",2,"none",0,0,92,3,33920,9190,2808,27,2],
|
||||
["p1",5,"op","chess_char_4_04_b",1,"none",0,0,137,0,13321,10188,0,31,8],
|
||||
["p1",6,"op","chess_char_4_07_b",1,"none",0,0,96,1,23318,10676,0,23,1],
|
||||
["p1",7,"op","chess_char_4_16_b",1,"none",0,0,136,3,27487,13878,0,36,0],
|
||||
["p1",8,"op","chess_char_3_01_b",2,"none",0,0,132,2,23235,3020,0,28,1],
|
||||
["p1",9,"op","chess_char_3_04_b",2,"none",0,0,4,0,0,13615,23921,23,2],
|
||||
["p1",10,"op","chess_char_3_21_b",2,"none",0,0,117,11,155638,7911,0,47,1],
|
||||
["p1",11,"op","chess_char_4_02_b",1,"none",0,0,2,5,48494,1483,0,31,1],
|
||||
["p1",12,"op","chess_char_4_13_b",1,"none",0,0,99,0,16734,3262,0,23,2]
|
||||
],
|
||||
"enemies": {
|
||||
"enemy_10162_mnctpt": [1,1,0,7990,39813],
|
||||
@@ -337,15 +349,15 @@
|
||||
"enemy_1023_jmage": [1,1,0,2086,5474],
|
||||
"enemy_1025_reveng": [1,1,0,0,12442],
|
||||
"enemy_1026_aghost": [1,1,0,0,9456],
|
||||
"enemy_1043_zomsbr": [1,0,1,0,0],
|
||||
"enemy_1251_lysyta_2": [57,26,31,15933,233652],
|
||||
"enemy_1252_lysytb_2": [21,8,13,23530,207750],
|
||||
"enemy_1329_cbshld_2": [12,1,11,32779,51307]
|
||||
"enemy_1043_zomsbr": [1,1,0,0,6287],
|
||||
"enemy_1251_lysyta_2": [57,29,28,15958,256380],
|
||||
"enemy_1252_lysytb_2": [21,8,13,24284,223998],
|
||||
"enemy_1329_cbshld_2": [12,1,11,35708,50755]
|
||||
},
|
||||
"hooks": [1,142,5483,525,525,773,799,61,74,141,16,40,0,6475,34,57,98,56,1,82,4,0,7],
|
||||
"events": {"atk":605,"bounty":4,"deploy":44,"die":85,"dmg":799,"fx":65,"heal":57,"layer":4,"leak":56,"skill":32,"spawn":110,"status":114},
|
||||
"fx": {"blink":11,"camouflage":1,"dodge":11,"dp":13,"explode":1,"garrison":4,"slash":24},
|
||||
"snaps": "2e7a8cb5 d50cd79b d7949cae 1b9749a6 6e3d202d f87f73b6 b1e5a323 226ab559 3962f99c b1192e86 1eb20f4b be395d5b 618f2a05 d9a8eb7c b36cfafe 0c3a2649 cb88aa80 0533b183 5054650e"
|
||||
"hooks": [1,138,5482,523,523,776,802,93,78,137,24,36,0,6341,31,56,98,52,1,86,5,0,4],
|
||||
"events": {"atk":611,"bounty":4,"deploy":40,"die":85,"dmg":802,"fx":59,"heal":66,"layer":5,"leak":52,"skill":48,"spawn":110,"status":107},
|
||||
"fx": {"blink":7,"camouflage":1,"dodge":8,"dp":13,"explode":1,"garrison":5,"slash":24},
|
||||
"snaps": "32c77df2 e0a686fc 511e3625 2a2c9595 4d454cd6 ba68fe2a 509a2a40 0f18d74e 26c72bef 4d6d5cce 834f8ce9 462b0854 0fec1e43 fd1bd922 ab1c6896 53173cd5 dbdfb42d 6f942256 4207d16e"
|
||||
},
|
||||
"standin-07": {
|
||||
"about": "elite band 6 as stand-ins: chess_char_4_17_b→char_610_acfend chess_char_4_18_b→char_602_cdfend chess_char_5_05_b→char_602_cdfend chess_char_4_19_b→char_608_acpion chess_char_5_01_b→char_600_cpione chess_char_4_22_b→char_609_acguad chess_char_4_23_b→char_609_acguad chess_char_5_06_b→char_609_acguad chess_char_4_20_b→char_603_csnipe chess_char_4_24_b→char_604_ccast chess_char_5_02_b→char_604_ccast chess_char_5_03_b→char_612_accast",
|
||||
@@ -464,7 +476,7 @@
|
||||
"enemy_1254_lypa_2": [48,1,47,74560,442348]
|
||||
},
|
||||
"hooks": [1,205,6900,1764,1764,3493,3533,147,56,189,40,44,0,26478,16569,72,179,133,1,59,0,0,10],
|
||||
"events": {"atk":2013,"bounty":1,"deploy":23,"die":56,"dmg":3508,"fx":308,"heal":130,"leak":133,"skill":80,"spawn":194,"status":1297},
|
||||
"events": {"atk":2013,"bounty":1,"deploy":23,"die":56,"dmg":3508,"fx":308,"heal":130,"leak":133,"skill":90,"spawn":194,"status":1297},
|
||||
"fx": {"aoe":90,"displace":12,"dodge":31,"dp":24,"shieldBreak":59,"slash":80,"sp":1,"spGain":11},
|
||||
"snaps": "4d4dc5ab 00efc642 4dc3aa32 13e9533b a3617bdf 587927d0 9fe3b7b8 0b39dceb 2981edf9 2ba1ce8c ef825d5b 85fd9608 a83f0a12 9e6b1271 7c5bf7ee f6ad8d8e c76286f6 037365de bf979497 771a38f7 b74aaf11 518326a3 19e5e338"
|
||||
},
|
||||
@@ -570,7 +582,7 @@
|
||||
"enemy_1402_tgshd_2": [36,2,34,29224,197345]
|
||||
},
|
||||
"hooks": [1,165,6486,549,549,865,877,6809,50,159,17,36,0,8308,1125,49,131,106,1,50,1,1,0],
|
||||
"events": {"atk":554,"deploy":32,"die":53,"dmg":802,"fx":530,"heal":21,"layer":1,"leak":106,"skill":34,"spawn":140,"status":378},
|
||||
"events": {"atk":554,"deploy":32,"die":53,"dmg":802,"fx":530,"heal":21,"layer":1,"leak":106,"skill":74,"spawn":140,"status":378},
|
||||
"fx": {"aoe":4,"blink":3,"burst":1,"displace":17,"dodge":3,"dp":17,"expose":414,"garrison":1,"garrisonGrant":3,"slash":67},
|
||||
"snaps": "80709a69 1b180600 2040e410 16970933 7ec28fab 061d6583 09f2da3e 98333fee 741e4051 c5f8165a 55db9474 b9b2bf46 d72aea5d 20d591d8 3077f062 ab32900d eb4540a2 37c492a6 3e3cd8f7 20a4cb8e 22ada53e b9ce62f2"
|
||||
}
|
||||
|
||||
+13
-14
@@ -55,27 +55,26 @@ test('glyph rules: melee only on LOW buildable ALL/MELEE; ranged also on HIGH/RA
|
||||
assert.ok(canPlace(buildDeployMap(null), 'melee', 10, 5));
|
||||
});
|
||||
|
||||
test('positionClass: melee stays ground-only; only elite 歌蕾蒂娅 + HOK-Y is widened (owner 2026-10-04)', () => {
|
||||
test('positionClass: a MELEE chess whose trait reads 「可以放置于远程位」 is widened, normal or elite, whatever the module (owner 2026-10-05)', () => {
|
||||
assert.equal(positionClass({ position: 'MELEE' }), 'melee');
|
||||
assert.equal(positionClass({ position: 'MELEE', placement: 'all' }), 'melee', 'the old placement field is ignored');
|
||||
assert.equal(basePositionClass({ position: 'MELEE', isGolden: true, charId: 'char_474_glady' }), 'melee');
|
||||
assert.equal(positionClass({ position: 'MELEE', trait: { desc: '技能可以使敌人产生位移\n可以放置于远程位' } }), 'all', 'the trait line');
|
||||
assert.equal(positionClass({ position: 'MELEE', trait: { desc: '可以额外部署在远程位' } }), 'melee', 'another wording is not the line');
|
||||
assert.equal(positionClass({ position: 'RANGED', trait: { desc: '可以放置于远程位' } }), 'ranged', 'a ranged record keeps its class');
|
||||
assert.equal(basePositionClass({ position: 'MELEE', trait: { desc: '可以放置于远程位' } }), 'melee', 'the battle class stays melee');
|
||||
assert.equal(positionClass({ position: 'RANGED' }), 'ranged');
|
||||
assert.equal(positionClass({ position: 'ALL' }), 'all');
|
||||
assert.equal(positionClass(null), 'all');
|
||||
const HOK_Y = 'uniequip_003_glady';
|
||||
const gladE = DATA.chess.chess_char_4_12_b;
|
||||
const map = buildDeployMap(DATA.stages.act2autochess_m01);
|
||||
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_4_12_a, HOK_Y), 10, 4), 'normal 歌蕾蒂娅, even with the id, not');
|
||||
assert.ok(!canPlace(map, positionClass(gladE), 10, 4), 'elite with no module id (default is not passed) not');
|
||||
assert.ok(!canPlace(map, positionClass(gladE, 'uniequip_002_glady'), 10, 4), 'HOK-X not');
|
||||
assert.ok(!canPlace(map, positionClass(gladE, 'none'), 10, 4), 'no module not');
|
||||
assert.ok(canPlace(map, positionClass(gladE, HOK_Y), 10, 4), 'elite + HOK-Y on the 高台');
|
||||
assert.ok(canPlace(map, positionClass(gladE, HOK_Y), 9, 3), 'and still on the ground');
|
||||
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_2_03_b, HOK_Y), 10, 4), '崖心 not');
|
||||
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_3_07_b, HOK_Y), 10, 4), '见行者 not');
|
||||
// the six trait holders: 歌蕾蒂娅 / 崖心 (钩索师) and 见行者 (推击手), normal and elite
|
||||
for (const id of ['chess_char_4_12_a', 'chess_char_4_12_b', 'chess_char_2_03_a', 'chess_char_2_03_b', 'chess_char_3_07_a', 'chess_char_3_07_b']) {
|
||||
assert.equal(positionClass(DATA.chess[id]), 'all', id);
|
||||
assert.ok(canPlace(map, positionClass(DATA.chess[id]), 10, 4), `${id} on the 高台`);
|
||||
assert.ok(canPlace(map, positionClass(DATA.chess[id]), 9, 3), `${id} still on the ground`);
|
||||
assert.equal(basePositionClass(DATA.chess[id]), 'melee', `${id} blocks like a melee unit`);
|
||||
assert.equal(DATA.chess[id].placement, undefined);
|
||||
}
|
||||
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_1_02_a), 10, 4), '角峰 (重装) not');
|
||||
assert.equal(DATA.chess.chess_char_4_12_a.placement, undefined);
|
||||
assert.equal(gladE.placement, undefined);
|
||||
});
|
||||
|
||||
test('g.move: legality per chess position, BAD_TILE outside, deploy cap 8, swaps allowed at cap', () => {
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
// nowhere to go); moving the owner sends it back anyway (PRTS 卫戍协议/帮助 "移动干员时,其所属召唤物全部退场并重置
|
||||
// 至手牌区") — with no room it leaves the board until the next round start.
|
||||
// The sim side (突袭 landing tile, tactical point) is test/sim/feedback1-water.test.js.
|
||||
// Owner's decision 2026-10-04 reverses DESIGN §22.6: the trait 「可以放置于远程位」 is not trusted. Only elite
|
||||
// 歌蕾蒂娅 carrying HOK-Y 淡金坠饰 (uniequip_003_glady) may stand on a 高台. The battle keeps position MELEE
|
||||
// (on a 高台 she attacks but blocks nothing). A bot may plan that one loadout on a 高台; everyone else stays on the ground.
|
||||
// 高台: the owner's decision of 2026-10-05 (reversing the one of 2026-10-04, elite 歌蕾蒂娅 with HOK-Y only) follows
|
||||
// PRTS — every MELEE chess whose trait reads 「可以放置于远程位」 (the 钩索师 / 推击手 branch trait: 歌蕾蒂娅, 崖心, 见行者,
|
||||
// normal and elite) may stand on a 高台, with any module or none; raised with the PRTS source in PR #69 by
|
||||
// @sunstricken. The battle keeps position MELEE (on a 高台 they attack but block nothing). The bots plan them on a 高台
|
||||
// that covers the enemy road; every other melee unit stays on the ground.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { buildDeployMap, canPlace as boardCanPlace, legalTiles, positionClass, placeClass, tileKey, ownerRangeKeys } from '../../server/match/board.js';
|
||||
import { GLADIIA_HOK_Y } from '../../shared/highGround.js';
|
||||
import { PLACE_ON_RANGED, meleeOnHighGround } from '../../shared/highGround.js';
|
||||
import { ERR } from '../../shared/constants.js';
|
||||
import { DATA, makeMatch, give, giveItem, checkInvariants, chessOfTier } from './harness.js';
|
||||
import { botPrep, planLayout, fieldModel } from '../../server/match/bot.js';
|
||||
@@ -305,75 +307,67 @@ test('#9 bots place 狼群 / 流形 only inside their owner\'s range', () => {
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// 高台: only elite 歌蕾蒂娅 + HOK-Y (owner's decision 2026-10-04)
|
||||
// 高台: every melee chess whose trait reads 「可以放置于远程位」, any module (the owner's decision of 2026-10-05)
|
||||
|
||||
const HIGH_STAGE = 'act2autochess_m01';
|
||||
const HIGH = ['10,4', '11,4', '12,4']; // 战场#01(下半)'s ranged-only (高台) tiles
|
||||
const GLAD_N = 'chess_char_4_12_a';
|
||||
const GLAD_E = 'chess_char_4_12_b'; // the golden record
|
||||
const CLIFF_N = 'chess_char_2_03_a';
|
||||
const CLIFF_E = 'chess_char_2_03_b';
|
||||
const FORCER_N = 'chess_char_3_07_a';
|
||||
const FORCER_E = 'chess_char_3_07_b';
|
||||
const HOLDERS = [GLAD_N, GLAD_E, CLIFF_N, CLIFF_E, FORCER_N, FORCER_E];
|
||||
const HOK_X = 'uniequip_002_glady';
|
||||
const HOK_Y = 'uniequip_003_glady';
|
||||
const TANK = (c) => c.profession === 'TANK' && c.position === 'MELEE';
|
||||
const rc = (k) => k.split(',').map(Number);
|
||||
const toHand = (m, ps, p) => move(m, p.uid, { area: 'hand', idx: ps.hand.findIndex((x) => x == null) });
|
||||
const equip = (ps, module) => {
|
||||
assert.equal(ps.setLoadout({ [GLAD_N]: { module } }), true, module);
|
||||
};
|
||||
/**
|
||||
* The loadouts each holder is tried with: its default (null), no module, and every module it has. 崖心 and 见行者 are
|
||||
* hidden this season (never in the pool): a player cannot set their loadout (shared/protocol.js checkLoadout), so they
|
||||
* are tried with their defaults (elite: HOK-X / PUS-X).
|
||||
*/
|
||||
const loadoutsOf = (id) => (DATA.chess[DATA.chess[id].baseId].isHidden ? [null]
|
||||
: [null, 'none', ...(DATA.chess[DATA.chess[id].baseId].goldenId ? DATA.chess[DATA.chess[DATA.chess[id].baseId].goldenId].modules : []).map((x) => x.uniEquipId)]);
|
||||
|
||||
test('高台 data: no chess stores placement; HOK-Y is 淡金坠饰 on the elite 歌蕾蒂娅 only', () => {
|
||||
test('高台 data: no chess stores placement; 「可以放置于远程位」 is the trait of exactly the six 钩索师 / 推击手 records, and every module keeps it', () => {
|
||||
assert.equal(Object.values(DATA.chess).some((c) => c.placement !== undefined), false);
|
||||
for (const t of Object.values(DATA.tokens)) assert.equal(t.placement, undefined, t.tokenId);
|
||||
const e = DATA.chess[GLAD_E];
|
||||
const y = e.modules.find((m) => m.uniEquipId === GLADIIA_HOK_Y);
|
||||
assert.equal(e.charId, 'char_474_glady');
|
||||
assert.equal(e.isGolden, true);
|
||||
assert.equal(y.typeName, 'HOK-Y');
|
||||
assert.equal(y.name, '淡金坠饰');
|
||||
assert.equal(e.modules.find((m) => m.isDefault).uniEquipId, HOK_X, 'the default module is HOK-X, ground-only');
|
||||
assert.equal(DATA.chess[GLAD_N].isGolden, false);
|
||||
assert.ok(!DATA.chess[CLIFF_E].modules.some((m) => m.uniEquipId === GLADIIA_HOK_Y), '崖心 has no HOK-Y');
|
||||
assert.equal(DATA.chess[GLAD_N].position, 'MELEE');
|
||||
assert.equal(e.position, 'MELEE');
|
||||
const widened = Object.values(DATA.chess).filter((c) => meleeOnHighGround(c)).map((c) => c.chessId).sort();
|
||||
assert.deepEqual(widened, HOLDERS.slice().sort());
|
||||
for (const id of HOLDERS) {
|
||||
const c = DATA.chess[id];
|
||||
assert.equal(c.position, 'MELEE', id);
|
||||
assert.ok(['钩索师', '推击手'].includes(c.subProfessionName), id);
|
||||
for (const x of c.modules || []) assert.ok(x.traitOverride.desc.includes(PLACE_ON_RANGED), `${id} ${x.typeName} keeps the line`);
|
||||
}
|
||||
// the module text never adds the line to anybody else (教官 Y "可以额外部署在远程位" is a talent, not the trait)
|
||||
for (const c of Object.values(DATA.chess)) {
|
||||
if (HOLDERS.includes(c.chessId)) continue;
|
||||
for (const x of c.modules || []) assert.ok(!(x.traitOverride?.desc || '').includes(PLACE_ON_RANGED), `${c.chessId} ${x.typeName}`);
|
||||
}
|
||||
assert.equal(DATA.chess[GLAD_E].modules.find((x) => x.uniEquipId === HOK_Y).typeName, 'HOK-Y');
|
||||
});
|
||||
|
||||
test('高台 table: (operator, module, tile) → allowed only for elite 歌蕾蒂娅 + HOK-Y on a 高台; ranged unchanged', () => {
|
||||
test('高台 table: every trait holder, normal and elite, with its default, no module or any module, may take each 高台; a 重装 is refused; ranged unchanged', () => {
|
||||
const { m, ps } = prep(HIGH_STAGE);
|
||||
assert.deepEqual([...ps.deployMap()].filter(([, cls]) => cls === 'ranged').map(([k]) => k).sort(), HIGH);
|
||||
// rows: chess id, module set on the 歌蕾蒂娅 loadout (null = leave the default), expect 高台
|
||||
const rows = [
|
||||
[GLAD_E, GLADIIA_HOK_Y, true],
|
||||
[GLAD_E, HOK_X, false],
|
||||
[GLAD_E, 'none', false],
|
||||
[GLAD_N, GLADIIA_HOK_Y, false],
|
||||
[GLAD_N, null, false],
|
||||
[CLIFF_E, GLADIIA_HOK_Y, false],
|
||||
[FORCER_N, null, false],
|
||||
[FORCER_E, null, false],
|
||||
];
|
||||
const owned = new Map();
|
||||
for (const [id, module, allowHigh] of rows) {
|
||||
if (module) equip(ps, module);
|
||||
else ps.setLoadout({});
|
||||
let p = owned.get(id);
|
||||
if (!p) { p = give(m, ps, id); owned.set(id, p); }
|
||||
else if (ps.find(p.uid)?.area === 'board') assert.deepEqual(toHand(m, ps, p), { ok: true });
|
||||
for (const k of HIGH) {
|
||||
const [r, c] = rc(k);
|
||||
const res = move(m, p.uid, board(r, c), 'DOWN');
|
||||
if (allowHigh) {
|
||||
assert.deepEqual(res, { ok: true }, `${id} ${module} → ${k}`);
|
||||
for (const id of HOLDERS) {
|
||||
const p = give(m, ps, id);
|
||||
for (const module of loadoutsOf(id)) {
|
||||
assert.equal(ps.setLoadout(module ? { [DATA.chess[id].baseId]: { module } } : {}), true, `${id} ${module}`);
|
||||
for (const k of HIGH) {
|
||||
const [r, c] = rc(k);
|
||||
assert.deepEqual(move(m, p.uid, board(r, c), 'DOWN'), { ok: true }, `${id} ${module || 'default'} → ${k}`);
|
||||
assert.equal(ps.board.get(k), p);
|
||||
checkInvariants(m);
|
||||
assert.deepEqual(toHand(m, ps, p), { ok: true });
|
||||
} else {
|
||||
assert.equal(res.error, ERR.BAD_TILE, `${id} ${module || 'default'} → ${k}`);
|
||||
}
|
||||
assert.deepEqual(move(m, p.uid, board(9, 3)), { ok: true }, `${id}: the ground stays legal`);
|
||||
assert.equal(placeClass(ps, DATA.chess[id]), 'all', id);
|
||||
assert.deepEqual(toHand(m, ps, p), { ok: true });
|
||||
}
|
||||
assert.deepEqual(move(m, p.uid, board(9, 3)), { ok: true }, `${id}: the ground stays legal`);
|
||||
assert.equal(placeClass(ps, DATA.chess[id]) === 'all', allowHigh, id);
|
||||
assert.deepEqual(toHand(m, ps, p), { ok: true });
|
||||
ps.setLoadout({});
|
||||
}
|
||||
const tank = give(m, ps, chessOfTier(1, TANK).find((x) => m.pool.has(x)));
|
||||
@@ -390,43 +384,41 @@ test('高台 table: (operator, module, tile) → allowed only for elite 歌蕾
|
||||
m.dispose();
|
||||
});
|
||||
|
||||
test('高台 client: drag highlights follow the loadout; a 重装 keeps "近战单位只能部署在地面"', () => {
|
||||
test('高台 client: drag highlights = the server for every trait holder under any module; a 重装 keeps "近战单位只能部署在地面"', () => {
|
||||
const { m, ps } = prep(HIGH_STAGE);
|
||||
equip(ps, GLADIIA_HOK_Y);
|
||||
const glad = give(m, ps, GLAD_E);
|
||||
const normal = give(m, ps, GLAD_N);
|
||||
const cliff = give(m, ps, CLIFF_E);
|
||||
const holders = HOLDERS.map((id) => give(m, ps, id));
|
||||
const tank = give(m, ps, chessOfTier(1, TANK).find((x) => m.pool.has(x)));
|
||||
const ranged = give(m, ps, chessOfTier(1, RANGED).find((x) => m.pool.has(x)));
|
||||
const ctx = clientCtx(m, ps);
|
||||
const map = ps.deployMap();
|
||||
for (const p of [glad, normal, cliff, tank, ranged]) {
|
||||
const lit = boardTargets(ctx, p.uid).legal.map(([r, c]) => tileKey(r, c)).sort();
|
||||
assert.deepEqual(lit, legalTiles(map, placeClass(ps, DATA.chess[p.id])).map(([r, c]) => tileKey(r, c)).sort(), `${p.id}: highlights = the server`);
|
||||
}
|
||||
for (const k of HIGH) {
|
||||
const [r, c] = rc(k);
|
||||
assert.equal(clientCanPlace(ctx, glad.uid, board(r, c)).ok, true, k);
|
||||
for (const p of [normal, cliff, tank]) {
|
||||
const res = clientCanPlace(ctx, p.uid, board(r, c));
|
||||
assert.equal(res.ok, false, p.id);
|
||||
for (const loadout of [{}, { [GLAD_N]: { module: HOK_Y } }, { [GLAD_N]: { module: HOK_X } }, { [GLAD_N]: { module: 'none' } }]) {
|
||||
assert.equal(ps.setLoadout(loadout), true);
|
||||
const ctx = clientCtx(m, ps);
|
||||
for (const p of [...holders, tank, ranged]) {
|
||||
const lit = boardTargets(ctx, p.uid).legal.map(([r, c]) => tileKey(r, c)).sort();
|
||||
assert.deepEqual(lit, legalTiles(map, placeClass(ps, DATA.chess[p.id])).map(([r, c]) => tileKey(r, c)).sort(), `${p.id}: highlights = the server`);
|
||||
}
|
||||
for (const k of HIGH) {
|
||||
const [r, c] = rc(k);
|
||||
for (const p of holders) assert.equal(clientCanPlace(ctx, p.uid, board(r, c)).ok, true, `${p.id} → ${k}`);
|
||||
const res = clientCanPlace(ctx, tank.uid, board(r, c));
|
||||
assert.equal(res.ok, false);
|
||||
assert.equal(res.reason, '近战单位只能部署在地面');
|
||||
assert.equal(clientCanPlace(ctx, ranged.uid, board(r, c)).ok, true);
|
||||
}
|
||||
assert.equal(clientCanPlace(ctx, ranged.uid, board(r, c)).ok, true);
|
||||
}
|
||||
m.dispose();
|
||||
});
|
||||
|
||||
test('高台 swaps: elite 歌蕾蒂娅 + HOK-Y trades with a ranged operator, not with a 重装', () => {
|
||||
test('高台 swaps: 歌蕾蒂娅 (normal, no module) trades with a ranged operator, not with a 重装', () => {
|
||||
const { m, ps } = prep(HIGH_STAGE);
|
||||
equip(ps, GLADIIA_HOK_Y);
|
||||
const glad = give(m, ps, GLAD_E);
|
||||
const glad = give(m, ps, GLAD_N);
|
||||
const tank = give(m, ps, chessOfTier(1, TANK).find((x) => m.pool.has(x)));
|
||||
const ranged = give(m, ps, chessOfTier(1, RANGED).find((x) => m.pool.has(x)));
|
||||
assert.deepEqual(move(m, glad.uid, board(10, 4), 'DOWN'), { ok: true });
|
||||
assert.deepEqual(move(m, tank.uid, board(9, 3)), { ok: true });
|
||||
assert.deepEqual(move(m, ranged.uid, board(11, 4)), { ok: true });
|
||||
let ctx = clientCtx(m, ps);
|
||||
// swapping 歌蕾蒂娅 (高台) with the 重装 (ground) would put the 重装 on the 高台
|
||||
assert.equal(clientCanPlace(ctx, glad.uid, board(9, 3)).ok, false);
|
||||
assert.equal(move(m, glad.uid, board(9, 3)).error, ERR.BAD_TILE);
|
||||
assert.equal(clientCanPlace(ctx, tank.uid, board(10, 4)).ok, false);
|
||||
@@ -444,16 +436,14 @@ test('高台 swaps: elite 歌蕾蒂娅 + HOK-Y trades with a ranged operator, no
|
||||
m.dispose();
|
||||
});
|
||||
|
||||
test('高台 battle input: elite 歌蕾蒂娅 + HOK-Y is fielded on (10,4) and blocks nothing there; on the ground she blocks', () => {
|
||||
test('高台 battle input: 歌蕾蒂娅 is fielded on (10,4) and blocks nothing there; on the ground she blocks', () => {
|
||||
const { m, ps } = prep(HIGH_STAGE);
|
||||
equip(ps, GLADIIA_HOK_Y);
|
||||
const glad = give(m, ps, GLAD_E);
|
||||
const glad = give(m, ps, GLAD_N);
|
||||
assert.deepEqual(move(m, glad.uid, board(10, 4), 'DOWN'), { ok: true });
|
||||
const u0 = ps.battleInput().units.find((u) => u.uid === glad.uid);
|
||||
assert.deepEqual([u0.row, u0.col, u0.dir, u0.moduleId], [10, 4, 'DOWN', GLADIIA_HOK_Y]);
|
||||
assert.deepEqual([u0.row, u0.col, u0.dir], [10, 4, 'DOWN']);
|
||||
m.dispose();
|
||||
// position stays MELEE either way: the normal record (the same body the 0.1.2 measurement used) attacks the lane
|
||||
// from the 高台 and blocks nothing there. HOK-Y's own range is not what this asserts.
|
||||
// position stays MELEE either way: she attacks the lane from the 高台 and blocks nothing there
|
||||
for (const [r, c, dir, high] of [[10, 4, 'DOWN', true], [9, 5, 'RIGHT', false]]) {
|
||||
const h = makeBattle({
|
||||
stageId: HIGH_STAGE, seed: 3, timeLimit: 60, autoFinish: false,
|
||||
@@ -473,42 +463,36 @@ test('高台 battle input: elite 歌蕾蒂娅 + HOK-Y is fielded on (10,4) and b
|
||||
}
|
||||
});
|
||||
|
||||
test('高台 bots: elite 歌蕾蒂娅 + HOK-Y takes a 高台 that covers the road while the ground is still free', () => {
|
||||
test('高台 bots: every trait holder, normal and elite, takes a 高台 that covers the road while the ground is still free; a plain melee stays on the road', () => {
|
||||
const { m, ps } = prep(HIGH_STAGE, 11);
|
||||
equip(ps, GLADIIA_HOK_Y);
|
||||
const elite = give(m, ps, GLAD_E);
|
||||
const k = planLayout(m, ps, [elite]).get(elite.uid);
|
||||
assert.equal(ps.deployMap().get(k), 'ranged', `planned on a 高台 with the road open (${k})`);
|
||||
const [r, c] = rc(k);
|
||||
assert.deepEqual(move(m, elite.uid, board(r, c)), { ok: true });
|
||||
m.dispose();
|
||||
});
|
||||
|
||||
test('高台 bots: everyone else stays on the road; elite 歌蕾蒂娅 + HOK-Y may take a 高台 when the ground is full', () => {
|
||||
const { m, ps } = prep(HIGH_STAGE, 11);
|
||||
const pieces = ['chess_char_4_02_a', GLAD_N, FORCER_N, 'chess_char_2_03_a'].map((id) => give(m, ps, id));
|
||||
const plan = planLayout(m, ps, pieces);
|
||||
const map = ps.deployMap();
|
||||
const road = fieldModel(m, ps).ground;
|
||||
for (const p of pieces.slice(1)) {
|
||||
const k = plan.get(p.uid);
|
||||
assert.equal(map.get(k), 'melee', `${p.id} on a ground tile (${k})`);
|
||||
assert.ok(road.has(k), `${p.id} on an enemy road tile (${k})`);
|
||||
assert.equal(move(m, p.uid, board(10, 4)).error, ERR.BAD_TILE, `${p.id} deploy refused`);
|
||||
for (const id of HOLDERS) {
|
||||
const p = give(m, ps, id);
|
||||
for (const module of loadoutsOf(id)) {
|
||||
assert.equal(ps.setLoadout(module ? { [DATA.chess[id].baseId]: { module } } : {}), true);
|
||||
const plan = planLayout(m, ps, [p]);
|
||||
const k = plan.get(p.uid);
|
||||
assert.equal(map.get(k), 'ranged', `${id} ${module || 'default'}: planned on a 高台 with the road open (${k})`);
|
||||
const [r, c] = rc(k);
|
||||
const cover = ownerRangeKeys(DATA.chess[id].rangeGrid, r, c, plan.dirOf(p.uid));
|
||||
assert.ok([...cover].some((t) => road.has(t)), `${id}: its range from ${k} covers the road`);
|
||||
assert.deepEqual(move(m, p.uid, board(r, c), plan.dirOf(p.uid)), { ok: true });
|
||||
assert.deepEqual(toHand(m, ps, p), { ok: true });
|
||||
}
|
||||
}
|
||||
const elite = give(m, ps, GLAD_E);
|
||||
equip(ps, GLADIIA_HOK_Y);
|
||||
const melee = [...map].filter(([, cls]) => cls === 'melee').map(([k]) => k);
|
||||
const highPlan = planLayout(m, ps, [elite], undefined, { occupied: new Set(melee) });
|
||||
const hk = highPlan.get(elite.uid);
|
||||
assert.equal(map.get(hk), 'ranged', `HOK-Y planned on a 高台 (${hk})`);
|
||||
const [hr, hc] = rc(hk);
|
||||
assert.deepEqual(move(m, elite.uid, board(hr, hc)), { ok: true });
|
||||
ps.setLoadout({});
|
||||
// a plain melee operator (a 重装) stays on an enemy road tile, and a 高台 is no fallback for it once the ground is full
|
||||
const tank = give(m, ps, chessOfTier(1, TANK).find((x) => m.pool.has(x)));
|
||||
const k = planLayout(m, ps, [tank]).get(tank.uid);
|
||||
assert.equal(map.get(k), 'melee', `the 重装 on a ground tile (${k})`);
|
||||
assert.ok(road.has(k), `the 重装 on an enemy road tile (${k})`);
|
||||
assert.equal(move(m, tank.uid, board(10, 4)).error, ERR.BAD_TILE, 'the 重装 deploy refused');
|
||||
const melee = new Set([...map].filter(([, cls]) => cls === 'melee').map(([key]) => key));
|
||||
assert.equal(planLayout(m, ps, [tank], undefined, { occupied: melee }).get(tank.uid), undefined, 'no ground left: no plan');
|
||||
const glad = give(m, ps, GLAD_E);
|
||||
const hk = planLayout(m, ps, [glad], undefined, { occupied: melee }).get(glad.uid);
|
||||
assert.equal(map.get(hk), 'ranged', `elite 歌蕾蒂娅 still gets a 高台 (${hk})`);
|
||||
checkInvariants(m);
|
||||
assert.deepEqual(toHand(m, ps, elite), { ok: true });
|
||||
equip(ps, 'none');
|
||||
const refused = planLayout(m, ps, [elite], undefined, { occupied: new Set(melee) });
|
||||
assert.equal(refused.get(elite.uid), undefined, 'no module: a 高台 is not a fallback');
|
||||
assert.equal(move(m, elite.uid, board(10, 4)).error, ERR.BAD_TILE);
|
||||
m.dispose();
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { DATA, makeMatch } from './harness.js';
|
||||
import { GameData } from '../../server/match/gamedata.js';
|
||||
import { bondSnapshot } from '../../server/match/bondsMeta.js';
|
||||
import { buildBattleSpec, createBattleFromSpec, compactResult } from '../../server/sim/spec.js';
|
||||
import { validateClientResult } from '../../server/match/fields.js';
|
||||
import { helperStats, helperOrder } from '../../server/match/unite.js';
|
||||
|
||||
const IDS = ['maniShip', 'emptyShip', 'soloShip', 'suntShip'];
|
||||
const gd = new GameData(DATA, 'mode_multi_normal');
|
||||
const bonds = () => Object.fromEntries(IDS.map((id) => [id, { count: 2, active: true, tier: 1, layers: 20 }]));
|
||||
|
||||
test('hidden-layer bonds retain saved layers, prep gains, pending gains and battle input layers', () => {
|
||||
const h = makeMatch({ mode: 'solo', humans: 1, fake: true }).start();
|
||||
try {
|
||||
h.toPrep(1);
|
||||
const ps = h.ps('p_0');
|
||||
for (const id of IDS) {
|
||||
assert.equal(gd.bond(id).noStack, true, id);
|
||||
ps.bondCountBonus[id] = gd.bond(id).thresholds[0];
|
||||
ps.layers[id] = 50;
|
||||
}
|
||||
ps.bondCountBonus.deputShip = 2;
|
||||
ps.recompute();
|
||||
for (const id of IDS) {
|
||||
assert.equal(ps.bonds[id].active, true);
|
||||
assert.equal(ps.layers[id], 50, 'recompute must not erase saved layers');
|
||||
assert.equal(ps.addLayers(id, 8, { requireActive: true }), 8);
|
||||
assert.equal(ps.addLayers(id, 8, { requireActive: false }), 8);
|
||||
}
|
||||
h.m.dispatch(ps, 'onPrepEnd', { round: 1 });
|
||||
for (const id of IDS) assert.equal(ps.layers[id], 68, '助力 prep-end traits can add hidden layers');
|
||||
ps.pendingLayerGains = Object.fromEntries(IDS.map((id) => [id, 9]));
|
||||
for (const id of IDS) {
|
||||
assert.equal(ps.bondsView()[id].layers, 77);
|
||||
assert.equal(bondSnapshot(ps.bonds)[id].layers, 68, 'battle input keeps settled layers');
|
||||
}
|
||||
} finally { h.m.dispose(); }
|
||||
});
|
||||
|
||||
test('hidden-layer gains run through the simulator hooks, event stream and result', () => {
|
||||
const spec = buildBattleSpec({ stageId: 'act2autochess_m01', timeLimit: 1, content: 'none',
|
||||
players: [{ playerId: 'p_0', units: [], bonds: bonds() }], flags: { layerGainsEnabled: true } });
|
||||
const b = createBattleFromSpec(spec, DATA, { quiet: true });
|
||||
const calls = [];
|
||||
b.on('layerGain', (c) => calls.push(c.bondId));
|
||||
for (const id of IDS) {
|
||||
assert.equal(b.getPlayer('p_0').bonds[id].layers, 20);
|
||||
assert.equal(b.addLayers('p_0', id, 4), 4);
|
||||
assert.equal(b.getPlayer('p_0').bonds[id].layers, 24);
|
||||
}
|
||||
assert.deepEqual(calls, IDS);
|
||||
assert.deepEqual(b.drainEvents().filter((e) => e[0] === 'layer').map((e) => e[2]), IDS);
|
||||
b.forceEnd();
|
||||
assert.deepEqual(b.result().perPlayer.p_0.layerGains, Object.fromEntries(IDS.map((id) => [id, 4])));
|
||||
});
|
||||
|
||||
test('client validation accepts legal hidden-layer gains and still rejects malformed JSON values', () => {
|
||||
const spec = buildBattleSpec({ stageId: 'act2autochess_m01', timeLimit: 1, content: 'none',
|
||||
players: [{ playerId: 'p_0', units: [], bonds: bonds() }], flags: { layerGainsEnabled: true } });
|
||||
const b = createBattleFromSpec(spec, DATA, { quiet: true });
|
||||
for (const id of IDS) b.addLayers('p_0', id, 4);
|
||||
b.forceEnd();
|
||||
const raw = compactResult(b.result());
|
||||
assert.equal(validateClientResult(spec, raw, { gd }).ok, true);
|
||||
for (const value of [-1, '4', null, {}, 1000]) {
|
||||
const forged = structuredClone(b.result());
|
||||
forged.perPlayer.p_0.layerGains.soloShip = value;
|
||||
assert.equal(validateClientResult(spec, forged, { gd }).ok, false, JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
|
||||
test('settlement retains hidden-layer gains from the server result', () => {
|
||||
const h = makeMatch({ mode: 'solo', humans: 1, fake: true,
|
||||
script: () => ({ layerGains: { p_0: { soloShip: 12, suntShip: 9, yanShip: 3 } } }) }).start();
|
||||
try {
|
||||
h.toPrep(1);
|
||||
h.m.handle('p_0', { t: 'g.ready', ready: true });
|
||||
h.toPrep(2);
|
||||
assert.equal(h.ps('p_0').layers.soloShip, 12);
|
||||
assert.equal(h.ps('p_0').layers.suntShip, 9);
|
||||
assert.equal(h.ps('p_0').layers.yanShip, 3);
|
||||
} finally { h.m.dispose(); }
|
||||
});
|
||||
|
||||
test('hidden layers contribute to unite pair ordering while inactive layers do not', () => {
|
||||
const player = (id, seat, layers) => ({ playerId: id, seat, deployCount: 1, board: new Map(),
|
||||
layers: { soloShip: layers, suntShip: 999 }, bonds: {
|
||||
soloShip: { active: true, layers }, suntShip: { active: false, layers: 999 },
|
||||
} });
|
||||
const a = player('p_0', 0, 5), b = player('p_1', 1, 8), m = { gd }, results = new Map();
|
||||
assert.equal(helperStats(m, b, results).layers, 8);
|
||||
assert.deepEqual(helperOrder(m, [a, b], results).map((p) => p.playerId), ['p_1', 'p_0']);
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
import { test, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { startServer } from '../../server/index.js';
|
||||
import { getData } from '../../server/data.js';
|
||||
import { Match } from '../../server/match/Match.js';
|
||||
import { TestClient } from '../helpers/wsClient.js';
|
||||
import { FakeBattle } from './fakeBattle.js';
|
||||
@@ -23,7 +24,8 @@ let srv = null;
|
||||
const clients = [];
|
||||
|
||||
async function server() {
|
||||
if (!srv) srv = await startServer({ port: 0, host: '127.0.0.1', log, MatchClass: FastMatch });
|
||||
// Issue #144: in the one-human + AI draft, seed 69 makes the AI take band_bldsk first.
|
||||
if (!srv) srv = await startServer({ port: 0, host: '127.0.0.1', log, MatchClass: FastMatch, seedFn: () => 69 });
|
||||
return srv;
|
||||
}
|
||||
async function player(name, token) {
|
||||
@@ -38,6 +40,23 @@ async function player(name, token) {
|
||||
}
|
||||
const ok = async (c, msg) => { const r = await c.request(msg); assert.equal(r.t, 'ok', `${msg.t}: ${JSON.stringify(r)}`); return r; };
|
||||
|
||||
/** Pick from the current public draft, including the AI's choices; never rely on a timeout after a refused pick. */
|
||||
async function pickCoopBands(players) {
|
||||
const pending = new Map(players.map((c) => [c.id, c]));
|
||||
let frame;
|
||||
while (pending.size) {
|
||||
frame = await players[0].waitFor('m.public', (p) => p.phase === 'BAND_DRAFT' && pending.has(p.draft?.turn), 10000);
|
||||
assert.equal(frame.draft.order.length, frame.players.length);
|
||||
const taken = new Set(Object.values(frame.draft.picks));
|
||||
const band = Object.values(getData().bands).find((b) =>
|
||||
(!Array.isArray(b.modeTypeList) || b.modeTypeList.includes('MULTI')) && !taken.has(b.bandId));
|
||||
assert.ok(band, 'a co-op strategy is still available');
|
||||
await ok(pending.get(frame.draft.turn), { t: 'g.band', bandId: band.bandId });
|
||||
pending.delete(frame.draft.turn);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
after(async () => {
|
||||
for (const c of clients) await c.terminate().catch(() => {});
|
||||
if (srv) await srv.close();
|
||||
@@ -101,15 +120,7 @@ test('co-op over websockets: two humans + AI, reconnect with the token mid-match
|
||||
for (const c of [a, b]) await c.waitFor('m.public', (p) => p.phase === 'INFO_CHECK');
|
||||
await ok(a, { t: 'g.infoReady' });
|
||||
await ok(b, { t: 'g.infoReady' });
|
||||
const draft = await a.waitFor('m.public', (p) => p.phase === 'BAND_DRAFT' && p.draft);
|
||||
assert.equal(draft.draft.order.length, 3);
|
||||
// humans pick when it is their turn (bots pick themselves)
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const p = await a.waitFor('m.public', (x) => x.phase !== 'BAND_DRAFT' || [a.id, b.id].includes(x.draft && x.draft.turn), 10000);
|
||||
if (p.phase !== 'BAND_DRAFT') break;
|
||||
const who = p.draft.turn === a.id ? a : b;
|
||||
await who.request({ t: 'g.band', bandId: 'band_bldsk' });
|
||||
}
|
||||
await pickCoopBands([a, b]);
|
||||
await a.waitFor('m.public', (p) => p.phase === 'PREP' && p.round === 1, 10000);
|
||||
// B drops and comes back with its token
|
||||
await b.terminate();
|
||||
@@ -120,7 +131,9 @@ test('co-op over websockets: two humans + AI, reconnect with the token mid-match
|
||||
assert.equal(priv.playerId, b2.id);
|
||||
await ok(a, { t: 'g.ready', ready: true });
|
||||
await ok(b2, { t: 'g.ready', ready: true });
|
||||
await a.waitFor('m.public', (p) => p.phase === 'COMBAT', 10000);
|
||||
// Instant simulated battles can finish before the throttled public phase is broadcast, as in the solo test.
|
||||
const start = await a.waitFor('b.start', (x) => x.authoritative, 10000);
|
||||
assert.equal(start.fieldId, `n:${a.id}`);
|
||||
await a.waitFor('m.public', (p) => p.phase === 'PREP' && p.round === 2, 15000);
|
||||
await ok(a, { t: 'g.leave' });
|
||||
await ok(b2, { t: 'g.leave' });
|
||||
@@ -154,11 +167,8 @@ test('co-op spectator over websockets: watches the real match like an eliminated
|
||||
assert.equal(r.code, 'SPECTATOR', msg.t);
|
||||
}
|
||||
await ok(a, { t: 'g.infoReady' });
|
||||
for (let i = 0; i < 40; i++) { // every draft frame in order: pick on the own turn (the AI picks at once)
|
||||
const p = await a.waitFor('m.public', (x) => x.phase !== 'INFO_CHECK', 10000);
|
||||
if (p.phase !== 'BAND_DRAFT') break;
|
||||
if (p.draft && p.draft.turn === a.id && !p.draft.picks[a.id]) await ok(a, { t: 'g.band', bandId: 'band_bldsk' });
|
||||
}
|
||||
const draft = await pickCoopBands([a]);
|
||||
assert.ok(Object.values(draft.draft.picks).includes('band_bldsk'), 'seed 69: the AI already took the default');
|
||||
await a.waitFor('m.public', (p) => p.phase === 'PREP' && p.round === 1, 10000);
|
||||
for (const msg of [{ t: 'g.buy', slot: 0 }, { t: 'g.ready', ready: true }, { t: 'g.refresh' }]) {
|
||||
const r = await s.request(msg);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// test/render/feedback4-texas-skill-clip.test.js — player report (after 0.1.3): 德克萨斯 S2「剑雨」释放时不播放技能动画.
|
||||
//
|
||||
// Her Front model's skill roles are { begin: null, loop: 'Skill', end: null } — the 2.17 s 剑雨 clip — and both her
|
||||
// skills are instant (server/sim/skills.js fires 'skill' 1 and 0 inside one tick and holds only the anim code SKILL for
|
||||
// 0.5 s, snapshot.js animOf). spine.js setSkill(true) had a branch for a Begin clip and otherwise just re-played the
|
||||
// base clip, so the skill clip never played: the actor showed its idle through the whole cast. The same clip shape
|
||||
// (no Begin, no own Idle) carries 58 instant skills across the manifest.
|
||||
//
|
||||
// Real manifest entries, headless fake PIXI (test/render/fakepixi.js), like feedback3-skill-idle.test.js.
|
||||
|
||||
import { test, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { installFakePixi } from './fakepixi.js';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const assets = JSON.parse(readFileSync(path.join(ROOT, 'data/assets.json'), 'utf8'));
|
||||
|
||||
let fake, SpineActor;
|
||||
before(async () => {
|
||||
fake = installFakePixi();
|
||||
({ SpineActor } = await import('../../public/js/render/spine.js'));
|
||||
});
|
||||
after(() => fake.restore());
|
||||
|
||||
/** An actor of a real operator's Front model with its equipped skill, recording every clip set / queued. */
|
||||
function actor(charId, skillIndex) {
|
||||
const entry = assets.chars[charId].spine.front;
|
||||
const a = new SpineActor({ animations: Object.keys(entry.animations).map((name) => ({ name })) }, entry);
|
||||
a.setSkillIndex(skillIndex);
|
||||
a.log = [];
|
||||
const st = a.spine.state;
|
||||
const set = st.setAnimation, add = st.addAnimation;
|
||||
st.setAnimation = (i, name, loop) => { a.log.push(['set', name, !!loop]); return set(i, name, loop); };
|
||||
st.addAnimation = (i, name, loop) => { a.log.push(['queue', name, !!loop]); return add(i, name, loop); };
|
||||
return a;
|
||||
}
|
||||
const run = (a, s, dt = 1 / 60) => { for (let t = 0; t < s - 1e-9; t += dt) a.update(dt); };
|
||||
const names = (a) => a.log.map(([, n]) => n);
|
||||
|
||||
test('德克萨斯 S2 剑雨: an instant skill with no Begin clip plays its Skill clip and keeps it for the clip length', () => {
|
||||
const a = actor('char_102_texas', 1);
|
||||
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.end, a.roles.skill.idle], [null, 'Skill', null, null], 'the manifest roles');
|
||||
assert.equal(a.dur('Skill'), 2.167, 'the 剑雨 clip');
|
||||
a.setSkill(true);
|
||||
assert.deepEqual(a.log, [['set', 'Skill', false]], 'the skill clip itself, played once');
|
||||
a.setSkill(false); // the sim fires 'skill' 1 and 0 in the same tick: the flag is already off
|
||||
run(a, 1);
|
||||
assert.equal(a.mode, 'skillBegin');
|
||||
assert.equal(a.current, 'Skill', 'still on screen halfway through the cast');
|
||||
run(a, 1.3);
|
||||
assert.equal(a.mode, 'base');
|
||||
assert.equal(a.current, 'Idle', 'back to the idle once the clip has run');
|
||||
assert.deepEqual(names(a), ['Skill', 'Idle'], 'no end clip, no base flash before it');
|
||||
});
|
||||
|
||||
test('the other instant skills with the same clip shape play their clip too (char_140_whitew S1 日晷, char_206_gnosis S2 零度爆发, 德克萨斯 S1)', () => {
|
||||
for (const [charId, idx, clip] of [['char_140_whitew', 0, 'Skill'], ['char_206_gnosis', 1, 'Skill_2'], ['char_102_texas', 0, 'Skill']]) {
|
||||
const a = actor(charId, idx);
|
||||
assert.equal(a.roles.skill.begin, null, `${charId} S${idx + 1}: no Begin clip`);
|
||||
assert.equal(a.roles.skill.idle, null, `${charId} S${idx + 1}: no own Idle`);
|
||||
a.setSkill(true);
|
||||
assert.equal(a.current, clip, `${charId} S${idx + 1} plays ${clip}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('a skill that stays on (银灰 S3 真银斩: same clip shape, 1.33 s clip) plays it once and then rests — no freeze on its last frame', () => {
|
||||
const a = actor('char_172_svrash', 2);
|
||||
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.idle], [null, 'Skill', null]);
|
||||
a.setSkill(true);
|
||||
assert.equal(a.current, 'Skill');
|
||||
run(a, 2);
|
||||
assert.equal(a.mode, 'base', 'the one-shot window is over');
|
||||
assert.equal(a.current, 'Idle', 'rests on the idle while the skill is still running');
|
||||
a.log.length = 0;
|
||||
a.attack(1.2);
|
||||
assert.equal(a.current, 'Skill', 'attacks still use the skill loop, as before');
|
||||
a.setSkill(false);
|
||||
assert.equal(a.current, 'Idle', 'no End clip: straight back to the idle');
|
||||
});
|
||||
|
||||
test('unchanged: a Begin clip still runs into its queued next clip, a skill with its own Idle still idles, and End still closes the skill', () => {
|
||||
const b = actor('char_4207_branch', 1); // 折桠 S2: Skill_2_Begin → Skill_2_Idle (community report #23)
|
||||
b.setSkill(true);
|
||||
assert.deepEqual(b.log, [['set', 'Skill_2_Begin', false], ['queue', 'Skill_2_Idle', true]]);
|
||||
const n = actor('char_1014_nearl2', 2); // 耀骑士临光 S3: no Begin, own Idle — plays that idle at once
|
||||
n.setSkill(true);
|
||||
assert.deepEqual(n.log, [['set', 'Skill_3_Idle', true]]);
|
||||
const u = actor('char_337_utage', 1); // 宴: Begin → loop, unchanged
|
||||
u.setSkill(true);
|
||||
assert.deepEqual(u.log, [['set', 'Skill_Start', false], ['queue', 'Skill_Loop', true]]);
|
||||
});
|
||||
|
||||
test('the deploy-time passives the sim now opens a window for play their clip (琳琅诗怀雅 S1/S2: kind passive, no duration)', () => {
|
||||
// server/sim/skills.js `_startPassive` fires ['skill', id, 1] at the deployment and 0 after SKILL_ANIM_WINDOW (0.5 s),
|
||||
// which is shorter than her 1 s clips: the actor holds the clip to its own end and then idles.
|
||||
for (const [idx, clip] of [[0, 'Skill_1'], [1, 'Skill_2']]) {
|
||||
const a = actor('char_1033_swire2', idx);
|
||||
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.idle], [null, clip, null]);
|
||||
assert.equal(a.dur(clip), 1);
|
||||
a.setSkill(true);
|
||||
assert.equal(a.current, clip, `S${idx + 1} plays ${clip} at the deployment`);
|
||||
run(a, 0.5);
|
||||
a.setSkill(false); // the sim closes the window at 0.5 s …
|
||||
run(a, 0.4);
|
||||
assert.equal(a.current, clip, '… the clip is still running (it is 1 s long)');
|
||||
run(a, 0.2);
|
||||
assert.equal(a.mode, 'base');
|
||||
assert.equal(a.current, 'Idle', 'and then back to the idle');
|
||||
}
|
||||
});
|
||||
|
||||
test('a skill clip that IS the normal attack clip is not played as a skill animation (no attack twitch at the deployment)', () => {
|
||||
const a = actor('char_1028_texas2', 0); // 缄默德克萨斯 S1: anims.skill.loop === anims.attack.loop ('Attack')
|
||||
assert.equal(a.roles.skill.loop, a.roles.attack.loop);
|
||||
a.setSkill(true);
|
||||
assert.deepEqual(a.log, [['set', 'Idle', true]], 'falls through to the base clip, as before');
|
||||
assert.equal(a.current, 'Idle');
|
||||
});
|
||||
@@ -78,7 +78,7 @@ test('耀骑士临光 S2: withdrawn at the skill end, she lies down on her tile
|
||||
const h = field([{ chessId: NEARL2, row: 10, col: 4, skillIndex: idx }]);
|
||||
const u = h.unit(NEARL2);
|
||||
h.step();
|
||||
assert.ok(u.alive && u.findBuff('nearl2:night'), 'S2 on deploy');
|
||||
assert.ok(u.alive && u.skill.active, 'S2 on deploy');
|
||||
assert.ok(h.runUntil(() => !u.alive, 40), 'withdraws when the skill ends');
|
||||
assert.equal(u.removeReason, 'retreat');
|
||||
const d = downOf(h, u);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Regression checks for 突袭 landing terrain, using the shared server/browser simulation.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeBattle, flatStage, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
||||
import { inRange } from '../../server/sim/content/support/index.js';
|
||||
|
||||
const HOME = [12, 3];
|
||||
const ENEMY = [10, 8];
|
||||
const BONDS = { raidShip: { count: 2, active: true, tier: 1, layers: 0 } };
|
||||
|
||||
function scenario(ready, noLanding = false) {
|
||||
const stage = flatStage();
|
||||
const put = (r, c, glyph) => { stage.rows[r] = stage.rows[r].slice(0, c) + glyph + stage.rows[r].slice(c + 1); };
|
||||
// Every tile in the landing search area is high ground or water except the one dry shore tile.
|
||||
for (let r = 9; r <= 12; r++) for (let c = 6; c <= 10; c++) put(r, c, 'h');
|
||||
put(...ENEMY, 'd');
|
||||
put(10, 9, 'r'); // A shooting platform: low ground in the legend, but unavailable to melee.
|
||||
if (!noLanding) put(9, 8, 'r');
|
||||
const h = makeBattle({
|
||||
stage, bonds: BONDS,
|
||||
defs: {
|
||||
chess: { raider: chessRec({
|
||||
id: 'raider', profession: 'WARRIOR', position: 'MELEE', bonds: ['raidShip'],
|
||||
rangeGrid: [[0, 0], [0, 1], [1, 0], [0, -1], [-1, 0]],
|
||||
skill: ready ? { spCost: 10, initSp: 10 } : null,
|
||||
}) },
|
||||
enemies: { enemy_wader: enemyRec({ key: 'enemy_wader', hp: 1e7, speed: 0 }) },
|
||||
},
|
||||
units: [{ chessId: 'raider', row: HOME[0], col: HOME[1] }],
|
||||
enemies: [{ key: 'enemy_wader', pos: ENEMY }],
|
||||
setup(b) { b.setObstacle(10, 9, true); },
|
||||
});
|
||||
h.step();
|
||||
assert.equal(h.unit('raider').alive, true);
|
||||
assert.equal(h.b.grid.canStand(10, 7), false, 'high ground is not a melee landing tile');
|
||||
assert.equal(h.b.grid.canStand(...ENEMY), false, 'water is not a landing tile');
|
||||
assert.equal(h.b.grid.canStand(10, 9), false, 'shooting platform is not a melee landing tile');
|
||||
return h;
|
||||
}
|
||||
|
||||
for (const ready of [false, true]) {
|
||||
const trigger = ready ? 'skill ready' : 'idle for 10 seconds';
|
||||
test(`突袭 (${trigger}): skips high ground, water and shooting platforms, landing on dry ground`, () => {
|
||||
const h = scenario(ready);
|
||||
const u = h.unit('raider');
|
||||
h.run(ready ? 0.6 : 11);
|
||||
assert.deepEqual([u.tileR, u.tileC], [9, 8], 'the only legal shore tile');
|
||||
assert.ok(u.alive && u.deployed && u.ground);
|
||||
assert.ok(inRange(u, h.enemy('enemy_wader')), 'the enemy is in range after the jump');
|
||||
const landings = h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial);
|
||||
assert.equal(landings.length, 1, 'one successful raid redeployment');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test(`突袭 (${trigger}): stays deployed when only high ground, water and platforms surround the enemy`, () => {
|
||||
const h = scenario(ready, true);
|
||||
const u = h.unit('raider');
|
||||
h.run(12);
|
||||
assert.deepEqual([u.tileR, u.tileC], HOME);
|
||||
assert.ok(u.alive && u.deployed, 'no withdrawal without a valid landing');
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === u).length, 0);
|
||||
assert.equal(h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial).length, 0);
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
}
|
||||
@@ -4,15 +4,160 @@ import assert from 'node:assert/strict';
|
||||
import { makeBattle, chessRec, enemyRec } from '../helpers/battleHarness.js';
|
||||
import { genericSkillSpec, genericKind } from '../../server/sim/content/generic.js';
|
||||
import { getDefaultSource } from '../../server/sim/simdata.js';
|
||||
import { UF } from '../../shared/constants.js';
|
||||
|
||||
const approx = (a, b, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps, `${a} ≈ ${b}`);
|
||||
const RANGE3 = [[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3], [-1, 0], [-1, 1], [-1, 2], [-1, 3]];
|
||||
const dummy = (o = {}) => enemyRec({ key: 'enemy_dummy', hp: 1e7, speed: 0, ...o });
|
||||
|
||||
test('deployment duration: synchronous start, one charge, countdown, carry and fresh redeployment', () => {
|
||||
for (const carryState of [undefined, { hpPct: 0.5, sp: 0, skillActive: true }]) {
|
||||
const order = [];
|
||||
const h = makeBattle({
|
||||
defs: { chess: { t_deploy: sniperWith({ spCost: 0 }, { id: 't_deploy' }) } },
|
||||
units: [{ chessId: 't_deploy', row: 10, col: 4, carryState }], timeLimit: 120,
|
||||
kits: { t_deploy: () => ({ skill: {
|
||||
kind: 'duration', activateOnDeploy: true, duration: 4, spCost: 0, spType: 'none', trigger: 'NEVER', mods: { atkPct: 1 },
|
||||
} }) },
|
||||
setup(b) {
|
||||
for (const name of ['skillStart', 'deploy']) b.on(name, (c) => order.push([name, c.reason, c.unit.skill.timeLeft]));
|
||||
},
|
||||
});
|
||||
h.b.start();
|
||||
const u = h.unit('t_deploy'), sk = u.skill;
|
||||
assert.deepEqual(order, [['skillStart', 'deploy', 4], ['deploy', undefined, 4]]);
|
||||
assert.equal(sk.active, true);
|
||||
assert.equal(sk.activations, 1);
|
||||
assert.equal(sk.charges, 0);
|
||||
assert.equal(sk.ready, false);
|
||||
approx(u.s.atk, 200);
|
||||
const tuple = () => h.snapshot().units.find((t) => t[0] === u.id);
|
||||
assert.ok(tuple()[7] & UF.SKILL);
|
||||
assert.deepEqual(tuple().slice(5, 7), [4, 4]);
|
||||
h.run(2);
|
||||
approx(sk.timeLeft, 2);
|
||||
assert.deepEqual(tuple().slice(5, 7), [2, 4]);
|
||||
h.run(2.1);
|
||||
assert.equal(sk.active, false);
|
||||
assert.equal(sk.ready, false);
|
||||
assert.equal(sk.charges, 0);
|
||||
assert.equal(sk.sp, 0);
|
||||
approx(u.s.atk, 100);
|
||||
assert.ok(!(tuple()[7] & UF.SKILL));
|
||||
assert.deepEqual(tuple().slice(5, 7), [0, 0]);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['duration']);
|
||||
assert.equal(sk.gainSp(100, 'talent'), 0);
|
||||
h.run(5);
|
||||
assert.equal(sk.activations, 1);
|
||||
h.b.retreat(u);
|
||||
assert.equal(h.hooksOf('skillEnd').length, 1, 'expired skill does not end again on retreat');
|
||||
assert.ok(h.b.redeploy(u, { free: true }));
|
||||
assert.equal(sk.activations, 2);
|
||||
approx(sk.timeLeft, 4);
|
||||
assert.equal(sk.charges, 0);
|
||||
h.invariants();
|
||||
}
|
||||
});
|
||||
|
||||
test('deployment duration: early retreat/death cleans effects and leaves no stale end on redeployment', () => {
|
||||
for (const remove of ['retreat', 'kill']) {
|
||||
const h = makeBattle({
|
||||
defs: { chess: { t_deploy: sniperWith({ spCost: 0 }, { id: 't_deploy' }) } },
|
||||
units: [{ chessId: 't_deploy', row: 10, col: 4 }],
|
||||
kits: { t_deploy: () => ({ skill: {
|
||||
kind: 'duration', activateOnDeploy: true, duration: 4, spCost: 0, spType: 'none', trigger: 'NEVER', mods: { aspd: 50 },
|
||||
} }) },
|
||||
});
|
||||
const u = h.unit('t_deploy');
|
||||
h.run(1);
|
||||
h.b[remove](u);
|
||||
assert.equal(u.skill.active, false);
|
||||
approx(u.s.aspd, u.base.aspd);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['death']);
|
||||
assert.ok(h.b.redeploy(u, { free: true }));
|
||||
h.run(3.1);
|
||||
assert.equal(u.skill.active, true, 'old deployment deadline does not end the new window');
|
||||
assert.equal(h.hooksOf('skillEnd').length, 1);
|
||||
h.run(1);
|
||||
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['death', 'duration']);
|
||||
assert.equal(u.skill.ready, false);
|
||||
h.invariants();
|
||||
}
|
||||
});
|
||||
|
||||
function sniperWith(skill, o = {}) {
|
||||
return chessRec({ id: 't_sn', profession: 'SNIPER', subProfessionId: 'closerange', stats: { atk: 100, bat: 1, ...(o.stats || {}) }, rangeGrid: RANGE3, skill, ...o });
|
||||
}
|
||||
|
||||
test('generic timed passive: deployment window owns stats and arts hits without reinterpreting passive proc parameters; permanent passive stays on', () => {
|
||||
const h = makeBattle({
|
||||
defs: { chess: {
|
||||
t_sn: sniperWith({ skillType: 'PASSIVE', duration: 0, spCost: 0,
|
||||
description: '部署后在4秒内攻击力+100%,攻击造成法术伤害',
|
||||
bb: { atk: 1, duration: 4, max_target: 2, 'attack@cold': 1, ability_range_forward_extend: 1 } }),
|
||||
t_p: sniperWith({ skillType: 'PASSIVE', duration: 0, spCost: 0, description: '攻击力+100%', bb: { atk: 1, duration: 4 } }, { id: 't_p' }),
|
||||
}, enemies: { enemy_dummy: dummy() } },
|
||||
units: [{ chessId: 't_sn', row: 10, col: 4 }, { chessId: 't_p', row: 12, col: 4 }],
|
||||
content: 'generic', captureNoisy: true,
|
||||
});
|
||||
h.b.start();
|
||||
const u = h.unit('t_sn'), p = h.unit('t_p');
|
||||
assert.equal(u.skill.kind, 'duration');
|
||||
assert.equal(u.skill.activations, 1);
|
||||
assert.equal(u.skill.ready, false);
|
||||
assert.deepEqual(u.rangeKeys, u.baseRangeKeys, 'passive parameters do not extend the attack range');
|
||||
h.spawn('enemy_dummy', { pos: [10, 6] });
|
||||
h.spawn('enemy_dummy', { pos: [10, 6] });
|
||||
h.run(2);
|
||||
approx(u.s.atk, 200);
|
||||
const hits = h.hooksOf('damaged').filter((c) => c.source === u);
|
||||
assert.ok(hits.length && hits.every((c) => c.type === 'arts'));
|
||||
assert.equal(new Set(hits.map((c) => c.target)).size, 1, 'passive max_target does not turn normal attacks into multi-target attacks');
|
||||
assert.deepEqual(h.snapshot().units.find((t) => t[0] === u.id).slice(5, 7), [2, 4]);
|
||||
h.run(2.1);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.charges, 0);
|
||||
approx(u.s.atk, 100);
|
||||
assert.deepEqual(u.rangeKeys, u.baseRangeKeys);
|
||||
h.run(2);
|
||||
const late = h.hooksOf('damaged').filter((c) => c.source === u && c.t > 4.5);
|
||||
assert.ok(late.length && late.every((c) => c.type === 'phys'));
|
||||
assert.equal(h.hooksOf('statusApplied').filter((c) => c.source === u && c.status === 'cold').length, 0,
|
||||
'passive proc parameters do not become attack statuses');
|
||||
assert.equal(p.skill.kind, 'passive');
|
||||
assert.equal(p.skill.active, true);
|
||||
approx(p.s.atk, 200);
|
||||
h.invariants();
|
||||
});
|
||||
|
||||
test('generic 宴 S2: real normal/elite descriptions convert damage to arts only during the deployment window', () => {
|
||||
const ds = getDefaultSource();
|
||||
for (const id of ['chess_char_1_18_a', 'chess_char_1_18_b']) {
|
||||
const index = ds.rawChess(id).skills.find((s) => s.skillId === 'skchr_utage_2').index;
|
||||
const h = makeBattle({
|
||||
content: 'generic', timeLimit: 40, captureNoisy: true,
|
||||
units: [{ chessId: id, skillIndex: index, row: 10, col: 4 }],
|
||||
defs: { enemies: { enemy_dummy: dummy({ atk: 0 }) } },
|
||||
});
|
||||
h.b.start();
|
||||
const u = h.unit(id);
|
||||
h.spawn('enemy_dummy', { pos: [10, 4] });
|
||||
const hits = () => h.hooksOf('damaged').filter((c) => c.source === u && c.dmg.isAttack);
|
||||
h.run(2);
|
||||
assert.ok(hits().length > 0 && hits().every((c) => c.type === 'arts'), `${id}: active S2 deals arts damage`);
|
||||
h.run(u.skill.timeLeft + h.TICK);
|
||||
assert.equal(u.skill.active, false);
|
||||
assert.equal(u.skill.ready, false);
|
||||
const endedAt = hits().length;
|
||||
h.run(3);
|
||||
const late = hits().slice(endedAt);
|
||||
assert.ok(late.length > 0 && late.every((c) => c.type === 'phys'), `${id}: normal physical attacks after expiry`);
|
||||
assert.equal(u.skill.activations, 1);
|
||||
assert.deepEqual(h.b.errors, []);
|
||||
h.invariants();
|
||||
}
|
||||
});
|
||||
|
||||
test('SP: time regen, starts at initSp, capped at cost; DEFAULT waits for an enemy in the initial range', () => {
|
||||
const h = makeBattle({
|
||||
defs: { chess: { t_sn: sniperWith({ spCost: 5, initSp: 2, duration: 3, bb: { atk: 1 } }) }, enemies: { enemy_dummy: dummy() } },
|
||||
@@ -288,3 +433,33 @@ test('spCostMul changes (绝技-style) keep SP within bounds and convert overflo
|
||||
assert.equal(u.skill.charges, 1);
|
||||
assert.ok(u.skill.sp <= u.skill.spCost);
|
||||
});
|
||||
|
||||
test('a deploy-time passive fires the skill animation window; a deploy-timed skill runs its duration (PR #109)', () => {
|
||||
// 琳琅诗怀雅 S1 仗义疏财 / S2 “见面礼”: kind 'passive', no duration — the sim used to start them silently, so the
|
||||
// client got no 'skill' event and never played the clip the manifest carries (player report follow-up). The window is
|
||||
// the same one an instant cast uses: on at the deployment, off 0.5 s later (SKILL_ANIM_WINDOW), the passive itself
|
||||
// staying active (it never ends before the unit does).
|
||||
const h = makeBattle({ units: [{ chessId: 'chess_char_3_04_a', row: 10, col: 6 }], autoFinish: false, timeLimit: 30 });
|
||||
h.run(0.2);
|
||||
const u = h.unit('chess_char_3_04_a');
|
||||
assert.equal(u.skill.kind, 'passive');
|
||||
assert.equal(u.skill.active, true, 'the passive is active from the deployment');
|
||||
assert.deepEqual(h.eventsOf('skill').map((e) => e[2]), [1], 'the window opens at the deployment');
|
||||
assert.ok(u.skillAnimUntil > h.b.time, 'skillAnimUntil drives ANIM.SKILL on the client');
|
||||
h.run(0.6);
|
||||
assert.deepEqual(h.eventsOf('skill').map((e) => e[2]), [1, 0], 'and closes 0.5 s later');
|
||||
assert.equal(u.skill.active, true, 'the passive itself never ends');
|
||||
|
||||
// a deploy-timed skill (缄默德克萨斯 S2 阵雨连绵, 8 s) is a real duration skill since PR #109: on at the deployment
|
||||
// (its own skill event, no 0.5 s window), off when the duration runs out, not ready again in that deployment
|
||||
const t = makeBattle({ units: [{ chessId: 'chess_char_4_16_a', row: 10, col: 6, skillIndex: 1 }], autoFinish: false, timeLimit: 30 });
|
||||
t.run(0.6);
|
||||
const tx = t.unit('chess_char_4_16_a');
|
||||
assert.equal(tx.skill.kind, 'duration');
|
||||
assert.equal(tx.skill.active, true);
|
||||
assert.deepEqual(t.eventsOf('skill').map((e) => e[2]), [1], 'on at the deployment');
|
||||
t.run(12);
|
||||
assert.equal(tx.skill.active, false, 'the duration ran out');
|
||||
assert.deepEqual(t.eventsOf('skill').map((e) => e[2]), [1, 0], 'and the skill ended');
|
||||
assert.equal(tx.skill.ready, false, 'one charge per deployment');
|
||||
});
|
||||
+80
-2
@@ -6,7 +6,7 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { bgmKeyFor, resolveBgm, SfxLimiter, AudioManager, normalAttackSfx } from '../../public/js/audio.js';
|
||||
import { bgmKeyFor, resolveBgm, SfxLimiter, AudioManager, normalAttackSfx, installAudio, audio, combatTrackFor, COMBAT_TRACK_SWITCH_ROUND } from '../../public/js/audio.js';
|
||||
import { mediaUrl } from '../../public/js/media.js';
|
||||
import { PHASE } from '../../shared/constants.js';
|
||||
|
||||
@@ -32,18 +32,53 @@ describe('bgm selection', () => {
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT }), 'combat');
|
||||
// 联防 has its own track: the official escaped_single / escaped_multi levels declare bgmEvent = corrosion
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.UNITE }), 'unite');
|
||||
// 开战 BGM: the round's own track (combatTrackFor) indexes the manifest's `bgm.combatAlts`
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT }, 0), 'combat:0');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT }, 1), 'combat:1');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.UNITE }, 1), 'unite', '联防 keeps its own track, not the round index');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.PREP }, 1), 'prep');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT, bossId: 'boss_4' }, 1), 'boss:boss_4');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT, bossId: 'boss_4' }), 'boss:boss_4');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT }), 'boss');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.HIDDEN_CORE, bossId: 'boss_1', hiddenBossId: 'boss_9' }), 'boss:boss_9');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.HIDDEN_CORE, bossId: 'boss_1', hiddenBossId: 'boss_9' }, 1), 'boss:boss_9');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.RESULT }), 'lobby');
|
||||
assert.equal(bgmKeyFor('weird', null), null);
|
||||
});
|
||||
test('开战 BGM: the track is fixed per round, not drawn (无畏者 1–7, 骑士之日 8–13)', () => {
|
||||
// The mode does not draw its battle theme: the official schedule plays 无畏者 through the early rounds and
|
||||
// 骑士之日 from round 8 on (reviewer note — review had this as a per-match 0.5 draw before).
|
||||
for (const r of [1, 2, 3, 4, 5, 6, 7]) assert.equal(combatTrackFor(r), 1, `round ${r} plays 无畏者`);
|
||||
for (const r of [8, 9, 10, 11, 12, 13]) assert.equal(combatTrackFor(r), 0, `round ${r} plays 骑士之日`);
|
||||
assert.equal(COMBAT_TRACK_SWITCH_ROUND, 7, 'the switch sits between round 7 and 8');
|
||||
// the boss rounds (14 最终攻势 / 15 隐秘核心) have their own tracks and never ask for combat:<i>
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT, round: 14 }, combatTrackFor(14)), 'boss');
|
||||
// an unknown round falls back to the manifest's plain combat track (older manifest / no round yet)
|
||||
for (const bad of [null, undefined, 0, -1, NaN, 'x']) assert.equal(combatTrackFor(bad), null, `${bad} ⇒ no index`);
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT, round: 3 }, combatTrackFor(null)), 'combat');
|
||||
// both ends of a real run: a solo 标准 match is 9 rounds, so it hears 无畏者 and then 骑士之日
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT, round: 7 }, combatTrackFor(7)), 'combat:1');
|
||||
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT, round: 8 }, combatTrackFor(8)), 'combat:0');
|
||||
// the index↔track mapping this table assumes, from docs/ASSETS.md: combatAlts[0] = m_bat_kazimierz2_1 骑士之日,
|
||||
// combatAlts[1] = m_bat_kazimierz2_2 无畏者 (a reordering upstream breaks this test, not the players' ears)
|
||||
const alt = manifest.audio.bgm.combatAlts;
|
||||
assert.ok(alt[0].loop.includes('m_bat_kazimierz2_1'), `combatAlts[0] is 骑士之日: ${alt[0].loop}`);
|
||||
assert.ok(alt[1].loop.includes('m_bat_kazimierz2_2'), `combatAlts[1] is 无畏者: ${alt[1].loop}`);
|
||||
});
|
||||
test('resolveBgm uses the manifest (boss fallback, intro optional)', () => {
|
||||
const lobby = resolveBgm(manifest, 'lobby');
|
||||
assert.ok(lobby && typeof lobby.loop === 'string');
|
||||
const b4 = resolveBgm(manifest, 'boss:boss_4');
|
||||
assert.equal(b4.loop, manifest.audio.bossBgm.boss_4.loop);
|
||||
assert.equal(resolveBgm(manifest, 'boss:nope').loop, manifest.audio.bgm.boss.loop);
|
||||
// 开战 BGM: combat:<i> → bgm.combatAlts[i], and back to the default combat track when the index (or the whole
|
||||
// array, e.g. an older manifest) is missing
|
||||
const alts = manifest.audio.bgm.combatAlts;
|
||||
assert.ok(Array.isArray(alts) && alts.length >= 2, 'manifest carries the mode’s own battle tracks');
|
||||
assert.equal(resolveBgm(manifest, 'combat:0').loop, alts[0].loop);
|
||||
assert.equal(resolveBgm(manifest, 'combat:1').loop, alts[1].loop);
|
||||
assert.notEqual(alts[0].loop, manifest.audio.bgm.combat.loop, 'a real battle track, not the shop loop');
|
||||
assert.equal(resolveBgm(manifest, 'combat:9').loop, manifest.audio.bgm.combat.loop);
|
||||
assert.equal(resolveBgm({ audio: { bgm: { combat: { loop: '/shop.mp3' } } } }, 'combat:0').loop, '/shop.mp3');
|
||||
assert.equal(resolveBgm(manifest, 'prep').intro, manifest.audio.bgm.prep.intro ?? null);
|
||||
// 联防's own track (bgm.unite = corrosion, the official escaped levels' bgmEvent), and the fallback for an older
|
||||
// manifest that has no `unite` entry (the music must not go silent)
|
||||
@@ -57,6 +92,49 @@ describe('bgm selection', () => {
|
||||
assert.equal(resolveBgm(manifest, null), null);
|
||||
assert.equal(resolveBgm(manifest, 'nope'), null);
|
||||
});
|
||||
test('installAudio: the round\'s own 开战 track, fixed per round and the same on every client', () => {
|
||||
const calls = [];
|
||||
const origPlay = audio.playBgm;
|
||||
audio.playBgm = (k) => { calls.push(k); };
|
||||
try {
|
||||
const pub = (o) => ({ phase: PHASE.PREP, round: 1, stageId: 'st1', players: [{ playerId: 'p1' }], ...o });
|
||||
const state = { route: 'game', match: { public: pub({}) } };
|
||||
let fire = null;
|
||||
/** One client: its own store subscription (the expected keys below are what its own round yields — the point of
|
||||
* the test is that the track follows the round, never moves inside a battle and never differs between clients). */
|
||||
const wire = () => installAudio({
|
||||
getManifest: () => manifest, getState: () => state, selectRoute: (s) => s.route,
|
||||
subscribe: (fn) => { fire = fn; return () => {}; },
|
||||
});
|
||||
wire();
|
||||
assert.equal(calls.at(-1), 'prep', 'the prep keeps the shop track');
|
||||
// round 1 ⇒ 无畏者 (combatAlts[1])
|
||||
state.match.public = pub({ phase: PHASE.COMBAT });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'combat:1');
|
||||
// a re-render / teammate view inside the same battle never moves
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'combat:1');
|
||||
// 联防 has its OWN track (#110, the official escaped levels' `bgmEvent = corrosion`) — it does not inherit the
|
||||
// round's 开战 track
|
||||
state.match.public = pub({ phase: PHASE.UNITE });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'unite');
|
||||
// …and back to the round's own track in the 作战
|
||||
state.match.public = pub({ phase: PHASE.COMBAT });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'combat:1', "back to the round's own track");
|
||||
// a second client of the same room hears the same track (its own installAudio, same round)
|
||||
calls.length = 0; wire();
|
||||
assert.deepEqual(calls, ['combat:1'], 'every client of the match hears the same track');
|
||||
// round 7 is the last on 无畏者, round 8 switches to 骑士之日 (combatAlts[0]) — whoever sits in seat 1
|
||||
state.match.public = pub({ round: 7, phase: PHASE.COMBAT });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'combat:1');
|
||||
state.match.public = pub({ round: 8, phase: PHASE.COMBAT });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'combat:0');
|
||||
state.match.public = pub({ round: 8, phase: PHASE.COMBAT, players: [{ playerId: 'p2' }] });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'combat:0', 'the track does not depend on the seats');
|
||||
// the boss rounds keep their own tracks
|
||||
state.match.public = pub({ phase: PHASE.FINAL_ASSAULT, bossId: 'boss_4' });
|
||||
fire(state, {}); assert.equal(calls.at(-1), 'boss:boss_4');
|
||||
} finally { audio.playBgm = origPlay; }
|
||||
});
|
||||
});
|
||||
|
||||
describe('SfxLimiter', () => {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Issue #142: the real match HUD in the mock harness, using the DOM field so optional art / WebGL is not required.
|
||||
// SP_E2E=1 CHROME_PATH=/path/to/chrome node --test test/ui/bond-collapse.e2e.test.js
|
||||
import { test, describe, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const CHROME = process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
||||
const ENABLED = process.env.SP_E2E === '1' && existsSync(CHROME);
|
||||
const OUT = fileURLToPath(new URL('../e2e/out/', import.meta.url));
|
||||
|
||||
describe('collapsible bond strip (issue #142)', { skip: !ENABLED && 'set SP_E2E=1 and CHROME_PATH to run' }, () => {
|
||||
let srv, browser;
|
||||
before(async () => {
|
||||
const { startServer } = await import('../../server/index.js');
|
||||
const puppeteer = (await import('puppeteer-core')).default;
|
||||
srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true });
|
||||
browser = await puppeteer.launch({ executablePath: CHROME, headless: true, args: ['--no-sandbox', '--no-proxy-server'] });
|
||||
mkdirSync(OUT, { recursive: true });
|
||||
});
|
||||
after(async () => { await browser?.close(); await srv?.close(); });
|
||||
|
||||
for (const [width, height, touch] of [[1920, 1080, false], [640, 360, true]]) {
|
||||
test(`collapse releases field input and keeps current bonds at ${width}×${height}`, async () => {
|
||||
const page = await browser.newPage();
|
||||
const errors = [];
|
||||
page.on('pageerror', (e) => errors.push(e.message));
|
||||
const activate = (selector) => touch ? page.tap(selector) : page.click(selector);
|
||||
const expanded = () => page.$eval('.bonds-toggle', (el) => el.getAttribute('aria-expanded'));
|
||||
try {
|
||||
await page.setViewport({ width, height, isMobile: touch, hasTouch: touch });
|
||||
await page.goto(`${srv.url}/dev/game-mock.html?shot=1&render=fallback&phase=PREP`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('.ff-piece');
|
||||
await page.waitForSelector('.bslot .bond');
|
||||
assert.equal(await expanded(), 'true');
|
||||
const toggle = await page.$eval('.bonds-toggle', (el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
return { width: r.width, height: r.height, controls: el.getAttribute('aria-controls') };
|
||||
});
|
||||
assert.ok(toggle.width >= 44 && toggle.height >= 44, 'visible touch target is at least 44×44');
|
||||
assert.equal(toggle.controls, 'match-bond-strip');
|
||||
const covered = await page.$eval('.bslot .bond', (el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
||||
});
|
||||
await activate('.bslot .bond');
|
||||
await page.waitForSelector('.bpop');
|
||||
await activate('.bonds-toggle');
|
||||
await page.waitForSelector('#match-bond-strip', { hidden: true });
|
||||
assert.equal(await expanded(), 'false');
|
||||
assert.equal(await page.$('.bpop'), null, 'collapsing closes the strip popup');
|
||||
assert.equal(await page.$eval('.bonds-toggle', (el) => el.getAttribute('aria-label')), '展开盟约');
|
||||
assert.ok(await page.evaluate(({ x, y }) => !!document.elementFromPoint(x, y)?.closest('.gm__field'), covered),
|
||||
'the former bond position now hits the field');
|
||||
await page.evaluate(() => {
|
||||
globalThis.__fieldPresses = 0;
|
||||
document.querySelector('.gm__field').addEventListener('pointerdown', () => globalThis.__fieldPresses++);
|
||||
});
|
||||
if (touch) await page.touchscreen.tap(covered.x, covered.y);
|
||||
else await page.mouse.click(covered.x, covered.y);
|
||||
assert.equal(await page.evaluate(() => globalThis.__fieldPresses), 1, 'pointer input reaches the field');
|
||||
await page.screenshot({ path: `${OUT}/bonds-collapsed-${width}.png` });
|
||||
|
||||
// Hidden content must keep following state changes, including the empty-bonds state.
|
||||
await page.evaluate(() => globalThis.__MOCK__.mutate((s) => { s.priv.board = []; s.priv.hand.fill(null); }));
|
||||
await activate('.bonds-toggle');
|
||||
await page.waitForSelector('.bstrip--empty', { visible: true });
|
||||
assert.equal(await expanded(), 'true');
|
||||
await page.focus('.bonds-toggle');
|
||||
await page.keyboard.press('Space');
|
||||
await page.waitForSelector('#match-bond-strip', { hidden: true });
|
||||
assert.equal(await page.evaluate(() => globalThis.__MOCK__.S().priv.ready), false, 'Space on the toggle does not ready the player');
|
||||
assert.equal(await page.$('.modal'), null, 'Space does not open the funds confirmation');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForSelector('.bstrip--empty', { visible: true });
|
||||
|
||||
await activate('.bonds-toggle');
|
||||
await activate('.team__row:not(.is-self) .team__btn');
|
||||
if (await page.$('.team__ob')) await activate('.team__ob');
|
||||
await page.waitForSelector('.gm__watching');
|
||||
assert.equal(await expanded(), 'false', 'watching a teammate preserves the collapse choice');
|
||||
await activate('.bonds-toggle');
|
||||
await page.waitForSelector('.bstrip__owner', { visible: true });
|
||||
assert.ok(await page.$eval('.bstrip', (el) => el.getAttribute('data-owner')), 'expanded bonds belong to the watched teammate');
|
||||
await activate('.bonds-toggle');
|
||||
await page.evaluate(() => globalThis.__MOCK__.setPhase('COMBAT'));
|
||||
await page.waitForSelector('.gm--combat');
|
||||
assert.equal(await expanded(), 'false', 'phase changes preserve the collapse choice');
|
||||
await activate('.bonds-toggle');
|
||||
await page.waitForSelector('.bslot .bond', { visible: true });
|
||||
assert.equal(await expanded(), 'true');
|
||||
await page.screenshot({ path: `${OUT}/bonds-expanded-${width}.png` });
|
||||
assert.deepEqual(errors, []);
|
||||
} finally { await page.close(); }
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -28,7 +28,7 @@ globalThis.fetch = async (url) => {
|
||||
const { modeOffBonds } = await import('../../public/js/ui/gameLogic.js');
|
||||
const { BondChips } = await import('../../public/js/ui/detailPanel.js');
|
||||
const { ChessCard } = await import('../../public/js/ui/shopBar.js');
|
||||
const { BondPopup } = await import('../../public/js/ui/bondStrip.js');
|
||||
const { BondPopup, BondStrip } = await import('../../public/js/ui/bondStrip.js');
|
||||
const { data, getMode } = await import('../../public/js/data.js');
|
||||
const { DATA, makeMatch, give, legalTileFor } = await import('../match/harness.js');
|
||||
const { tileKey } = await import('../../server/match/board.js');
|
||||
@@ -120,3 +120,21 @@ test('the game screen hands the mode-disabled set to the shop bar, the detail ca
|
||||
assert.match(src, /<\$\{DetailPanel\}[\s\S]*?offBonds=\$\{offBonds\}/);
|
||||
assert.match(src, /<\$\{BondPopup\}[\s\S]*?off=\$\{offBonds\.has\(bondPop\.bondId\)\}/);
|
||||
});
|
||||
|
||||
test('hidden-layer bonds show activation and tiers without a stack badge or layer count', () => {
|
||||
for (const bondId of ['maniShip', 'emptyShip', 'soloShip', 'suntShip']) {
|
||||
const entry = { bondId, active: true, count: 2, tier: 1, layers: 99 };
|
||||
const pop = BondPopup({ bondId, entry, priv: { board: [], hand: [] }, onClose() {} });
|
||||
assert.match(textOf(pop), /层数不显示/);
|
||||
assert.doesNotMatch(textOf(pop), /99|99 层/);
|
||||
const strip = BondStrip({ bonds: [entry], onOpen() {} });
|
||||
const disc = [...walk(strip)].find((v) => v.props?.name === DATA.bonds[bondId].name);
|
||||
assert.ok(disc);
|
||||
assert.equal(disc.props.layers, undefined);
|
||||
assert.equal(disc.props.active, true);
|
||||
assert.equal(disc.props.tier, 1);
|
||||
}
|
||||
const disc = [...walk(BondStrip({ bonds: [{ bondId: 'yanShip', active: true, layers: 12 }], onOpen() {} }))]
|
||||
.find((v) => v.props?.name === DATA.bonds.yanShip.name);
|
||||
assert.equal(disc.props.layers, 12, 'stacking bonds keep their badge');
|
||||
});
|
||||
+13
-20
@@ -245,28 +245,21 @@ describe('placement mirror (canPlace)', () => {
|
||||
assert.equal(canPlace(ctx, m.uid, null).ok, false);
|
||||
assert.equal(canPlace(ctx, m.uid, { area: 'temp', idx: 0 }).ok, false, 'no temp target');
|
||||
});
|
||||
test('only elite 歌蕾蒂娅 carrying HOK-Y lights the 高台; 崖心, 见行者, a normal record and other modules stay on the ground', () => {
|
||||
const HOK_Y = 'uniequip_003_glady';
|
||||
const gladE = piece('chess_char_4_12_b');
|
||||
const gladN = piece('chess_char_4_12_a');
|
||||
const cliff = piece('chess_char_2_03_b');
|
||||
const forcer = piece('chess_char_3_07_b');
|
||||
test('every trait holder (歌蕾蒂娅, 崖心, 见行者, normal and elite) lights the 高台 whatever the module; a plain melee stays on the ground', () => {
|
||||
const holders = ['chess_char_4_12_a', 'chess_char_4_12_b', 'chess_char_2_03_a', 'chess_char_2_03_b', 'chess_char_3_07_a', 'chess_char_3_07_b'].map((id) => piece(id));
|
||||
const m = piece(MELEE);
|
||||
const loadout = { chess_char_4_12_a: { module: HOK_Y } };
|
||||
const ctx = ctxFor(privWith({ hand: [gladE, gladN, cliff, forcer, m], loadout }));
|
||||
for (const [row, col] of [[10, 4], [11, 4], [12, 4], [9, 3]]) assert.equal(canPlace(ctx, gladE.uid, { area: 'board', row, col }).ok, true, `elite HOK-Y on ${row},${col}`);
|
||||
const lit = boardTargets(ctx, gladE.uid).legal.map(([a, b]) => tileKey(a, b));
|
||||
assert.equal(lit.length, STAGE.deployTiles.normal.melee.length + STAGE.deployTiles.normal.rangedOnly.length, 'every deploy tile lit');
|
||||
for (const p of [gladN, cliff, forcer, m]) {
|
||||
assert.deepEqual(canPlace(ctx, p.uid, { area: 'board', row: 10, col: 4 }), { ok: false, code: 'BAD_TILE', reason: '近战单位只能部署在地面' }, p.id);
|
||||
assert.equal(canPlace(ctx, p.uid, { area: 'board', row: 9, col: 3 }).ok, true, `${p.id} on the ground`);
|
||||
const every = STAGE.deployTiles.normal.melee.length + STAGE.deployTiles.normal.rangedOnly.length;
|
||||
// the module does not matter: the default, HOK-Y, HOK-X and none on 歌蕾蒂娅; 崖心's HOK-X; none on 见行者
|
||||
for (const loadout of [null, { chess_char_4_12_a: { module: 'uniequip_003_glady' } }, { chess_char_4_12_a: { module: 'uniequip_002_glady' } },
|
||||
{ chess_char_4_12_a: { module: 'none' }, chess_char_2_03_a: { module: 'none' }, chess_char_3_07_a: { module: 'none' } }]) {
|
||||
const ctx = ctxFor(privWith({ hand: [...holders, m], loadout }));
|
||||
for (const p of holders) {
|
||||
for (const [row, col] of [[10, 4], [11, 4], [12, 4], [9, 3]]) assert.equal(canPlace(ctx, p.uid, { area: 'board', row, col }).ok, true, `${p.id} on ${row},${col} (${JSON.stringify(loadout)})`);
|
||||
assert.equal(boardTargets(ctx, p.uid).legal.length, every, `${p.id}: every deploy tile lit`);
|
||||
}
|
||||
assert.deepEqual(canPlace(ctx, m.uid, { area: 'board', row: 10, col: 4 }), { ok: false, code: 'BAD_TILE', reason: '近战单位只能部署在地面' }, '角峰');
|
||||
assert.equal(canPlace(ctx, m.uid, { area: 'board', row: 9, col: 3 }).ok, true, '角峰 on the ground');
|
||||
}
|
||||
const none = ctxFor(privWith({ hand: [gladE], loadout: { chess_char_4_12_a: { module: 'none' } } }));
|
||||
assert.equal(canPlace(none, gladE.uid, { area: 'board', row: 10, col: 4 }).ok, false, 'no module');
|
||||
const xmod = ctxFor(privWith({ hand: [gladE], loadout: { chess_char_4_12_a: { module: 'uniequip_002_glady' } } }));
|
||||
assert.equal(canPlace(xmod, gladE.uid, { area: 'board', row: 10, col: 4 }).ok, false, 'HOK-X');
|
||||
const bare = ctxFor(privWith({ hand: [gladE] }));
|
||||
assert.equal(canPlace(bare, gladE.uid, { area: 'board', row: 10, col: 4 }).ok, false, 'default module is HOK-X');
|
||||
});
|
||||
test('not editable ⇒ nothing is legal', () => {
|
||||
const m = piece(MELEE);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
// The chess: elite 野鬃 (chess_char_1_19_b). Default = S2 夹枪冲锋 (25/40 SP) + module 长枪替补套装 (ATK +40, ASPD +3).
|
||||
// Chosen here: S1 骑枪刺击 (ON_DEPLOY: "部署后攻击速度+100" for 25 s) + 不装备 — so right after the unit deploys the
|
||||
// local battle must show the S1 buff (wildmn:s1, ASPD +100, skill active) and the no-module stats (ATK 524, ASPD 100).
|
||||
// local battle must show S1 active with a draining duration bar, ASPD +100 and the no-module stats (ATK 524, ASPD 100).
|
||||
// The server is test/e2e/fastServer.mjs with its starter-kit hook (SP_START_CHESS: the elite in the hand at round 1);
|
||||
// everything else is the real match engine and the real UI driven by real mouse input.
|
||||
// Screenshots: test/e2e/out/loadout-battle-*.png.
|
||||
@@ -16,6 +16,9 @@ import assert from 'node:assert/strict';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { Client, ROOT, sleep, hasChrome, startRealServer, problemsOf } from '../e2e/client.mjs';
|
||||
import { makeBattle } from '../helpers/battleHarness.js';
|
||||
import { UF } from '../../shared/constants.js';
|
||||
import { COLORS } from '../../public/js/render/style.js';
|
||||
|
||||
const ENABLED = process.env.SP_E2E === '1' && hasChrome() && existsSync(path.join(ROOT, 'public/assets'));
|
||||
const BASE = 'chess_char_1_19_a';
|
||||
@@ -129,15 +132,16 @@ describe('user playtest #2 item 1 — loadout chosen in the UI fights in the loc
|
||||
const e = r && [...r._entries.values()].find((x) => x.own && x.battle);
|
||||
if (!e) return false;
|
||||
const u = e.battle.allyUnits.find((x) => x.uid === uid);
|
||||
if (!u || !u.findBuff?.('wildmn:s1')) return false;
|
||||
const b = u.findBuff('wildmn:s1');
|
||||
if (!u?.skill?.active || u.skill.id !== 'skchr_wildmn_1') return false;
|
||||
const tuple = e.battle.snapshot().units.find((t) => t[0] === u.id);
|
||||
const entry = (e.spec.players || []).flatMap((p) => p.units || []).find((x) => x.uid === uid) || null;
|
||||
return {
|
||||
authoritative: e.authoritative, t: e.battle.time,
|
||||
spec: entry && { chessId: entry.chessId, skillIndex: entry.skillIndex, moduleId: entry.moduleId },
|
||||
skill: u.skill?.id ?? null, source: u.kit?.skillSource ?? null, active: !!u.skill?.active,
|
||||
loadout: u.def?.loadout ?? null, atk: u.def?.stats?.atk, aspd: u.def?.stats?.aspd,
|
||||
buff: { aspd: b.mods?.aspd ?? null },
|
||||
liveAspd: u.s.aspd, timeLeft: u.skill.timeLeft, duration: u.skill.duration, ready: u.skill.ready,
|
||||
bar: tuple && { value: tuple[5], max: tuple[6] },
|
||||
};
|
||||
}, { timeout: 30000, polling: 100 }, piece.uid).then((h) => h.jsonValue())
|
||||
.catch(async (err) => {
|
||||
@@ -157,7 +161,11 @@ describe('user playtest #2 item 1 — loadout chosen in the UI fights in the loc
|
||||
assert.equal(got.atk, rec.statsBase.atk, 'no module: base ATK (the default module adds +40)');
|
||||
assert.equal(got.aspd, rec.statsBase.aspd, 'no module: base ASPD');
|
||||
assert.notEqual(rec.stats.atk, rec.statsBase.atk, 'control: the default module would change ATK');
|
||||
assert.equal(got.buff.aspd, s1.bb.attack_speed, 'S1 signature: ASPD +100 on deploy');
|
||||
assert.equal(got.liveAspd - got.aspd, s1.bb.attack_speed, 'S1 signature: ASPD +100 on deploy');
|
||||
assert.equal(got.ready, false, 'deployment consumed its sole charge');
|
||||
assert.equal(got.duration, s1.duration);
|
||||
assert.equal(got.bar.max, s1.duration, 'zero-SP skill has a duration bar');
|
||||
assert.ok(Math.abs(got.bar.value - got.timeLeft) <= 0.06);
|
||||
assert.ok(got.active, 'S1 is active right after deploying');
|
||||
assert.ok(got.t < 10, `S1 fires on deploy (t = ${got.t}), long before S2 could charge (25/40 SP)`);
|
||||
await sleep(800);
|
||||
@@ -171,4 +179,63 @@ describe('user playtest #2 item 1 — loadout chosen in the UI fights in the loc
|
||||
await srv.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test('野鬃 S1 duration bar: full on deploy, half remaining, hidden after expiry (real renderer)', { timeout: 120000 }, async () => {
|
||||
const h = makeBattle({
|
||||
stageId: 'act2autochess_m01', autoFinish: false, timeLimit: 60,
|
||||
units: [{ chessId: ELITE, skillIndex: 0, moduleId: 'none', row: 9, col: 5 }],
|
||||
});
|
||||
h.b.start();
|
||||
const u = h.unit(ELITE);
|
||||
assert.equal(u.skill.id, 'skchr_wildmn_1');
|
||||
const meta = { ...h.b.fieldMeta(), stageId: 'act2autochess_m01' };
|
||||
const frames = [];
|
||||
for (const [name, elapsed, share] of [['full', 0, 1], ['half', u.skill.duration / 2, 0.5], ['ended', u.skill.duration / 2 + h.TICK, 0]]) {
|
||||
h.run(elapsed);
|
||||
const { t, ...rest } = h.snapshot();
|
||||
frames.push({ name, share, snap: { ...rest, t: 'b.snap', gt: t } });
|
||||
assert.equal(u.skill.ready, false, `${name}: no remaining deploy charge`);
|
||||
assert.equal(u.skill.active, name !== 'ended');
|
||||
}
|
||||
const srv = await startRealServer();
|
||||
const P = (await import('puppeteer-core')).default;
|
||||
const c = new Client(P, srv.base, 'duration', { prefix: 'loadout-battle' });
|
||||
try {
|
||||
await c.open();
|
||||
await c.page.goto(`${srv.base}/dev/render-demo.html?scene=normal-m01&paused=1&panel=0`);
|
||||
await c.page.waitForFunction('window.__demo && (window.__demo.ready || window.__demo.error)', { timeout: 30000 });
|
||||
assert.equal(await c.page.evaluate(() => window.__demo.error ?? null), null);
|
||||
for (const f of frames) {
|
||||
await c.page.evaluate((meta, snap) => {
|
||||
const v = window.__demo.view;
|
||||
v.enterBattle(meta);
|
||||
v.setCamera('normal', { rect: meta.rect, side: 'L', instant: true });
|
||||
v.setLocalFeed({ on: true, speed: 2 });
|
||||
v.pushSnapshot(snap);
|
||||
}, meta, f.snap);
|
||||
await c.page.waitForFunction((id, spMax) => {
|
||||
const x = window.__demo.view.debug.views.get(id);
|
||||
return x?.spineReady && x.hud.visible && x.hud.alpha > 0.99 && x.hpFill.visible && x.spMax === spMax;
|
||||
}, { timeout: 15000 }, u.id, f.snap.units.find((t) => t[0] === u.id)[6]);
|
||||
const bar = await c.page.evaluate((id) => {
|
||||
const x = window.__demo.view.debug.views.get(id);
|
||||
return { visible: x.spFill.visible, bg: x.spBg.visible, share: x.spFill.width / (x.spBg.width - 2),
|
||||
tint: x.spFill.tint, flags: x.flags, glow: x.spGlow.visible, sp: x.sp, max: x.spMax };
|
||||
}, u.id);
|
||||
assert.equal(bar.glow, false, `${f.name}: no ready highlight`);
|
||||
assert.equal(!!(bar.flags & UF.SKILL), f.name !== 'ended');
|
||||
assert.equal(bar.visible, f.name !== 'ended');
|
||||
assert.equal(bar.bg, f.name !== 'ended');
|
||||
if (bar.visible) {
|
||||
assert.ok(Math.abs(bar.share - f.share) < 0.002, `${f.name}: drawn share ${bar.share}`);
|
||||
assert.equal(bar.tint, COLORS.spActive);
|
||||
} else assert.deepEqual([bar.sp, bar.max], [0, 0]);
|
||||
await c.shot(`duration-${f.name}`);
|
||||
}
|
||||
assert.deepEqual(problemsOf([c]), []);
|
||||
} finally {
|
||||
await c.close();
|
||||
await srv.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -41,6 +41,17 @@ describe('the scouted prep board renders the hand like the own bench', () => {
|
||||
assert.match(app, /function scoutItemInfo\(info\)/);
|
||||
});
|
||||
|
||||
test('ItemView syncs battle samples: a hand item rides the scouted field without breaking syncBattle (render layer: source)', () => {
|
||||
// syncBattle syncs EVERY unit of the field's snapshot (render/app.js `v.sync(s, renderT)`); the ItemView class
|
||||
// needs a sync of its own or the frame loop throws `v.sync is not a function` on every tick and the scouted
|
||||
// board's operators never draw (found in playtesting: only teammates with an item in hand / temp broke).
|
||||
const units = read('public/js/render/units.js');
|
||||
const start = units.indexOf('export class ItemView');
|
||||
assert.ok(start > 0, 'ItemView exists');
|
||||
const body = units.slice(start, units.indexOf('export class ', start + 1));
|
||||
assert.match(body, /\bsync\(s\) \{ this\.x = s\.x; this\.y = s\.y; \}/);
|
||||
});
|
||||
|
||||
test('tapping a held item on the scouted board opens its card (resolveDetail unit → item)', () => {
|
||||
const IT = 'chess_item_1_01_e_a';
|
||||
const d = resolveDetail({ kind: 'unit', unit: { id: 3, kind: 'item', side: 'ally', ownerId: 'p2', defId: IT } }, new Map());
|
||||
|
||||
Reference in new issue
Block a user