feedback5 O10: 自选 operator kits 棘刺, 司霆惊蛰 and 赤刃明霄陈 — every skill, talent, trait and module (stage 1 / 3) at every form

- server/sim/content/kits/ops/op-thorns.js (领主; LOR-X, LOR-Δ), op-leizi2.js (解放者; LIB-X), op-chen3.js (术战者; AFT-X),
  appended to OPERATOR_KIT_FILES; numbers from the composed records (backups.json), mechanics from PRTS and the client's
  battle data (buff templates, charpacks, skill / equip / projectile prefabs, skeleton clips). No engine change; the data
  triggers unchanged (司霆惊蛰 S1 / S2 SEARCH reported, not changed).
- tests: test/content/op_thorns.test.js (12), op_leizi2.test.js (12), op_chen3.test.js (11).
- golden: only diy moved — diy-049 … diy-053 now field the three new kits in every form × module × skill (the diy family
  packs every kitted operator, 12 pieces per battle); the prototype battles moved from diy-049 … 052 to diy-054 … 057 (same
  pieces, new ids); diy-001 … 048 and the roster / bonds / fields / matches / standins families unchanged.
This commit is contained in:
sganggs committed 2026-10-06 01:35:01 +08:00
1 parent fef4ea3d4b
commit fd7f16c595
8 files changed
+2324 -118

No files matched your search

+341
View File
@@ -0,0 +1,341 @@
// server/sim/content/kits/ops/op-leizi2.js — 司霆惊蛰 (char_1043_leizi2) 自选 operator kit: 6★ 解放者 (近卫), an owned-6★
// pick of the tier-5 and tier-6 自选 slots; every skill, both talents, the trait and her module (LIB-X) at every form.
// Kit contract and the 自选 rules: ../README.md ("How to add an operator (自选)").
//
// Forms (data/backups.json units.char_1043_leizi2, the DIY slot statuses): normal = E2 Lv1, skills at rank 4, no module;
// elite = E2 Lv60, rank 7, LIB-X at stage 1 (tier 5) or 3 (tier 6). Potential 0 [ASSUMED: no account].
// Sources: character_table / skill_table / battle_equip_table (zh_CN, as built into backups.json); the client's battle
// logic — buff_template_data (leizi2_tr, leizi2_e_002_tr, leizi2_s_cast, leizi2_t_1[atk_up] / [on_calcul] / [fly] /
// [trigger] / [buff_start], leizi2_s_2[try_add_atk], leizi2_s_3), her charpack (Trait buff: triggerInterval 1; the
// "thunder" ability: `_maxCount` 15, `_resetCountList` 3…7, `_rangeCastDelay` 0.25; S1 abilities s1_m / s1_l / s1_r:
// `_preDelay` 0.3, ground selectors; S3 attack: projectile_char_leizi2_s3 every `_triggerDelta` 0.28 s), the skill prefabs
// (skchr_leizi2_1: leizi2_s_1 lifeTime 0.5; skchr_leizi2_2: the flood-fill selector `_depth` 3, `checkHeightType`) and
// the projectile prefabs (projectile_char_leizi2_t_1 / [start]: lands `_delayTime` 0.15 s after its cast, ground + air;
// projectile_char_leizi2_s3: speed 1, `_lifeTime` 3.1, `_keepAlreadyHitTime` 0.6, ground only, magical, modifier key
// "leizi2", rebounds off its source), her skeleton's clips (Skill_1_Begin 0.5 s with OnAttack at 0.3; Skill_2_Begin /
// Skill_3_Begin 0.5 s; Skill_1_End / Skill_2_End 0.933 s, Skill_3_End 0.833 s) — and PRTS 司霆惊蛰 (talent / skill 备注).
// The official map of a battle is its field (`rect`): stage tiles beyond it (another player's half) are no tiles here —
// no strike falls there, S2's range and S3's currents stop at its edge [ASSUMED].
// - Trait (解放者) "通常不攻击且阻挡数为0,技能未开启时40秒内攻击力逐渐提升至最高+200%且技能结束时重置攻击力": the librator
// profile (professions.js: no attack and block 0 while the skill is off, ATK +atk / max_stack_cnt every second up to
// +atk while it is off, reset when it ends). Module LIB-X “一念” "部署后获得+100%加成": its hidden talent init_atk
// (leizi2_e_002_tr: init_atk / (atk / max_stack_cnt) stacks at each deployment) — the profile's rampInit (her DISPLAY
// trait part carries no blackboard, unlike 玛恩纳's, so the kit hands it over).
// - T1 明断 "攻击范围内每个地块每秒有10%的概率落雷对所有敌人造成相当于攻击力100%的法术伤害。未开启技能时起飞;技能期间攻击时攻击
// 力提升至107%":
// · the strikes (PRTS 备注's algorithm, the thunder ability's numbers): every 1.0333 s from each deployment, each tile of
// her current attack range — first seen: a counter at a random 0…14; else +1, and at 15 the tile is struck and its
// counter restarts at a random 3…7; counters outlive a tile leaving the range and die with the deployment. A strike
// lands 0.15 s after its cast: atk_scale_t × ATK arts on every enemy she can select on that tile, air units too
// (PRTS 可对空). Counting pauses while she cannot act [ASSUMED: the ability does not cast under 晕眩 / 冻结].
// · 起飞 while no skill runs (leizi2_t_1[fly] in her default mode only; gamedata_const ba.liftoff): flag `liftoff` —
// no ground enemy selects her or is blocked by her (her block count is 0 then anyway). PRTS "开启技能时的降落动作与技能
// 结束时的起飞动作…期间自身不处于起飞状态,其中降落动作计入技能总时长": she lands at the cast and makes no attack for the
// landing clip (S2 / S3 0.5 s, inside the skill's time; S1's attacks are its own), and after the skill she is up
// again only when the take-off clip is over (S1 / S2 0.933 s, S3 0.833 s) — ground enemies may select her meanwhile;
// her block count is the trait's 0 from the skill's end on.
// · leizi2_t_1[on_calcul] (AtkScaleUp on ON_CALCULATE_DAMAGE while a skill runs; PRTS "对自身技能期间造成的所有伤害生效"):
// an atkScaleMul ×atk_scale[skill_up] buff for the skill's time — her attacks (ai.js resolveHit) and every damage this
// kit computes (strikes, S1, currents) multiply it. LIB-X stage 2+: the module talent's value (stage 3: 1.13).
// - T2 追责 "开启技能时,使攻击范围内所有地面地块落雷,对所有敌人造成相当于攻击力100%的法术伤害和2秒战栗": at every cast (each S1
// charge too) every low tile of her attack range — the skill's, S2's flood fill included — is struck, ring by ring by
// Manhattan distance from her tile (PRTS "自内向外"), 0.25 s per ring (the thunder `_rangeCastDelay`; 0.07 s under S1 —
// leizi2_t_1[trigger] `_specialRangeCastDelay`), each landing 0.15 s after its cast: atk_scale_t2 × ATK arts and 战栗
// (`tremble`) not_combat s on every enemy she can select there, air units too. The strikes start at the cast
// [ASSUMED: leizi2_t_1[trigger] rides her landing clip's start event (≤ 0.17 s in), not modelled].
// - S1 浩气长存 (MANUAL, 可充能 3 次, data SEARCH): a 0.5 s skill (PRTS "技能持续0.5s"; skill prefab lifeTime 0.5) whose
// range is the 3-19; no normal attack in it; 0.3 s after the cast (the abilities' `_preDelay`) three attacks — a 3-2 to
// her left, her front and her right (PRTS: the 3-19 only explains the coverage) — each hit every ground enemy she can
// select on it for attack@atk_scale_s1 × ATK physical (one on her own tile takes all three). "充能耗尽前特性不重置"
// (leizi2_s_cast: the trait resets only when no charge is left): while a charge remains the ramp is kept.
// - S2 正霆摄威 (MANUAL, data SEARCH): at the cast her range becomes the low tiles reachable from hers in up to three 4-way
// steps over the field (PRTS 备注: "任意高度类型为低地的地块以及干员自己所在的地块", "不存在地块数据的地块始终视为不合法";
// a real attack range — rangeExtend applies), fixed for the skill; attacks hit attack@max_target_s2 targets for
// attack@atk_scale_s2 × ATK physical, air units too (PRTS 攻击可对空); every lightning cast of hers while it runs
// (leizi2_s_2[try_add_atk], the thunder ability's buffsToOwnerOnCast — 追责's strikes too) gives ATK +thunder_atk, up
// to thunder_max_stack_cnt stacks, gone when it ends.
// - S3 天地通明 (MANUAL, data CUSTOM_RANGE on its 2-1): range 2-1, attack interval +base_attack_time s (flat), each attack
// hits its target and every enemy within attack@range_radius of it for attack@atk_scale_s3 × ATK physical (air units
// too: PRTS 普通攻击可对空); after each attack four currents leave the target's spot — up, right, down, left (screen
// directions), 0.28 s apart (the first at the hit [ASSUMED]) — at 1 tile/s for 3.1 s, turning back before they would
// enter a high tile, her own tile or the field's edge (PRTS "只在即将进入地块时进行反弹判定"), gone when she leaves (they
// outlive the skill [ASSUMED]); each hits every ground enemy she can select on its
// tile for attack@atk_scale_current × ATK arts, each enemy at most every 0.6 s per current, and — while S3 runs
// (leizi2_s_3 ON_OUTPUT_MODIFIER, modifier key "leizi2") — with `prob` to 战栗 it not_combat s.
import { num, talentBb, moduleBb, skillRec, batMod, up } from '../shared/tier1.js';
import { absoluteRangeKeys } from '../../../targeting.js';
import { toLocal } from '../../../dir.js';
import { COLS } from '../../../constants.js';
const S1 = 'skchr_leizi2_1';
const S2 = 'skchr_leizi2_2';
const S3 = 'skchr_leizi2_3';
/** The skill ranges when a record carries none (data: 3-19, 2-1). */
const R3_19 = Object.freeze([[3, 0], [2, 0], [1, 0], [0, 0], [0, 1], [0, 2], [0, 3], [-1, 0], [-2, 0], [-3, 0]]);
const R2_1 = Object.freeze([[2, 0], [1, 0], [1, 1], [0, 0], [0, 1], [0, 2], [-1, 0], [-1, 1], [-2, 0]]);
/** S1's three attacks (PRTS 备注): a 3-2 to her front, her left and her right (facing-RIGHT offsets). */
const S1_ARMS = Object.freeze([
Object.freeze([[0, 0], [0, 1], [0, 2], [0, 3]]),
Object.freeze([[0, 0], [1, 0], [2, 0], [3, 0]]),
Object.freeze([[0, 0], [-1, 0], [-2, 0], [-3, 0]]),
]);
/** skchr_leizi2_1 leizi2_s_1 lifeTime (PRTS "技能持续0.5s") and the s1_* abilities' `_preDelay`. */
const S1_TIME = 0.5;
const S1_STRIKE_AT = 0.3;
/** PRTS 明断 备注: "部署后每1.0333秒对自身当前攻击范围内所有地块执行计数判定" (31 ticks). */
const COUNT_IV = 31 / 30;
/** The thunder ability: a strike at `_maxCount` 15, then a restart at one of `_resetCountList` 3…7; a new tile 0…14. */
const COUNT_MAX = 15;
const RESET_MIN = 3;
const RESET_SPAN = 5;
/** projectile_char_leizi2_t_1 / [start] `_delayTime`: a strike lands this long after its cast. */
const STRIKE_DELAY = 0.15;
/** 追责: the thunder `_rangeCastDelay` per Manhattan ring; leizi2_t_1[trigger] `_specialRangeCastDelay` under S1. */
const RING_DELAY = 0.25;
const RING_DELAY_S1 = 0.07;
/** projectile_char_leizi2_s3: speed, lifetime, one hit per enemy per this long; the S3 attack's `_triggerDelta`. */
const CURRENT_SPEED = 1;
const CURRENT_LIFE = 3.1;
const CURRENT_REHIT = 0.6;
const CURRENT_GAP = 0.28;
/** PRTS: "依次生成向上/向右/向下/向左移动的电流" — screen directions as [dRow, dCol] (row 0 is the bottom). */
const CURRENT_DIRS = Object.freeze([[1, 0], [0, 1], [-1, 0], [0, -1]]);
const FOUR = Object.freeze([[1, 0], [0, 1], [-1, 0], [0, -1]]);
const AIR = Object.freeze({ canHitFly: true });
const GROUND = Object.freeze({ canHitFly: false });
/** Her skeleton's landing clips (Skill_2_Begin / Skill_3_Begin; S1's is its own 0.5 s) and take-off clips (Skill_N_End). */
const LANDING = Object.freeze({ [S2]: 0.5, [S3]: 0.5 });
const TAKEOFF = Object.freeze({ [S1]: 0.933, [S2]: 0.933, [S3]: 0.833 });
const LIFTOFF_KEY = 'talent:leizi2:liftoff';
const SKILL_UP_KEY = 'talent:leizi2:skillUp';
const S2_ATK_KEY = 'skill:leizi2:thunderAtk';
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
/** Absolute tile keys of a grid at the unit's tile and facing. */
const keysOf = (unit, grid) => absoluteRangeKeys(grid, unit.tileR, unit.tileC, unit.dir, 0);
/** A tile of the battle's field — the official map of a battle: no lightning falls on a stage tile beyond it. */
const onField = (battle, k) => battle.grid.inRect(Math.floor(k / COLS), k % COLS);
/**
* S2's range: the tiles reachable from hers in up to three 4-way steps over low tiles (her own tile always; a tile
* without data never — off the battle's field: the official map of a battle is its field, `rect`), as facing-RIGHT
* offsets (PRTS 备注, the selector's `_depth` 3).
*/
function floodGrid(battle, unit) {
const r0 = unit.tileR, c0 = unit.tileC;
const seen = new Set([r0 * COLS + c0]);
const cells = [[r0, c0]];
let frontier = [[r0, c0]];
for (let step = 0; step < 3 && frontier.length; step++) {
const next = [];
for (const [r, c] of frontier) {
for (const [dr, dc] of FOUR) {
const rr = r + dr, cc = c + dc, k = rr * COLS + cc;
if (seen.has(k) || !battle.grid.inRect(rr, cc) || battle.grid.tile(rr, cc).height !== 'LOW') continue;
seen.add(k);
next.push([rr, cc]);
cells.push([rr, cc]);
}
}
frontier = next;
}
return cells.map(([r, c]) => toLocal(r - r0, c - c0, unit.dir));
}
export default {
char_1043_leizi2: (bb, chess) => {
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
const hidden = moduleBb(chess); // LIB-X: init_atk
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
const s1 = skillRec(chess, S1), s3 = skillRec(chess, S3);
const skillUp = num(t0['atk_scale[skill_up]'], 1);
const s2Grid = [[0, 0]]; // this unit's S2 range, rebuilt at every cast
/** A lightning cast on tile `key` (T1's strikes, `accuse` = T2's): S2's stack at once, the strike 0.15 s later. */
const cast = (battle, unit, key, accuse) => {
const sk = unit.skill;
if (sk && sk.active && sk.id === S2 && num(b2.thunder_atk) > 0) {
battle.addBuff(unit, { key: S2_ATK_KEY, refresh: 'stack', stacks: 1, maxStacks: Math.max(1, Math.floor(num(b2.thunder_max_stack_cnt, 25))), mods: { atkPct: num(b2.thunder_atk) }, tags: ['skill'] });
}
const seq = unit.deploySeq;
battle.after(STRIKE_DELAY, () => {
if (!up(unit) || unit.deploySeq !== seq) return; // _stopWhenSourceInvalid
const r = Math.floor(key / COLS), c = key % COLS;
battle.fx('lightning', { x: c, y: r, id: unit.id, skill: accuse ? 'leizi2:accuse' : 'leizi2:thunder' });
const scale = num(accuse ? t1.atk_scale_t2 : t0.atk_scale_t, 1);
for (const e of battle.enemiesInKeys([key], unit, AIR)) {
if (!e.alive) continue;
battle.dealDamage(unit, e, { amount: unit.s.atk * scale * unit.s.atkScaleMul, type: 'arts', tags: ['talent', accuse ? 'leizi2:accuse' : 'leizi2:thunder'] });
if (accuse && e.alive && num(t1.not_combat) > 0) battle.applyStatus(e, 'tremble', { duration: num(t1.not_combat), source: unit });
}
}, { owner: unit });
};
return {
trait: {
// LIB-X “一念” 部署后获得+100%加成: the trait's start (the module's hidden talent; her trait part has no blackboard)
...(num(hidden.init_atk) > 0 ? { rampInit: num(hidden.init_atk) } : {}),
// S2 / S3: no attack while she lands (PRTS "降落动作计入技能总时长")
canAttack: (b, u) => !(num(u.mem.leiziLandUntil) > b.time + 1e-9),
},
skills: {
[S1]: {
kind: 'duration',
duration: S1_TIME,
targeting: { rangeGrid: s1?.rangeGrid ?? R3_19 },
attack: { noAttack: true }, // the three attacks below are the skill's
onStart({ battle, unit, skill }) {
const seq = unit.deploySeq, act = skill.activations;
battle.after(S1_STRIKE_AT, () => {
if (!up(unit) || unit.deploySeq !== seq || skill.activations !== act) return;
battle.fx('aoe', { x: unit.x, y: unit.y, radius: 3, id: unit.id, skill: 'leizi2:s1' });
for (const arm of S1_ARMS) {
for (const e of battle.enemiesInKeys(keysOf(unit, arm), unit, GROUND)) {
if (e.alive) battle.dealDamage(unit, e, { amount: unit.s.atk * num(b1['attack@atk_scale_s1'], 1) * unit.s.atkScaleMul, type: 'phys', isSkill: true, tags: ['skill', 'leizi2:s1'] });
}
}
}, { owner: unit });
},
},
[S2]: {
kind: 'duration',
targeting: { rangeGrid: s2Grid, canHitFly: true, maxTargets: Math.max(1, Math.floor(num(b2['attack@max_target_s2'], 3))) },
attack: { atkScale: num(b2['attack@atk_scale_s2'], 1) },
onStart({ battle, unit }) {
const g = floodGrid(battle, unit);
s2Grid.length = 0;
for (const p of g) s2Grid.push(p);
battle.refreshRange(unit);
},
onEnd({ battle, unit }) { battle.removeBuff(unit, S2_ATK_KEY); },
},
[S3]: {
kind: 'duration',
mods: { batPct: batMod(b3.base_attack_time, chess) },
targeting: { rangeGrid: s3?.rangeGrid ?? R2_1, canHitFly: true },
attack: {
atkScale: num(b3['attack@atk_scale_s3'], 1),
splashRadius: num(b3['attack@range_radius'], 1.65),
splashScale: 1,
onHit({ battle, unit, x, y }) { // the currents leave the target's spot
const seq = unit.deploySeq;
CURRENT_DIRS.forEach(([dr, dc], i) => {
const spawn = () => {
if (!up(unit) || unit.deploySeq !== seq) return;
(unit.mem.leiziCurrents ??= []).push({ x, y, dr, dc, life: CURRENT_LIFE, seq, hitAt: new Map() });
};
if (i === 0) spawn();
else battle.after(CURRENT_GAP * i, spawn, { owner: unit });
});
battle.fx('pulse', { x, y, id: unit.id, skill: 'leizi2:current' });
},
},
},
},
talents: [
{ install(battle, unit) { // 明断: the strikes, 起飞 while no skill runs, ×atk_scale[skill_up] during a skill
const lift = () => { if (up(unit)) battle.addBuff(unit, { key: LIFTOFF_KEY, flags: { liftoff: true }, tags: ['talent'] }); };
battle.on('deploy', (c) => {
if (c.unit !== unit) return;
if (!unit.skill?.active) lift();
if (c.move) return;
// the strike counters: a new list with every deployment
const seq = unit.deploySeq;
const counters = new Map();
battle.every(COUNT_IV, (b, sc) => {
if (!up(unit) || unit.deploySeq !== seq) { sc.cancel(); return; }
if (!unit.canAct) return;
for (const k of unit.rangeKeys) {
if (!onField(b, k)) continue;
if (!counters.has(k)) { counters.set(k, b.rng.int(COUNT_MAX)); continue; }
const n = counters.get(k) + 1;
if (n >= COUNT_MAX) { counters.set(k, RESET_MIN + b.rng.int(RESET_SPAN)); cast(b, unit, k, false); } else counters.set(k, n);
}
}, { owner: unit });
}, { owner: unit });
battle.on('skillStart', (c) => {
if (c.unit !== unit) return;
battle.removeBuff(unit, LIFTOFF_KEY);
unit.mem.leiziLandUntil = battle.time + (LANDING[unit.skill.id] ?? 0);
if (skillUp !== 1) battle.addBuff(unit, { key: SKILL_UP_KEY, mods: { atkScaleMul: skillUp }, tags: ['talent'] });
}, { owner: unit, priority: 50 });
battle.on('skillEnd', (c) => {
if (c.unit !== unit) return;
battle.removeBuff(unit, SKILL_UP_KEY);
unit.mem.leiziLandUntil = 0;
if (c.reason === 'death') return;
// (the trait's own handler releases what she blocked) — 起飞 again once the take-off clip is over
const seq = unit.deploySeq;
battle.after(TAKEOFF[unit.skill.id] ?? 0, () => { if (unit.deploySeq === seq && !unit.skill?.active) lift(); }, { owner: unit });
}, { owner: unit });
} },
{ install(battle, unit) { // 追责: every cast strikes the low tiles of her range, ring by ring from her tile
if (!(num(t1.atk_scale_t2) > 0)) return;
battle.on('skillStart', (c) => {
if (c.unit !== unit) return;
const gap = unit.skill?.id === S1 ? RING_DELAY_S1 : RING_DELAY;
const rings = new Map();
for (const k of unit.rangeKeys) {
const r = Math.floor(k / COLS), cc = k % COLS;
if (!onField(battle, k) || !battle.grid.isLow(r, cc)) continue;
const d = Math.abs(r - unit.tileR) + Math.abs(cc - unit.tileC);
if (!rings.has(d)) rings.set(d, []);
rings.get(d).push(k);
}
const seq = unit.deploySeq;
for (const d of [...rings.keys()].sort((a, b) => a - b)) {
const fire = () => { if (up(unit) && unit.deploySeq === seq) for (const k of rings.get(d)) cast(battle, unit, k, true); };
if (d === 0) fire();
else battle.after(d * gap, fire, { owner: unit });
}
}, { owner: unit });
} },
],
install(battle, unit) {
const id = unit.skill?.id;
if (id === S1) {
// 充能耗尽前特性不重置: the trait resets the ramp on skillEnd (priority 0) — keep it while a charge remains
battle.on('skillEnd', (c) => {
if (c.unit === unit) unit.mem.leiziRamp = c.reason !== 'death' && unit.skill.charges > 0 ? num(unit.trait.ramp) : null;
}, { owner: unit, priority: 100 });
battle.on('skillEnd', (c) => {
if (c.unit !== unit || unit.mem.leiziRamp == null) return;
unit.trait.ramp = unit.mem.leiziRamp;
unit.mem.leiziRamp = null;
if (unit.trait.ramp > 0 && unit.alive) battle.addBuff(unit, { key: 'trait:libratorRamp', mods: { atkPct: unit.trait.ramp } });
}, { owner: unit, priority: -100 });
}
if (id !== S3) return;
// S3: the currents
const scale = num(b3['attack@atk_scale_current']), prob = num(b3.prob), stop = num(b3.not_combat);
battle.on('tick', ({ dt }) => {
const list = unit.mem.leiziCurrents;
if (!list || !list.length) return;
const keep = [];
for (const cur of list) {
cur.life -= dt;
if (cur.life <= 1e-9 || !up(unit) || unit.deploySeq !== cur.seq) continue;
const nx = cur.x + cur.dc * CURRENT_SPEED * dt, ny = cur.y + cur.dr * CURRENT_SPEED * dt;
const r = Math.round(ny), c = Math.round(nx);
if (r !== Math.round(cur.y) || c !== Math.round(cur.x)) {
// about to enter (r, c): back off a high tile (高台), her own tile or the field's edge [ASSUMED]
if (!battle.grid.inRect(r, c) || battle.grid.tile(r, c).height === 'HIGH' || (r === unit.tileR && c === unit.tileC)) { cur.dr = 0 - cur.dr; cur.dc = 0 - cur.dc; } else { cur.x = nx; cur.y = ny; }
} else { cur.x = nx; cur.y = ny; }
keep.push(cur);
const s3On = !!(unit.skill?.active && unit.skill.id === S3);
for (const e of battle.enemiesInKeys([Math.round(cur.y) * COLS + Math.round(cur.x)], unit, GROUND)) {
const at = cur.hitAt.get(e);
if (!e.alive || (at != null && battle.time - at < CURRENT_REHIT - 1e-9)) continue;
cur.hitAt.set(e, battle.time);
const dealt = battle.dealDamage(unit, e, { amount: unit.s.atk * scale * unit.s.atkScaleMul, type: 'arts', isSkill: true, tags: ['skill', 'leizi2:current'] });
if (s3On && dealt > 0 && e.alive && stop > 0 && battle.rng.chance(prob)) battle.applyStatus(e, 'tremble', { duration: stop, source: unit });
}
if (!unit.alive) break;
}
unit.mem.leiziCurrents = keep;
}, { owner: unit });
},
};
},
};