feedback7 R22-GHSIM: three or more overlapping 同名效果取最高 applications resume strongest first — the middle one no longer drops out when the strongest ends (GitHub #342, PR #348 by @siruimei07); golden: diy-059, diy-122, diy-123, diy-124, diy-133, diy-138 move
The contributor's code (status.js _applyValuedStatus and both test files, applied as written from PR #348), so they are the author of this commit; the maintainer side added the 同名效果取最高 label back to the JSDoc and a docs/SIM.md line. buff.data.tail held ONE waiting application (the later-ending one): a third, weaker but longer one replaced a middle one that was still running, so when the strongest ended the effect jumped to the weakest (#342: 30 % / 25 % / 20 % 减防 — DEF 1000 → 700 → 800 instead of 750 → 800 → 1000). The tail is now a chain ordered by strength (decreasing) and end (increasing): a weaker application is inserted in it, a stronger one takes over with the old value and its chain behind it, an equal refresh trims waiters that end before it, and an expiry resumes the next live waiter with the rest of the chain. removeStatus / removeBuff still drop the whole chain; two-application data shapes are unchanged. Golden: six 自选 battles that stack three same-name applications on one unit — diy-059 / diy-122 / diy-123 / diy-124 (庇护 from several operators), diy-133 (Mon3tr's ASPD talent buff talent:monstr:aspd), diy-138 (脆弱) — the middle application now resumes.
This commit is contained in:
1 parent
ae22335cfe
commit
3356b340a7
5 files changed
+261
-62
No files matched your search
+3
-1
@@ -596,7 +596,9 @@ and the 浮空 weight rule, then `statusApplied { source, target, status, durati
|
|||||||
= the target carried no buff of that status before (a refresh / a weaker "取最高" application is not an entry: "进入…时"). Effects follow the official term table
|
= the target carried no buff of that status before (a refresh / a weaker "取最高" application is not an entry: "进入…时"). Effects follow the official term table
|
||||||
(`gamedata_const.termDescriptionDict`, `ba.*`). Same-key statuses refresh to the longer duration, except the
|
(`gamedata_const.termDescriptionDict`, `ba.*`). Same-key statuses refresh to the longer duration, except the
|
||||||
"同名效果取最高" ones marked *strongest* below: the strongest value wins, a weaker application never overrides it and,
|
"同名效果取最高" ones marked *strongest* below: the strongest value wins, a weaker application never overrides it and,
|
||||||
if it outlasts it, resumes when the strong one expires (pass `refresh` to opt out); `stackAs` = the strength such an
|
if it outlasts it, resumes when the strong one expires (pass `refresh` to opt out) — every such waiting application keeps
|
||||||
|
its own end, and they resume strongest first (`buff.data.tail` is a chain ordered by strength: of 30 % / 25 % / 20 %
|
||||||
|
applied one after another, the 25 % follows the 30 %, then the 20 % — GitHub #342, PR #348); `stackAs` = the strength such an
|
||||||
application competes with instead of its `value`, which stays its effect (Raidian S3's 虚弱: PRTS 备注 "在叠加时视为90%…的
|
application competes with instead of its `value`, which stays its effect (Raidian S3's 虚弱: PRTS 备注 "在叠加时视为90%…的
|
||||||
虚弱(仅影响叠加优先级,不影响实际效果)").
|
虚弱(仅影响叠加优先级,不影响实际效果)").
|
||||||
`battle.applyStrongest(target, key, { duration, value, mods: (v) => mods, source })` gives a content effect that is not a
|
`battle.applyStrongest(target, key, { duration, value, mods: (v) => mods, source })` gives a content effect that is not a
|
||||||
|
|||||||
@@ -274,23 +274,39 @@ export class BattleStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "同名效果取最高": keep the strongest value; a weaker one that outlasts it resumes afterwards (buff.data.tail). A
|
* "同名效果取最高": keep the strongest value, then resume weaker effects in order of strength at their original expiry times.
|
||||||
|
* `buff.data.tail` links the waiting effects, with decreasing strength and increasing expiry times. A
|
||||||
* `plain` template (applyStrongest) is an ordinary invisible buff, not a status. `stackAs` (finite): the strength this
|
* `plain` template (applyStrongest) is an ordinary invisible buff, not a status. `stackAs` (finite): the strength this
|
||||||
* application competes with instead of its value (applyStatus opts.stackAs); kept in `data.stackAs` / the tail.
|
* application competes with instead of its value (applyStatus opts.stackAs); kept in `data.stackAs` / the tail.
|
||||||
*/
|
*/
|
||||||
_applyValuedStatus(target, key, tpl, duration, value, source, stackAs = null) {
|
_applyValuedStatus(target, key, tpl, duration, value, source, stackAs = null) {
|
||||||
const as = Number.isFinite(stackAs) ? stackAs : null;
|
const as = Number.isFinite(stackAs) ? stackAs : null;
|
||||||
const strength = (v, s = null) => Math.abs(Number.isFinite(s) ? s : Number.isFinite(v) ? v : tpl.valued);
|
const strength = (v, s = null) => Math.abs(Number.isFinite(s) ? s : Number.isFinite(v) ? v : tpl.valued);
|
||||||
// (no stackAs: the very objects of before — data { value, tail }, tails { value, until })
|
|
||||||
const entry = (v, s, extra) => (Number.isFinite(s) ? { value: v, stackAs: s, ...extra } : { value: v, ...extra });
|
const entry = (v, s, extra) => (Number.isFinite(s) ? { value: v, stackAs: s, ...extra } : { value: v, ...extra });
|
||||||
|
const after = (tail, until) => {
|
||||||
|
while (tail && tail.until <= until) tail = tail.tail;
|
||||||
|
return tail || null;
|
||||||
|
};
|
||||||
|
const insertTail = (tail, incoming) => {
|
||||||
|
if (!tail) return incoming;
|
||||||
|
const diff = strength(incoming.value, incoming.stackAs) - strength(tail.value, tail.stackAs);
|
||||||
|
if (diff > 1e-12) return { ...incoming, tail: after(tail, incoming.until) };
|
||||||
|
if (diff < -1e-12) {
|
||||||
|
if (incoming.until <= tail.until) return tail;
|
||||||
|
return { ...tail, tail: insertTail(tail.tail, incoming) };
|
||||||
|
}
|
||||||
|
// Equal-priority waiting effects keep the longest-lived application's value, including its stackAs.
|
||||||
|
const kept = incoming.until > tail.until ? incoming : tail;
|
||||||
|
return { ...kept, tail: after(tail.tail, kept.until) };
|
||||||
|
};
|
||||||
const make = (v, dur, tail, s = null) => ({
|
const make = (v, dur, tail, s = null) => ({
|
||||||
...(tpl.buff || null), // extra buff fields of the status (抵抗: the 麻痹 decay tick)
|
...(tpl.buff || null), // extra buff fields of the status (抵抗: the 麻痹 decay tick)
|
||||||
key, duration: dur, refresh: 'replace', mods: tpl.mods(v), flags: tpl.flags || null, status: tpl.plain ? null : key,
|
key, duration: dur, refresh: 'replace', mods: tpl.mods(v), flags: tpl.flags || null, status: tpl.plain ? null : key,
|
||||||
visible: !tpl.plain, source,
|
visible: !tpl.plain, source,
|
||||||
data: entry(v, s, { tail }),
|
data: entry(v, s, { tail }),
|
||||||
onExpire: ({ battle, unit, buff }) => {
|
onExpire: ({ battle, unit, buff }) => {
|
||||||
const t = buff.data.tail;
|
const t = after(buff.data.tail, battle.time + 1e-6);
|
||||||
if (t && t.until - battle.time > 1e-6 && unit.alive) battle.addBuff(unit, make(t.value, t.until - battle.time, null, t.stackAs));
|
if (t && unit.alive) battle.addBuff(unit, make(t.value, t.until - battle.time, t.tail || null, t.stackAs));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const old = target.buffs.find((b) => b.key === key && (tpl.plain || b.status === key));
|
const old = target.buffs.find((b) => b.key === key && (tpl.plain || b.status === key));
|
||||||
@@ -299,17 +315,17 @@ export class BattleStatus {
|
|||||||
const oldAs = old.data && Number.isFinite(old.data.stackAs) ? old.data.stackAs : null;
|
const oldAs = old.data && Number.isFinite(old.data.stackAs) ? old.data.stackAs : null;
|
||||||
const oldEnd = this.time + old.timeLeft, newEnd = this.time + duration;
|
const oldEnd = this.time + old.timeLeft, newEnd = this.time + duration;
|
||||||
const oldTail = old.data && old.data.tail;
|
const oldTail = old.data && old.data.tail;
|
||||||
const longerTail = (a, b) => (!a ? b : !b ? a : (b.until > a.until ? b : a));
|
|
||||||
if (strength(value, as) > strength(oldV, oldAs) + 1e-12) {
|
if (strength(value, as) > strength(oldV, oldAs) + 1e-12) {
|
||||||
// stronger: takes over now; the weaker old one (or its tail) resumes if it lasts longer
|
// Stronger: take over now, retaining every old effect that can still become active afterwards.
|
||||||
const tail = longerTail(oldEnd > newEnd ? entry(oldV, oldAs, { until: oldEnd }) : null, oldTail && oldTail.until > newEnd ? oldTail : null);
|
const tail = after(entry(oldV, oldAs, { until: oldEnd, tail: oldTail }), newEnd);
|
||||||
this.addBuff(target, make(value, duration, tail, as));
|
this.addBuff(target, make(value, duration, tail, as));
|
||||||
} else if (strength(value, as) < strength(oldV, oldAs) - 1e-12) {
|
} else if (strength(value, as) < strength(oldV, oldAs) - 1e-12) {
|
||||||
// weaker: never overrides; remembered as the tail when it outlasts the running one
|
// Weaker: insert among the waiting effects instead of replacing the one with the latest expiry.
|
||||||
if (newEnd > oldEnd && (!oldTail || newEnd > oldTail.until)) old.data = { ...old.data, value: oldV, tail: entry(value, as, { until: newEnd }) };
|
if (newEnd > oldEnd) old.data = { ...old.data, value: oldV, tail: insertTail(oldTail, entry(value, as, { until: newEnd })) };
|
||||||
} else if (duration > old.timeLeft) {
|
} else if (duration > old.timeLeft) {
|
||||||
old.timeLeft = duration;
|
old.timeLeft = duration;
|
||||||
old.duration = Math.max(old.duration, duration);
|
old.duration = Math.max(old.duration, duration);
|
||||||
|
old.data.tail = after(oldTail, newEnd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
|
||||||
|
|
||||||
|
test('Dorothy, Pozëmka, and Schwarz DEF reductions fall back to the strongest unexpired application', () => {
|
||||||
|
const h = makeBattle({
|
||||||
|
autoFinish: false,
|
||||||
|
timeLimit: 10,
|
||||||
|
flags: { dpInit: 0, dpPerSec: 0 },
|
||||||
|
defs: { enemies: { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e9, def: 1000, speed: 0 }) } },
|
||||||
|
units: [
|
||||||
|
{ uid: 1, diy: { slot: 'chess_char_6_diy1_a', charId: 'char_4048_doroth', skillIndex: 0, uniEquipId: null }, elite: true, row: 10, col: 4 },
|
||||||
|
{ uid: 2, kind: 'token', tokenId: 'token_10025_doroth_recttp', ownerUid: 1, row: 9, col: 6 },
|
||||||
|
{ uid: 3, diy: { slot: 'chess_char_6_diy2_a', charId: 'char_4055_bgsnow', skillIndex: 0, uniEquipId: null }, elite: true, row: 10, col: 5 },
|
||||||
|
{ uid: 4, kind: 'token', tokenId: 'token_10026_bgsnow_subbow', ownerUid: 3, row: 9, col: 5 },
|
||||||
|
{ uid: 5, diy: { slot: 'chess_char_5_diy1_a', charId: 'char_340_shwaz', skillIndex: 2, uniEquipId: null }, elite: true, row: 9, col: 4 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
h.step();
|
||||||
|
// Keep the real kits and projectile travel; prevent extra attacks refreshing their reductions.
|
||||||
|
for (const unit of h.allies()) unit.atkCd = 100;
|
||||||
|
const trap = h.unit(2), typewriter = h.unit(4), schwarz = h.unit(5);
|
||||||
|
const enemy = h.spawn('enemy_dummy', { pos: [9, 6] });
|
||||||
|
h.step();
|
||||||
|
assert.equal(enemy.s.def, 700, 'Dorothy S1 applies the initial 30% reduction');
|
||||||
|
|
||||||
|
h.run(0.9 - h.b.time);
|
||||||
|
assert.equal(h.b.forceAttack(typewriter, [enemy]), true);
|
||||||
|
h.run(1.8 - h.b.time);
|
||||||
|
schwarz.skill.gainSp(999);
|
||||||
|
schwarz.atkCd = 0;
|
||||||
|
h.step();
|
||||||
|
assert.equal(schwarz.skill.active, true, 'Schwarz S3 guarantees her armor-piercing talent');
|
||||||
|
schwarz.atkCd = 100;
|
||||||
|
h.run(2.1 - h.b.time);
|
||||||
|
|
||||||
|
const reductions = h.hooksOf('statusApplied').filter((event) => event.target === enemy && event.status === 'defDown');
|
||||||
|
assert.deepEqual(reductions.map(({ source, value, duration }) => [source.id, value, duration]), [
|
||||||
|
[trap.id, 0.3, 5],
|
||||||
|
[typewriter.id, 0.25, 5],
|
||||||
|
[schwarz.id, 0.2, 5],
|
||||||
|
]);
|
||||||
|
for (const [index, expectedTime] of [[0, 0], [1, 1], [2, 2]]) {
|
||||||
|
assert.ok(Math.abs(reductions[index].t - expectedTime) < 0.1, `reduction ${index + 1} lands near ${expectedTime}s`);
|
||||||
|
}
|
||||||
|
assert.equal(enemy.s.def, 700, 'weaker applications do not reduce the active strength');
|
||||||
|
|
||||||
|
h.run(5.1 - h.b.time);
|
||||||
|
assert.equal(enemy.s.def, 750, 'the typewriter reduction survives Dorothy S1 expiring');
|
||||||
|
h.run(6.1 - h.b.time);
|
||||||
|
assert.equal(enemy.s.def, 800, 'Schwarz reduction survives the typewriter reduction expiring');
|
||||||
|
h.run(7.1 - h.b.time);
|
||||||
|
assert.equal(enemy.s.def, 1000, 'DEF returns to its base value after the final application expires');
|
||||||
|
assert.deepEqual(h.b.errors, []);
|
||||||
|
h.invariants();
|
||||||
|
});
|
||||||
+52
-52
@@ -2403,15 +2403,15 @@
|
|||||||
"bossHpLeft": null,
|
"bossHpLeft": null,
|
||||||
"rngDraws": 0,
|
"rngDraws": 0,
|
||||||
"errors": {"count":0,"kinds":[]},
|
"errors": {"count":0,"kinds":[]},
|
||||||
"players": {"p1":{"killed":51,"total":96,"perfect":false,"leaks":45,"leaksUncounted":0,"coins":0,"dmg":294912,"bossDmg":0,"heal":232713,"deaths":15,"layerGains":{}}},
|
"players": {"p1":{"killed":51,"total":96,"perfect":false,"leaks":45,"leaksUncounted":0,"coins":0,"dmg":294912,"bossDmg":0,"heal":232708,"deaths":15,"layerGains":{}}},
|
||||||
"units": [
|
"units": [
|
||||||
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_shu",3270,1,118,5,16598,16808,5472,47,2],
|
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_shu",3270,1,118,5,16598,16808,5472,47,2],
|
||||||
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_shu",3270,1,120,1,2287,16353,17674,22,1],
|
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_shu",3270,1,120,1,2287,16353,17674,22,1],
|
||||||
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_shu",3270,1,119,5,30488,15560,32515,90,3],
|
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_shu",3270,1,119,5,30488,15560,32515,90,3],
|
||||||
["p1",4,"op","chess_char_6_diy1_a",0,null,2333.85,1,117,3,24548,27318,9004,88,2],
|
["p1",4,"op","chess_char_6_diy1_a",0,null,2333.85,1,117,3,24548,27318,9004,88,2],
|
||||||
["p1",5,"op","chess_char_6_diy2_a",1,null,2513,1,99,1,8037,34719,37158,50,2],
|
["p1",5,"op","chess_char_6_diy2_a",1,null,2513,1,99,1,8037,34719,37158,50,2],
|
||||||
["p1",6,"op","chess_char_6_diy1_a",2,null,0,0,104,6,30371,33925,18939,121,1],
|
["p1",6,"op","chess_char_6_diy1_a",2,null,0,0,104,6,30371,33925,18935,121,1],
|
||||||
["p1",7,"op","chess_char_6_diy2_b",0,"none",0,0,102,6,40221,40728,12451,139,3],
|
["p1",7,"op","chess_char_6_diy2_b",0,"none",0,0,102,6,40221,40723,12451,139,3],
|
||||||
["p1",8,"op","chess_char_6_diy1_b",1,"none",0,0,95,3,17536,32106,25999,78,2],
|
["p1",8,"op","chess_char_6_diy1_b",1,"none",0,0,95,3,17536,32106,25999,78,2],
|
||||||
["p1",9,"op","chess_char_6_diy2_b",2,"none",0,0,5,3,17720,6178,25141,72,2],
|
["p1",9,"op","chess_char_6_diy2_b",2,"none",0,0,5,3,17720,6178,25141,72,2],
|
||||||
["p1",10,"op","chess_char_6_diy1_b",0,"uniequip_002_shu",0,0,8,0,15926,8337,2961,37,0],
|
["p1",10,"op","chess_char_6_diy1_b",0,"uniequip_002_shu",0,0,8,0,15926,8337,2961,37,0],
|
||||||
@@ -2421,11 +2421,11 @@
|
|||||||
["-","~2","device","trap_1105_accrate",null,null,100,1,2,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]
|
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||||
],
|
],
|
||||||
"enemies": {"enemy_1229_darmy":[27,18,9,42204,86393],"enemy_1229_darmy_2":[57,33,18,151066,218478],"enemy_9007_acelem":[12,0,11,64874,94148]},
|
"enemies": {"enemy_1229_darmy":[27,18,9,42200,86393],"enemy_1229_darmy_2":[57,33,18,151051,218478],"enemy_9007_acelem":[12,0,11,64889,94148]},
|
||||||
"hooks": [1,120,6900,1655,1655,4214,4508,24466,66,104,21,20,0,16520,0,98,96,38,1,66,0,42,0],
|
"hooks": [1,120,6900,1655,1655,4214,4508,24466,66,104,21,20,0,16520,0,98,96,38,1,66,0,42,0],
|
||||||
"events": {"atk":1711,"deploy":21,"die":66,"dmg":4508,"engage":12,"fx":146,"heal":369,"leak":38,"skill":41,"spawn":111,"status":104},
|
"events": {"atk":1711,"deploy":21,"die":66,"dmg":4508,"engage":12,"fx":146,"heal":369,"leak":38,"skill":41,"spawn":111,"status":104},
|
||||||
"fx": {"beam":16,"burst":42,"healField":46,"teleport":42},
|
"fx": {"beam":16,"burst":42,"healField":46,"teleport":42},
|
||||||
"snaps": "37fbb702 9cbcf13c 0a89a448 750d277b d9592437 862401cb 093e7be6 1be132e4 3c1f9fb3 49416bde 2829f9ff 004d18cc 4a040151 bbe05ad5 c52beaeb 202f1244 3901f434 601d0167 a4eb9728 5fc3c9a2 2267d1c9 4e912d69 fd2bfd2e"
|
"snaps": "37fbb702 9cbcf13c 0a89a448 750d277b d9592437 be9b7730 7dbdaeb3 ac878763 fcb3fa2d 0d8b02dc 4a580dd9 9469a886 763426bb cd35262b 13c864f5 dcf9735a cfb24522 27704e21 83f9d1ea 25515c68 00b3c803 21fe5f4f 6cf1426c"
|
||||||
},
|
},
|
||||||
"diy-060": {
|
"diy-060": {
|
||||||
"about": "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 5_diy1_b=pallas/S1/002_pallas 5_diy2_b=pallas/S2/002_pallas 5_diy1_b=pallas/S3/002_pallas 5_diy2_b=pallas/S1/003_pallas 5_diy1_b=pallas/S2/003_pallas 5_diy2_b=pallas/S3/003_pallas",
|
"about": "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 5_diy1_b=pallas/S1/002_pallas 5_diy2_b=pallas/S2/002_pallas 5_diy1_b=pallas/S3/002_pallas 5_diy2_b=pallas/S1/003_pallas 5_diy1_b=pallas/S2/003_pallas 5_diy2_b=pallas/S3/003_pallas",
|
||||||
@@ -5248,29 +5248,29 @@
|
|||||||
"bossHpLeft": null,
|
"bossHpLeft": null,
|
||||||
"rngDraws": 0,
|
"rngDraws": 0,
|
||||||
"errors": {"count":0,"kinds":[]},
|
"errors": {"count":0,"kinds":[]},
|
||||||
"players": {"p1":{"killed":0,"total":123,"perfect":false,"leaks":123,"leaksUncounted":0,"coins":0,"dmg":306066,"bossDmg":0,"heal":32670,"deaths":17,"layerGains":{}}},
|
"players": {"p1":{"killed":0,"total":123,"perfect":false,"leaks":123,"leaksUncounted":0,"coins":0,"dmg":307696,"bossDmg":0,"heal":37911,"deaths":17,"layerGains":{}}},
|
||||||
"units": [
|
"units": [
|
||||||
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_radian",1158,1,4,0,33930,0,0,127,4],
|
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_radian",1158,1,4,0,34135,0,0,127,4],
|
||||||
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_radian",1158,1,5,0,41774,0,0,124,4],
|
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_radian",1158,1,5,0,41953,0,0,124,4],
|
||||||
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_radian",1158,1,11,0,60012,0,0,137,3],
|
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_radian",1158,1,11,0,60038,0,0,137,3],
|
||||||
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_radian",993,1,13,0,40817,0,0,140,4],
|
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_radian",993,1,13,0,40604,0,0,140,4],
|
||||||
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_radian",993,1,6,0,43040,0,0,121,4],
|
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_radian",993,1,6,0,42960,0,0,121,4],
|
||||||
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_radian",993,1,14,0,62016,0,0,132,3],
|
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_radian",993,1,14,0,62016,0,0,132,3],
|
||||||
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,147,0,1742,13278,7559,25,1],
|
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,149,0,3483,17332,8610,34,1],
|
||||||
["p1",8,"op","chess_char_5_diy2_a",1,null,0,0,148,0,5534,14951,4707,35,1],
|
["p1",8,"op","chess_char_5_diy2_a",1,null,0,0,147,0,5147,16241,5568,34,1],
|
||||||
["p1",9,"op","chess_char_5_diy1_a",2,null,0,0,130,0,1625,4555,220,8,0],
|
["p1",9,"op","chess_char_5_diy1_a",2,null,0,0,130,0,1625,4555,220,8,0],
|
||||||
["p1",10,"op","chess_char_5_diy2_b",0,"none",0,0,149,0,8888,10867,5572,46,1],
|
["p1",10,"op","chess_char_5_diy2_b",0,"none",0,0,148,0,9046,11213,5918,46,1],
|
||||||
["p1",11,"op","chess_char_5_diy1_b",1,"none",0,0,133,0,3696,5730,435,15,0],
|
["p1",11,"op","chess_char_5_diy1_b",1,"none",0,0,133,0,3696,5730,435,15,0],
|
||||||
["p1",12,"op","chess_char_5_diy2_b",2,"none",0,0,134,0,2992,10413,14176,43,1],
|
["p1",12,"op","chess_char_5_diy2_b",2,"none",0,0,134,0,2992,9964,17159,47,1],
|
||||||
["-","~1","device","trap_1105_accrate",null,null,100,1,1,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],
|
["-","~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]
|
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon":[54,0,53,29077,128397],"enemy_1422_lrsldr_2":[39,0,39,21409,104996],"enemy_1425_lrcmra_2":[30,0,30,9310,72672]},
|
"enemies": {"enemy_1010_demon":[54,0,53,37546,132382],"enemy_1422_lrsldr_2":[39,0,39,17859,103701],"enemy_1425_lrcmra_2":[30,0,30,9630,71612]},
|
||||||
"hooks": [1,149,6900,1053,1053,986,986,1657,17,139,26,26,0,29616,226,17,123,122,1,18,0,0,0],
|
"hooks": [1,149,6900,1072,1072,1001,1001,2279,17,139,26,26,0,30067,226,17,123,122,1,20,0,0,0],
|
||||||
"events": {"atk":1053,"deploy":23,"die":17,"dmg":986,"engage":12,"fx":9,"heal":67,"leak":122,"skill":52,"spawn":138,"status":234},
|
"events": {"atk":1072,"deploy":23,"die":17,"dmg":1001,"engage":12,"fx":9,"heal":71,"leak":122,"skill":52,"spawn":138,"status":234},
|
||||||
"fx": {"shield":8,"undying":1},
|
"fx": {"shield":8,"undying":1},
|
||||||
"snaps": "422f4ca7 82d85ded 1385aefd a28b5e3b a21d6686 d83862af 7e5ba79d e77bedc9 b9807ab8 28b73dbc 909a17e9 b7d76569 d8a709e8 011fc05a 22f68ccc 8d128c96 d14e6a62 7b6b8033 bc6418b2 000d2486 8070923b 23199b14 c39148dc"
|
"snaps": "422f4ca7 82d85ded f9912cdd d2129b24 b99085df a39a1c44 8adc939d 29eff8bc 80335586 a23ffeab ddb0ec35 0c8321e8 31df363f 198f71d3 407a9f76 14edeeda 025ea6cd 9741503f 4b8237b4 98be51c7 45f5682e b173bb87 5b2dd744"
|
||||||
},
|
},
|
||||||
"diy-123": {
|
"diy-123": {
|
||||||
"about": "5_diy1_b=slent2/S1/002_slent2 5_diy2_b=slent2/S2/002_slent2 5_diy1_b=slent2/S3/002_slent2 5_diy2_b=slent2/S1/003_slent2 5_diy1_b=slent2/S2/003_slent2 5_diy2_b=slent2/S3/003_slent2 6_diy1_a=slent2/S1 6_diy2_a=slent2/S2 6_diy1_a=slent2/S3 6_diy2_b=slent2/S1 6_diy1_b=slent2/S2 6_diy2_b=slent2/S3",
|
"about": "5_diy1_b=slent2/S1/002_slent2 5_diy2_b=slent2/S2/002_slent2 5_diy1_b=slent2/S3/002_slent2 5_diy2_b=slent2/S1/003_slent2 5_diy1_b=slent2/S2/003_slent2 5_diy2_b=slent2/S3/003_slent2 6_diy1_a=slent2/S1 6_diy2_a=slent2/S2 6_diy1_a=slent2/S3 6_diy2_b=slent2/S1 6_diy1_b=slent2/S2 6_diy2_b=slent2/S3",
|
||||||
@@ -5290,26 +5290,26 @@
|
|||||||
"bossHpLeft": null,
|
"bossHpLeft": null,
|
||||||
"rngDraws": 11,
|
"rngDraws": 11,
|
||||||
"errors": {"count":0,"kinds":[]},
|
"errors": {"count":0,"kinds":[]},
|
||||||
"players": {"p1":{"killed":12,"total":36,"perfect":false,"leaks":24,"leaksUncounted":0,"coins":0,"dmg":163954,"bossDmg":0,"heal":112142,"deaths":21,"layerGains":{}}},
|
"players": {"p1":{"killed":12,"total":36,"perfect":false,"leaks":24,"leaksUncounted":0,"coins":0,"dmg":158628,"bossDmg":0,"heal":111125,"deaths":21,"layerGains":{}}},
|
||||||
"units": [
|
"units": [
|
||||||
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_slent2",0,0,57,0,8245,5577,12709,42,2],
|
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_slent2",0,0,57,0,11446,6763,12809,48,2],
|
||||||
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_slent2",0,0,54,0,22019,9588,6117,65,3],
|
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_slent2",1865,1,54,0,21728,5602,7475,71,3],
|
||||||
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_slent2",0,0,56,0,3686,6734,13453,37,1],
|
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_slent2",1116.5,1,56,0,7663,8790,15155,51,2],
|
||||||
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_slent2",0,0,52,0,21984,8140,11594,47,2],
|
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_slent2",0,0,53,0,19296,9125,8872,43,2],
|
||||||
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_slent2",819.96,1,60,0,28128,4330,2400,38,2],
|
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_slent2",0,0,60,0,23328,4125,1375,31,2],
|
||||||
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_slent2",1068.01,1,58,0,17952,2762,11014,44,1],
|
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_slent2",0,0,58,0,12288,3897,9218,35,1],
|
||||||
["p1",7,"op","chess_char_6_diy1_a",0,null,724.73,1,55,0,10681,2855,6823,50,2],
|
["p1",7,"op","chess_char_6_diy1_a",0,null,0,0,55,0,7895,4480,6020,42,2],
|
||||||
["p1",8,"op","chess_char_6_diy2_a",1,null,0,0,53,0,12848,6062,5102,58,2],
|
["p1",8,"op","chess_char_6_diy2_a",1,null,1392.65,1,52,0,16254,3375,4934,67,2],
|
||||||
["p1",9,"op","chess_char_6_diy1_a",2,null,0,0,48,1,5235,7339,11935,51,1],
|
["p1",9,"op","chess_char_6_diy1_a",2,null,0,0,50,1,7402,9240,11944,58,1],
|
||||||
["p1",10,"op","chess_char_6_diy2_b",0,"none",0,0,47,0,10824,44211,14116,51,2],
|
["p1",10,"op","chess_char_6_diy2_b",0,"none",0,0,49,0,10824,49145,18299,58,2],
|
||||||
["p1",11,"op","chess_char_6_diy1_b",1,"none",0,0,59,0,13464,3751,878,38,2],
|
["p1",11,"op","chess_char_6_diy1_b",1,"none",0,0,59,0,12056,4225,1168,35,2],
|
||||||
["p1",12,"op","chess_char_6_diy2_b",2,"none",0,0,51,0,8888,50121,16001,60,1]
|
["p1",12,"op","chess_char_6_diy2_b",2,"none",0,0,51,0,8448,39925,13856,59,1]
|
||||||
],
|
],
|
||||||
"enemies": {"enemy_1005_yokai_2":[12,12,0,334,2893],"enemy_1005_yokai_3":[12,0,8,76865,64685],"enemy_9009_acfort":[12,0,11,74270,96377]},
|
"enemies": {"enemy_1005_yokai_2":[12,12,0,335,2893],"enemy_1005_yokai_3":[12,0,8,77142,51740],"enemy_9009_acfort":[12,0,11,71214,103995]},
|
||||||
"hooks": [1,60,6900,1019,1019,858,858,1795,33,52,21,21,0,24163,0,0,36,19,1,32,0,0,0],
|
"hooks": [1,60,6900,1033,1033,846,846,1544,33,52,22,20,0,24223,0,0,36,19,1,36,0,0,0],
|
||||||
"events": {"atk":1103,"deploy":24,"die":33,"dmg":858,"engage":12,"fx":49,"heal":250,"leak":19,"skill":42,"spawn":48,"status":28},
|
"events": {"atk":1104,"deploy":24,"die":33,"dmg":846,"engage":12,"fx":49,"heal":260,"leak":19,"skill":42,"spawn":48,"status":29},
|
||||||
"fx": {"beam":46,"undying":3},
|
"fx": {"beam":45,"undying":4},
|
||||||
"snaps": "a5b373d8 cade2571 25bd2c7f 01adb744 fbb45b57 6848708b 0a96115c 7e128c01 21df8c78 e6903500 2f41b832 aa0ba68b 8641acbf 21932ee2 038f1d76 a983d0f1 3ced4395 63969cf1 ccc7caac 2ce3eb58 ecd3dfd9 4ef03b03 eb5d21bd"
|
"snaps": "a5b373d8 cade2571 25bd2c7f 01adb744 fbb45b57 e30ff540 3b72daed 51b4c8b9 06683530 b735ddf5 82033c8c 713018f8 4395803f 890d91a1 2e056bbf 2e2e98df d852cfe2 19169929 86dc378b 79c1efd7 d9de2b5f 90c00c25 ea46d144"
|
||||||
},
|
},
|
||||||
"diy-124": {
|
"diy-124": {
|
||||||
"about": "6_diy1_b=slent2/S1/002_slent2 6_diy2_b=slent2/S2/002_slent2 6_diy1_b=slent2/S3/002_slent2 6_diy2_b=slent2/S1/003_slent2 6_diy1_b=slent2/S2/003_slent2 6_diy2_b=slent2/S3/003_slent2 5_diy1_a=haruka/S1 5_diy2_a=haruka/S2 5_diy1_a=haruka/S3 5_diy2_b=haruka/S1 5_diy1_b=haruka/S2 5_diy2_b=haruka/S3",
|
"about": "6_diy1_b=slent2/S1/002_slent2 6_diy2_b=slent2/S2/002_slent2 6_diy1_b=slent2/S3/002_slent2 6_diy2_b=slent2/S1/003_slent2 6_diy1_b=slent2/S2/003_slent2 6_diy2_b=slent2/S3/003_slent2 5_diy1_a=haruka/S1 5_diy2_a=haruka/S2 5_diy1_a=haruka/S3 5_diy2_b=haruka/S1 5_diy1_b=haruka/S2 5_diy2_b=haruka/S3",
|
||||||
@@ -5329,15 +5329,15 @@
|
|||||||
"bossHpLeft": null,
|
"bossHpLeft": null,
|
||||||
"rngDraws": 0,
|
"rngDraws": 0,
|
||||||
"errors": {"count":0,"kinds":[]},
|
"errors": {"count":0,"kinds":[]},
|
||||||
"players": {"p1":{"killed":3,"total":147,"perfect":false,"leaks":144,"leaksUncounted":0,"coins":0,"dmg":388624,"bossDmg":0,"heal":186324,"deaths":1,"layerGains":{}}},
|
"players": {"p1":{"killed":3,"total":147,"perfect":false,"leaks":144,"leaksUncounted":0,"coins":0,"dmg":388624,"bossDmg":0,"heal":186275,"deaths":1,"layerGains":{}}},
|
||||||
"units": [
|
"units": [
|
||||||
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_slent2",2298,1,6,0,21267,0,30997,124,4],
|
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_slent2",2298,1,6,0,21267,0,30997,124,4],
|
||||||
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_slent2",2298,1,11,0,30498,0,13312,138,5],
|
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_slent2",2298,1,11,0,30498,0,13312,138,5],
|
||||||
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_slent2",2298,1,14,0,17697,0,14062,99,2],
|
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_slent2",2298,1,14,0,17697,0,14062,99,2],
|
||||||
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_slent2",1965,1,4,0,31024,0,5800,77,4],
|
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_slent2",1965,1,4,0,31024,0,5870,76,4],
|
||||||
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_slent2",1965,1,5,0,52007,0,10668,130,5],
|
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_slent2",1965,1,5,0,52007,0,10668,130,5],
|
||||||
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_slent2",2358,1,7,0,33734,19361,24981,114,2],
|
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_slent2",2358,1,7,0,33734,19312,24981,114,2],
|
||||||
["p1",7,"op","chess_char_5_diy1_a",0,null,1556.5,1,12,1,18973,0,14769,124,5],
|
["p1",7,"op","chess_char_5_diy1_a",0,null,1556.5,1,12,1,18973,0,14651,124,5],
|
||||||
["p1",8,"op","chess_char_5_diy2_a",1,null,1556.5,1,15,0,36496,0,16816,75,2],
|
["p1",8,"op","chess_char_5_diy2_a",1,null,1556.5,1,15,0,36496,0,16816,75,2],
|
||||||
["p1",9,"op","chess_char_5_diy1_a",2,null,1556.5,1,8,1,31939,48618,9950,102,3],
|
["p1",9,"op","chess_char_5_diy1_a",2,null,1556.5,1,8,1,31939,48618,9950,102,3],
|
||||||
["p1",10,"op","chess_char_5_diy2_b",0,"none",1531.8,1,10,1,20540,21770,18960,127,5],
|
["p1",10,"op","chess_char_5_diy2_b",0,"none",1531.8,1,10,1,20540,21770,18960,127,5],
|
||||||
@@ -5347,11 +5347,11 @@
|
|||||||
["-","~2","device","trap_1105_accrate",null,null,100,1,2,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]
|
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon_2":[36,0,34,129029,143213],"enemy_1071_dftman":[51,1,50,10701,69708],"enemy_1072_dlancer":[60,2,56,49435,175702]},
|
"enemies": {"enemy_1010_demon_2":[36,0,34,129029,143213],"enemy_1071_dftman":[51,1,50,10701,69708],"enemy_1072_dlancer":[60,2,56,49387,175702]},
|
||||||
"hooks": [1,163,6900,1602,1602,1824,1824,1019,4,144,42,38,0,42759,60,66,147,140,1,4,0,0,0],
|
"hooks": [1,163,6900,1601,1601,1824,1824,1018,4,144,42,38,0,42759,60,66,147,140,1,4,0,0,0],
|
||||||
"events": {"atk":1836,"deploy":13,"die":4,"dmg":1818,"engage":12,"fx":794,"heal":616,"leak":140,"skill":80,"spawn":162,"status":757},
|
"events": {"atk":1835,"deploy":13,"die":4,"dmg":1818,"engage":12,"fx":794,"heal":614,"leak":140,"skill":80,"spawn":162,"status":757},
|
||||||
"fx": {"aoe":98,"heal":310,"levitate":60,"shield":326},
|
"fx": {"aoe":98,"heal":310,"levitate":60,"shield":326},
|
||||||
"snaps": "1b8091e0 73c802f9 110ffccb 73e97743 2c3d94d2 37aed2ff 5fdabeab 69cb41ab 491632d5 aef350f1 30f8d397 418bbed0 483f862b e8d524ca 1a85cf2d 0771fb68 0ed53d18 5abc2708 a6f0038b 0f8d27f3 f803bfaf 669dc197 e8d45f8a"
|
"snaps": "1b8091e0 73c802f9 110ffccb 73e97743 2c3d94d2 37aed2ff 5fdabeab 69cb41ab 491632d5 aef350f1 30f8d397 418bbed0 483f862b e8d524ca 1a85cf2d 0771fb68 0ed53d18 5abc2708 83618567 204d2f27 488b9d83 57bbc743 6a9e58ce"
|
||||||
},
|
},
|
||||||
"diy-125": {
|
"diy-125": {
|
||||||
"about": "5_diy1_b=haruka/S1/002_haruka 5_diy2_b=haruka/S2/002_haruka 5_diy1_b=haruka/S3/002_haruka 6_diy1_a=haruka/S1 6_diy2_a=haruka/S2 6_diy1_a=haruka/S3 6_diy2_b=haruka/S1 6_diy1_b=haruka/S2 6_diy2_b=haruka/S3 6_diy1_b=haruka/S1/002_haruka 6_diy2_b=haruka/S2/002_haruka 6_diy1_b=haruka/S3/002_haruka",
|
"about": "5_diy1_b=haruka/S1/002_haruka 5_diy2_b=haruka/S2/002_haruka 5_diy1_b=haruka/S3/002_haruka 6_diy1_a=haruka/S1 6_diy2_a=haruka/S2 6_diy1_a=haruka/S3 6_diy2_b=haruka/S1 6_diy1_b=haruka/S2 6_diy2_b=haruka/S3 6_diy1_b=haruka/S1/002_haruka 6_diy2_b=haruka/S2/002_haruka 6_diy1_b=haruka/S3/002_haruka",
|
||||||
@@ -5822,22 +5822,22 @@
|
|||||||
["p1",3,"op","chess_char_6_diy1_a",2,null,1810.9,1,11,0,0,0,53690,111,2],
|
["p1",3,"op","chess_char_6_diy1_a",2,null,1810.9,1,11,0,0,0,53690,111,2],
|
||||||
["p1",4,"op","chess_char_6_diy2_b",0,"none",2005.9,1,13,0,0,0,46002,87,4],
|
["p1",4,"op","chess_char_6_diy2_b",0,"none",2005.9,1,13,0,0,0,46002,87,4],
|
||||||
["p1",5,"op","chess_char_6_diy1_b",1,"none",2005.9,1,6,18,704057,0,15788,61,3],
|
["p1",5,"op","chess_char_6_diy1_b",1,"none",2005.9,1,6,18,704057,0,15788,61,3],
|
||||||
["p1",6,"op","chess_char_6_diy2_b",2,"none",2005.9,1,14,0,0,0,26930,79,3],
|
["p1",6,"op","chess_char_6_diy2_b",2,"none",2005.9,1,14,0,0,0,27264,78,3],
|
||||||
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,7,0,0,81970,41023,72,12],
|
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,7,0,0,81970,41023,72,12],
|
||||||
["p1",8,"op","chess_char_5_diy2_a",1,null,1492,1,9,0,0,98191,51694,93,4],
|
["p1",8,"op","chess_char_5_diy2_a",1,null,1492,1,9,0,0,98191,51694,93,4],
|
||||||
["p1",9,"op","chess_char_5_diy1_a",2,null,217.03,1,10,0,0,107130,60043,84,0],
|
["p1",9,"op","chess_char_5_diy1_a",2,null,217.03,1,10,0,0,107130,60487,84,0],
|
||||||
["p1",10,"op","chess_char_5_diy2_b",0,"none",1653,1,12,0,0,22049,53056,73,18],
|
["p1",10,"op","chess_char_5_diy2_b",0,"none",1653,1,12,0,0,22049,53056,73,18],
|
||||||
["p1",11,"op","chess_char_5_diy1_b",1,"none",1653,1,15,0,0,64626,18934,43,2],
|
["p1",11,"op","chess_char_5_diy1_b",1,"none",1653,1,15,0,0,64626,18156,43,2],
|
||||||
["p1",12,"op","chess_char_5_diy2_b",2,"none",1653,1,8,0,0,90649,44508,81,0],
|
["p1",12,"op","chess_char_5_diy2_b",2,"none",1653,1,8,0,0,90649,44508,81,0],
|
||||||
["-","~1","device","trap_1105_accrate",null,null,100,1,1,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],
|
["-","~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]
|
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
|
||||||
],
|
],
|
||||||
"enemies": {"enemy_1006_shield_3":[21,0,16,372723,566446],"enemy_1043_zomsbr":[66,60,5,22690,479046],"enemy_1402_tgshd_2":[36,10,20,69202,794079]},
|
"enemies": {"enemy_1006_shield_3":[21,0,16,372723,566446],"enemy_1043_zomsbr":[66,60,5,22690,479046],"enemy_1402_tgshd_2":[36,10,20,69202,794079]},
|
||||||
"hooks": [1,138,6900,1408,1408,1120,1120,13592,71,112,55,52,70,22853,566,8,123,41,1,71,0,0,0],
|
"hooks": [1,138,6900,1407,1407,1120,1120,13591,71,112,55,52,70,22853,566,8,123,41,1,71,0,0,0],
|
||||||
"events": {"atk":2335,"deploy":12,"die":71,"dmg":1110,"engage":2,"fx":458,"heal":1178,"leak":41,"skill":107,"spawn":138,"status":209},
|
"events": {"atk":2334,"deploy":12,"die":71,"dmg":1110,"engage":2,"fx":458,"heal":1178,"leak":41,"skill":107,"spawn":138,"status":209},
|
||||||
"fx": {"aoe":70,"expose":272,"healAoe":70,"shield":40,"takeoff":6},
|
"fx": {"aoe":70,"expose":272,"healAoe":70,"shield":40,"takeoff":6},
|
||||||
"snaps": "0a2a0a58 9edb0805 ec947f3f 7ffcd830 23c8b514 9268451b 1587cab5 93203dcf 675b3282 b8e42ac4 d194e3af c0d85745 cb282efe f152e307 02944050 4dab08fa 73f1afb0 1a6a7b38 89999054 6738780f f5ef296b 9876bcdc 9e74e0c9"
|
"snaps": "0a2a0a58 9edb0805 ec947f3f 7ffcd830 23c8b514 9268451b 1587cab5 93203dcf 675b3282 b8e42ac4 d194e3af c0d85745 4520de90 8fa30b22 3c71ad2d 7af3c5a9 1b07ebf9 d6841316 a0cdffb6 e3a8979b d1066696 fe5e3f62 b6c3a091"
|
||||||
},
|
},
|
||||||
"diy-134": {
|
"diy-134": {
|
||||||
"about": "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 6_diy2_b=monstr/S1 6_diy1_b=monstr/S2 6_diy2_b=monstr/S3 6_diy1_b=monstr/S1/002_monstr 6_diy2_b=monstr/S2/002_monstr 6_diy1_b=monstr/S3/002_monstr",
|
"about": "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 6_diy2_b=monstr/S1 6_diy1_b=monstr/S2 6_diy2_b=monstr/S3 6_diy1_b=monstr/S1/002_monstr 6_diy2_b=monstr/S2/002_monstr 6_diy1_b=monstr/S3/002_monstr",
|
||||||
@@ -6036,7 +6036,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1288_duskls":[27,4,22,19614,114889],"enemy_1288_duskls_2":[57,12,45,7003,313267],"enemy_1292_duskld":[51,9,38,77789,451811]},
|
"enemies": {"enemy_1288_duskls":[27,4,22,19614,114889],"enemy_1288_duskls_2":[57,12,45,7003,313267],"enemy_1292_duskld":[51,9,38,77789,451811]},
|
||||||
"hooks": [1,153,6900,1638,1638,2202,2465,332,35,140,20,18,0,28180,18680,57,135,105,1,84,0,0,6],
|
"hooks": [1,153,6900,1638,1638,2202,2465,332,35,140,20,18,0,28180,18680,57,135,105,1,84,0,0,6],
|
||||||
"events": {"atk":1961,"deploy":18,"die":35,"dmg":2465,"engage":9,"fx":119,"heal":279,"leak":105,"skill":42,"spawn":147,"status":1300},
|
"events": {"atk":1961,"deploy":18,"die":35,"dmg":2465,"engage":9,"fx":119,"heal":279,"leak":105,"skill":42,"spawn":147,"status":1298},
|
||||||
"fx": {"aoe":5,"dodge":43,"ember":49,"revive":22},
|
"fx": {"aoe":5,"dodge":43,"ember":49,"revive":22},
|
||||||
"snaps": "3e9a4d11 f9a475f7 d909dafe 07c79a9f 76e65988 7dff2527 631c9a48 90e879b4 cfe3ca91 ccb275cc 2445e31f 2132570f ae5cb98a c2376534 c8afbc93 fbbb00d5 98187a92 3228cfb6 f8b78613 5724ac77 b963f388 bb322dda fee7681a"
|
"snaps": "3e9a4d11 f9a475f7 d909dafe 07c79a9f 76e65988 7dff2527 631c9a48 90e879b4 cfe3ca91 ccb275cc 2445e31f 2132570f ae5cb98a c2376534 c8afbc93 fbbb00d5 98187a92 3228cfb6 f8b78613 5724ac77 b963f388 bb322dda fee7681a"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
const h = makeBattle({
|
||||||
|
defs: { enemies: { enemy_test: enemyRec({ key: 'enemy_test', hp: 1e6, atk: 100, def: 1000, speed: 0 }) } },
|
||||||
|
enemies: [{ key: 'enemy_test', pos: [11, 8] }], content: 'none', autoFinish: false,
|
||||||
|
});
|
||||||
|
h.step();
|
||||||
|
return { h, e: h.enemy('enemy_test') };
|
||||||
|
}
|
||||||
|
|
||||||
|
const orders = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]];
|
||||||
|
const approx = (actual, expected) => assert.ok(Math.abs(actual - expected) < 1e-6, `${actual} ≈ ${expected}`);
|
||||||
|
|
||||||
|
for (const plain of [false, true]) {
|
||||||
|
for (const order of orders) {
|
||||||
|
test(`${plain ? 'applyStrongest' : 'fragile'}: three effects expire in strength order after application order ${order}`, () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
const effects = [{ value: 0.8, duration: 2 }, { value: 0.6, duration: 5 }, { value: 0.3, duration: 10 }];
|
||||||
|
const key = plain ? 'test:strongest' : 'fragile';
|
||||||
|
for (const i of order) {
|
||||||
|
if (plain) h.b.applyStrongest(e, key, { ...effects[i], mods: (v) => ({ dmgTakenMul: 1 + v }) });
|
||||||
|
else h.b.applyStatus(e, key, effects[i]);
|
||||||
|
}
|
||||||
|
for (const [elapsed, multiplier, damage] of [[0, 1.8, 180], [2.1, 1.6, 160], [3, 1.3, 130], [5, 1, 100]]) {
|
||||||
|
if (elapsed) h.run(elapsed);
|
||||||
|
approx(e.s.dmgTakenMul, multiplier);
|
||||||
|
assert.equal(h.b.dealDamage(null, e, { amount: 100, type: 'true' }), damage);
|
||||||
|
assert.equal(e.buffs.filter((b) => b.key === key).length, multiplier === 1 ? 0 : 1);
|
||||||
|
}
|
||||||
|
assert.deepEqual(h.b.errors, []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('strongest statuses: a weaker effect that expires under the winner never resumes', () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.6, duration: 3 });
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.3, duration: 10 });
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.8, duration: 5 });
|
||||||
|
h.run(5.1);
|
||||||
|
approx(e.s.dmgTakenMul, 1.3);
|
||||||
|
h.run(5);
|
||||||
|
assert.equal(e.findBuff('fragile'), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strongest statuses: equal-strength refresh skips expired fallbacks but keeps the later one', () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
for (const [value, duration] of [[0.8, 2], [0.6, 5], [0.3, 10]]) h.b.applyStatus(e, 'fragile', { value, duration });
|
||||||
|
h.run(1);
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.8, duration: 6 });
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.8, duration: 1 });
|
||||||
|
h.run(5.1);
|
||||||
|
approx(e.s.dmgTakenMul, 1.8);
|
||||||
|
h.run(1);
|
||||||
|
approx(e.s.dmgTakenMul, 1.3);
|
||||||
|
h.run(3);
|
||||||
|
assert.equal(e.findBuff('fragile'), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strongest statuses: refreshing a waiting effect preserves the weaker later fallback', () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
for (const [value, duration] of [[0.8, 2], [0.6, 5], [0.3, 10]]) h.b.applyStatus(e, 'fragile', { value, duration });
|
||||||
|
h.run(1);
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.6, duration: 6 });
|
||||||
|
h.run(4.1);
|
||||||
|
approx(e.s.dmgTakenMul, 1.6);
|
||||||
|
h.run(2);
|
||||||
|
approx(e.s.dmgTakenMul, 1.3);
|
||||||
|
h.run(3);
|
||||||
|
assert.equal(e.findBuff('fragile'), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strongest statuses: stackAs controls every fallback without changing its actual effect', () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.1, stackAs: 0.9, duration: 2 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.2, stackAs: 0.8, duration: 5 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.3, duration: 10 });
|
||||||
|
approx(e.s.atk, 90);
|
||||||
|
h.run(2.1);
|
||||||
|
approx(e.s.atk, 80);
|
||||||
|
h.run(3);
|
||||||
|
approx(e.s.atk, 70);
|
||||||
|
h.run(5);
|
||||||
|
approx(e.s.atk, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strongest statuses: equal-priority waiting effects keep the longest-lived application value', () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.9, duration: 2 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.1, stackAs: 0.8, duration: 5 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.3, duration: 10 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.2, stackAs: 0.8, duration: 7 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.4, stackAs: 0.8, duration: 6 });
|
||||||
|
h.b.applyStatus(e, 'weaken', { value: 0.5, stackAs: 0.8, duration: 7 });
|
||||||
|
h.run(2.1);
|
||||||
|
approx(e.s.atk, 80);
|
||||||
|
h.run(3);
|
||||||
|
approx(e.s.atk, 80);
|
||||||
|
h.run(2);
|
||||||
|
approx(e.s.atk, 70);
|
||||||
|
h.run(3);
|
||||||
|
approx(e.s.atk, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const plain of [false, true]) {
|
||||||
|
test(`${plain ? 'removeBuff' : 'removeStatus'} discards every waiting strongest effect`, () => {
|
||||||
|
const { h, e } = setup();
|
||||||
|
const key = plain ? 'test:strongest' : 'fragile';
|
||||||
|
for (const [value, duration] of [[0.8, 2], [0.6, 5], [0.3, 10]]) {
|
||||||
|
if (plain) h.b.applyStrongest(e, key, { value, duration, mods: (v) => ({ dmgTakenMul: 1 + v }) });
|
||||||
|
else h.b.applyStatus(e, key, { value, duration });
|
||||||
|
}
|
||||||
|
assert.equal(plain ? h.b.removeBuff(e, key) : h.b.removeStatus(e, key), 1);
|
||||||
|
h.b.applyStatus(e, 'fragile', { value: 0.1, duration: 1 });
|
||||||
|
h.run(2.1);
|
||||||
|
approx(e.s.dmgTakenMul, 1);
|
||||||
|
h.run(8);
|
||||||
|
approx(e.s.dmgTakenMul, 1);
|
||||||
|
assert.equal(e.findBuff(key), null);
|
||||||
|
assert.deepEqual(h.b.errors, []);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user