Let charged guardian heals act without enemies and fix mixed S2 triggers (#406)
This commit is contained in:
1 parent
c9481c40bb
commit
e86c7c8304
9 files changed
+155
-50
No files matched your search
@@ -0,0 +1,71 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
||||
const GUARDIANS = [
|
||||
{ chessId: 'chess_char_5_11_b', skillIndex: 0 },
|
||||
{ diy: { slot: 6, charId: 'char_2025_shu', skillIndex: 0 }, elite: true },
|
||||
];
|
||||
function guardian(entry) {
|
||||
const h = makeBattle({ autoFinish: false, captureNoisy: true, hooks: ['heal', 'skillStart'],
|
||||
defs: { chess: { patient: chessRec({ id: 'patient', skill: null, stats: { maxHp: 100000 } }) } },
|
||||
units: [{ uid: 1, ...entry, row: 10, col: 4 }, { uid: 2, chessId: 'patient', row: 10, col: 5 }] });
|
||||
h.step();const u = h.unit(1), p = h.unit(2);u.skill.gainSp(999);return { h, u, p };
|
||||
}
|
||||
for (const entry of GUARDIANS) {
|
||||
const name = entry.chessId ? '塞雷娅' : '黍';
|
||||
test(`${name} S1 heals without enemies, respects half HP and keeps attack cadence`, () => {
|
||||
const { h, u, p } = guardian(entry);
|
||||
p.hp = p.s.maxHp * 0.5001;h.run(0.5);assert.equal(u.skill.activations, 0);
|
||||
p.hp = p.s.maxHp * 0.5;u.atkCd = 1;
|
||||
h.run(0.5);assert.equal(u.skill.activations, 0, 'wait for the attack interval');
|
||||
assert.ok(h.runUntil(() => u.skill.activations > 0, 1));
|
||||
assert.ok(p.hp > p.s.maxHp * 0.5, 'exact half HP is eligible');
|
||||
p.hp = p.s.maxHp * 0.2;
|
||||
assert.ok(h.runUntil(() => u.skill.activations >= 2, 4));
|
||||
const casts = h.hooksOf('skillStart').filter(c => c.unit === u);
|
||||
assert.ok(casts[1].t - casts[0].t >= u.base.bat - 0.04, 'charges are paced by attacks');
|
||||
checkInvariants(h.b);assert.deepEqual(h.b.errors, []);
|
||||
});
|
||||
test(`${name} S1 preserves exact normal/fast attack intervals and waits out disarm`, () => {
|
||||
for (const aspd of [0, 100]) {
|
||||
const { h, u, p } = guardian(entry);
|
||||
p.hp *= 0.1;
|
||||
if (aspd) h.b.addBuff(u, { key: 'test:aspd', mods: { aspd } });
|
||||
h.b.on('heal', c => { if (c.source === u) u.skill.gainSp(999); });
|
||||
const charges = u.skill.charges;
|
||||
h.b.applyStatus(u, 'disarm', { duration: 1 });
|
||||
h.run(0.5);
|
||||
assert.equal(u.skill.activations, 0);
|
||||
assert.equal(u.skill.pending, false);
|
||||
assert.equal(u.skill.charges, charges);
|
||||
h.run(4);
|
||||
const times = h.hooksOf('heal').filter(c => c.source === u).map(c => c.t);
|
||||
assert.ok(times.length >= 3);
|
||||
for (let i = 1; i < times.length; i++) assert.ok(Math.abs(times[i] - times[i - 1] - u.s.interval) < 1e-9);
|
||||
checkInvariants(h.b);assert.deepEqual(h.b.errors, []);
|
||||
}
|
||||
});
|
||||
test(`${name} S1 cannot heal a forbidden patient or cast while silenced; invalid pending heal refunds the charge`, () => {
|
||||
const { h, u, p } = guardian(entry);p.hp *= 0.2;
|
||||
h.b.addBuff(p, { key: 'test:noheal', flags: { noHeal: true } });h.run(2);assert.equal(u.skill.activations, 0);
|
||||
h.b.removeBuff(p, 'test:noheal');h.b.applyStatus(u, 'silence', { duration: 2 });h.run(1);assert.equal(u.skill.activations, 0);
|
||||
h.run(1.1);assert.ok(u.skill.activations > 0);
|
||||
u.skill.gainSp(999);const charges = u.skill.charges;u.atkCd = 1;
|
||||
assert.ok(u.skill.activate('test'));p.hp = p.s.maxHp;h.step();
|
||||
assert.equal(u.skill.pending, false);assert.equal(u.skill.charges, charges);
|
||||
checkInvariants(h.b);assert.deepEqual(h.b.errors, []);
|
||||
});
|
||||
}
|
||||
for (const motion of ['WALK', 'FLY']) {
|
||||
test(`凯尔希·思衡托 S2 opens for a selectable ${motion} enemy only in the expanded range, without an injured ally`, () => {
|
||||
const h = makeBattle({ autoFinish: false,
|
||||
defs: { enemies: { target: enemyRec({ key: 'target', motion, hp: 1e8, atk: 0, speed: 0 }) } },
|
||||
units: [{ uid: 1, diy: { slot: 6, charId: 'char_1052_kalts2', skillIndex: 1 }, elite: true, row: 10, col: 4 }] });
|
||||
h.step();const u = h.unit(1);u.skill.gainSp(999);h.run(0.5);assert.equal(u.skill.activations, 0);
|
||||
const e = h.spawn('target', { pos: [10, 7] });h.b.applyStatus(e, 'sleep', { duration: 1 });
|
||||
assert.equal(h.b.enemiesInKeys(u.baseRangeKeys, u, { canHitFly: true }).length, 0);
|
||||
h.run(0.5);assert.equal(u.skill.activations, 0, 'sleep cannot open the mixed skill');
|
||||
assert.ok(h.runUntil(() => u.skill.active, 1));
|
||||
assert.equal(u.skill.activations, 1);checkInvariants(h.b);assert.deepEqual(h.b.errors, []);
|
||||
});
|
||||
}
|
||||
+35
-35
@@ -2394,38 +2394,38 @@
|
||||
"round": 13,
|
||||
"seed": 4104625554,
|
||||
"waveId": "act1autochess_h06",
|
||||
"time": 213.53,
|
||||
"ticks": 6406,
|
||||
"time": 224.2,
|
||||
"ticks": 6726,
|
||||
"reason": "cleared",
|
||||
"killed": 54,
|
||||
"killed": 60,
|
||||
"total": 96,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 0,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":54,"total":96,"perfect":false,"leaks":42,"leaksUncounted":0,"coins":0,"dmg":325631,"bossDmg":0,"heal":201962,"deaths":14,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":60,"total":96,"perfect":false,"leaks":36,"leaksUncounted":0,"coins":0,"dmg":328165,"bossDmg":0,"heal":215296,"deaths":15,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_shu",0,0,6,3,28220,22841,8556,68,3],
|
||||
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_shu",0,0,4,0,3764,7244,15696,24,1],
|
||||
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_shu",0,0,7,3,36219,18295,24201,93,2],
|
||||
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_shu",0,0,9,8,29213,25714,7056,77,1],
|
||||
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_shu",0,0,11,8,40747,35870,26790,94,1],
|
||||
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_shu",0,0,13,9,55531,31153,19129,132,1],
|
||||
["p1",7,"op","chess_char_6_diy1_a",0,null,0,0,105,4,26549,22529,10129,96,5],
|
||||
["p1",8,"op","chess_char_6_diy2_a",1,null,0,0,94,2,12140,19351,10447,60,1],
|
||||
["p1",9,"op","chess_char_6_diy1_a",2,null,0,0,5,3,15284,4893,22539,66,2],
|
||||
["p1",10,"op","chess_char_6_diy2_b",0,"none",0,0,8,0,3357,5468,2925,12,1],
|
||||
["p1",11,"op","chess_char_6_diy1_b",1,"none",0,0,10,4,20997,29429,30955,60,1],
|
||||
["p1",12,"op","chess_char_6_diy2_b",2,"none",3421,1,114,5,53610,24940,23541,116,2],
|
||||
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_shu",2027.59,1,116,4,28271,24195,6202,68,1],
|
||||
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_shu",0,0,4,0,1711,23126,33329,30,2],
|
||||
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_shu",0,0,7,12,35861,22460,27238,104,2],
|
||||
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_shu",0,0,9,3,39020,25122,12684,97,4],
|
||||
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_shu",0,0,11,11,48461,41096,28378,112,1],
|
||||
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_shu",0,0,114,9,47371,22141,16394,112,1],
|
||||
["p1",7,"op","chess_char_6_diy1_a",0,null,0,0,103,5,24150,24095,7966,92,3],
|
||||
["p1",8,"op","chess_char_6_diy2_a",1,null,2764.3,1,117,1,10276,17246,9358,51,1],
|
||||
["p1",9,"op","chess_char_6_diy1_a",2,null,0,0,5,1,15673,5966,23558,68,2],
|
||||
["p1",10,"op","chess_char_6_diy2_b",0,"none",3421,1,119,1,4178,7509,4920,14,2],
|
||||
["p1",11,"op","chess_char_6_diy1_b",1,"none",3421,1,118,3,20756,28537,30597,59,1],
|
||||
["p1",12,"op","chess_char_6_diy2_b",2,"none",3421,1,115,6,52439,24767,14672,123,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],
|
||||
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||
],
|
||||
"enemies": {"enemy_1229_darmy":[27,17,10,37119,84479],"enemy_1229_darmy_2":[57,37,20,116481,226930],"enemy_9007_acelem":[12,0,12,75361,111113]},
|
||||
"hooks": [1,114,6406,1483,1483,4018,4279,24024,68,110,21,21,0,15323,0,130,96,42,1,68,0,40,0],
|
||||
"events": {"atk":1530,"deploy":15,"die":68,"dmg":4279,"engage":12,"fx":129,"heal":304,"leak":42,"skill":42,"spawn":111,"status":104},
|
||||
"fx": {"beam":18,"burst":40,"healField":35,"teleport":36},
|
||||
"snaps": "caea0314 70d99341 a190ed01 c66b7d44 cc4aa097 1a3e11e7 b9309122 fcf78826 cd12b374 81495da7 877f63e5 87ffd853 2c5e098e 7fa84dec 5d10862d d52e4b5d b483e93a 33405189 e58dd069 11d14bc3 7d55f750 e98d98ef"
|
||||
"enemies": {"enemy_1229_darmy":[27,22,5,39408,94374],"enemy_1229_darmy_2":[57,38,19,128503,234099],"enemy_9007_acelem":[12,0,12,77871,113345]},
|
||||
"hooks": [1,119,6726,1553,1553,4410,4689,25170,75,111,22,21,0,17046,0,126,96,36,1,75,0,44,0],
|
||||
"events": {"atk":1612,"deploy":20,"die":75,"dmg":4689,"engage":12,"fx":131,"heal":320,"leak":36,"skill":43,"spawn":111,"status":111},
|
||||
"fx": {"beam":23,"burst":44,"healField":36,"teleport":28},
|
||||
"snaps": "caea0314 cba33632 f90e1b1d 5e82f1ff 929abe9c 394a1be8 5e0e99e5 eab4fee5 adfb57e8 aad6dce8 05e8f51c 3738b6d0 721871ea b6845c30 4ce6f2df 47cfe53a 1562c368 1bcdcadc 929a1dfb a34d9a94 d6a40dfd b6073386 8f66b944"
|
||||
},
|
||||
"diy-060": {
|
||||
"about": "6_diy1_b=shu/S1/002_shu 6_diy2_b=shu/S2/002_shu 6_diy1_b=shu/S3/002_shu 6_diy2_b=shu/S1/003_shu 6_diy1_b=shu/S2/003_shu 6_diy2_b=shu/S3/003_shu 5_diy1_a=pallas/S1 5_diy2_a=pallas/S2 5_diy1_a=pallas/S3 5_diy2_b=pallas/S1 5_diy1_b=pallas/S2 5_diy2_b=pallas/S3",
|
||||
@@ -5769,38 +5769,38 @@
|
||||
"round": 13,
|
||||
"seed": 953190138,
|
||||
"waveId": "act1autochess_h06",
|
||||
"time": 229,
|
||||
"ticks": 6870,
|
||||
"reason": "cleared",
|
||||
"killed": 0,
|
||||
"time": 230,
|
||||
"ticks": 6900,
|
||||
"reason": "timeout",
|
||||
"killed": 97,
|
||||
"total": 123,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 0,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":0,"total":123,"perfect":false,"leaks":123,"leaksUncounted":0,"coins":0,"dmg":0,"bossDmg":0,"heal":0,"deaths":0,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":97,"total":123,"perfect":false,"leaks":26,"leaksUncounted":0,"coins":0,"dmg":3464054,"bossDmg":0,"heal":0,"deaths":0,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_5_diy1_a",0,null,1991.99,1,4,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_5_diy2_a",1,null,1991.99,1,5,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_5_diy2_a",1,null,1991.99,1,5,10,602379,0,0,40,4],
|
||||
["p1",3,"op","chess_char_5_diy1_a",2,null,1991.99,1,11,0,0,0,0,0,0],
|
||||
["p1",4,"op","chess_char_5_diy2_b",0,"none",2206.49,1,13,0,0,0,0,0,0],
|
||||
["p1",5,"op","chess_char_5_diy1_b",1,"none",2206.49,1,6,0,0,0,0,0,0],
|
||||
["p1",5,"op","chess_char_5_diy1_b",1,"none",2206.49,1,6,26,1100819,0,0,30,3],
|
||||
["p1",6,"op","chess_char_5_diy2_b",2,"none",2206.49,1,14,0,0,0,0,0,0],
|
||||
["p1",7,"op","chess_char_6_diy1_a",0,null,1991.99,1,7,0,0,0,0,0,0],
|
||||
["p1",8,"op","chess_char_6_diy2_a",1,null,1991.99,1,9,0,0,0,0,0,0],
|
||||
["p1",8,"op","chess_char_6_diy2_a",1,null,1991.99,1,9,16,531690,0,0,40,4],
|
||||
["p1",9,"op","chess_char_6_diy1_a",2,null,1991.99,1,10,0,0,0,0,0,0],
|
||||
["p1",10,"op","chess_char_6_diy2_b",0,"none",2206.49,1,12,0,0,0,0,0,0],
|
||||
["p1",11,"op","chess_char_6_diy1_b",1,"none",2206.49,1,15,0,0,0,0,0,0],
|
||||
["p1",11,"op","chess_char_6_diy1_b",1,"none",2206.49,1,15,45,1229167,0,0,40,4],
|
||||
["p1",12,"op","chess_char_6_diy2_b",2,"none",2206.49,1,8,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_1006_shield_3":[21,0,21,0,0],"enemy_1043_zomsbr":[66,0,66,0,0],"enemy_1402_tgshd_2":[36,0,36,0,0]},
|
||||
"hooks": [1,138,6870,0,0,0,0,0,0,123,0,0,0,8040,0,0,123,123,1,0,0,0,0],
|
||||
"events": {"deploy":12,"fx":72,"leak":123,"spawn":138},
|
||||
"fx": {"shield":60,"takeoff":12},
|
||||
"snaps": "73ed7f8a 7b620bb2 4d91bff8 2b838d89 4e28c0a4 4d3057b7 dd413269 d9e2dba6 2f326b81 83e4122d e47cd4da b766c561 ea452dcc 749d7006 6e97bed7 aeb7002d a0cf0a74 38ae1555 f0426635 2ddc66fa 83570095 8351e611 7abf7fb8"
|
||||
"enemies": {"enemy_1006_shield_3":[21,5,11,0,1729904],"enemy_1043_zomsbr":[66,64,2,0,484262],"enemy_1402_tgshd_2":[36,28,3,0,1249889]},
|
||||
"hooks": [1,138,6900,150,150,988,988,6824,97,113,15,15,150,20923,891,0,123,16,1,97,0,0,0],
|
||||
"events": {"atk":150,"deploy":12,"die":97,"dmg":988,"engage":4,"fx":744,"leak":16,"skill":30,"spawn":138,"status":246},
|
||||
"fx": {"aoe":149,"expose":359,"healAoe":149,"shield":75,"takeoff":12},
|
||||
"snaps": "73ed7f8a ad542748 3ff80098 ae6a8ec3 59598083 93567826 ed19865f e6485e06 aa8d6124 079e6a4e 392585c2 28570fb0 7bfd39c4 1fddcff9 9573a597 de3a6b22 d1d3361c 838f78dd e71a4502 a1225cf5 639d3bd5 6e01ec4b f97fa410"
|
||||
},
|
||||
"diy-134": {
|
||||
"about": "5_diy1_a=monstr/S1 5_diy2_a=monstr/S2 5_diy1_a=monstr/S3 5_diy2_b=monstr/S1 5_diy1_b=monstr/S2 5_diy2_b=monstr/S3 5_diy1_b=monstr/S1/002_monstr 5_diy2_b=monstr/S2/002_monstr 5_diy1_b=monstr/S3/002_monstr 6_diy1_a=monstr/S1 6_diy2_a=monstr/S2 6_diy1_a=monstr/S3",
|
||||
|
||||
Reference in new issue
Block a user