Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_5_03-blaze2.js
T
sganggs 1169102d46 feedback5 R4: one file per operator kit — kits/tier1..6.js split into kits/ops/<chessId>-<codename>.js (129 kits), kits/shared/tier1..6.js (the helpers two or more kits of a tier use, and each tier's header notes) and kits/index.js (the registry, same keys in the same order); the tier files become re-export shims
Pure move: every line of a kit, of a helper and of the tier headers is copied verbatim (only the headers' first
line names the new file); a helper used by one kit moved into that kit's file. Glue only: the generated imports,
`export default { … }` around each registry entry, `export { … }` lists in shared/tier2..6.js, and tier4's
`(bb, chess, def)` default-argument wrapper as shared/tier4.js `withDefaults` (applied in each tier-4 kit file).
kits/index.js loads every kit file with a guarded dynamic import (browser-safe, no directory listing); tierN.js
re-export the tier registry (TIER_KITS) and their old named exports (tier1 helpers + tinmanKit, tier6
PITHST_ELEMENTS). The merged registry, every tier default and named export have the same keys, order, frozenness
and function sources as before. test/docs-consistency.test.js reads the moved code at its new paths.
Golden: GOLDEN_FULL=1 node --test test/golden.test.js unchanged.
2026-10-05 16:13:56 +08:00

167 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// server/sim/content/kits/ops/chess_char_5_03-blaze2.js — 烛煌 (char_1040_blaze2) kit, tier 5.
// Conventions of the tier-5 kits: ../shared/tier5.js; kit contract and rules: ../README.md.
import { COLS } from '../../../constants.js';
import { bodyInKeys } from '../../../body.js';
import {
AURA_IV, AURA_DUR, num, on, talent, traitBb, skillGrid, batPct, mods, isOp, selectedId, lazySkills, instantKind,
skillRange, burstSpUp, elementHit, whileOn, burstDamageUp,
} from '../shared/tier5.js';
/** 烛煌 绝处重燃 "使附近的敌人晕眩" radius (tiles). PRTS 备注: 1.7. */
const NEARBY_RADIUS = 1.7;
/** 烛煌 S3 "攻击变为群体攻击": splash radius around the target (tiles). PRTS 技能3 备注 "攻击溅射半径1.7". */
const BLAZE_S3_SPLASH = 1.7;
export default {
// ---------------------------------------------------------------------------------------------------------------
// 烛煌 — S3 众恶的焚场 (ammo): skill range 4-11 (the targets; the DEFAULT cast still needs an enemy in 3-1), ATK +,
// BAT −1.3 s, "攻击变为群体攻击" = one target + a BLAZE_S3_SPLASH (1.7) splash around it (PRTS 备注 "攻击溅射半径1.7",
// 中点判定 — so the fire reaches past the diamond; it used to hit every enemy inside it instead, community report E1
// after 0.1.0), +attack@atk_scale ATK elemental damage to every enemy of the attack in a burn burst (main and splash),
// dealt BEFORE the attack's own damage ("于攻击造成伤害前判定元素爆发并造成元素伤害": a 'hit' hook — so a hit that kills
// or starts the burst keeps / does not get it); loses 3 % max HP/s; any burn burst on the field refills ammo_recover
// bullets, never above the skill's ammo ("补充后的弹药数量无法超过上限"). Each landed bolt shows its splash (fx 'splash' r 1.7).
// T1 熔点引爆: burn burst anywhere → 350 % ATK elemental damage to it + heal 12 % max HP. T2 绝处重燃: downed instead of
// dying (6000 shield, no attack, no heal, 3 %/s regen) → revives at full HP and stuns nearby enemies.
// Module (elite): ×damage_scale vs enemies in an element burst.
// S1 炙手之援 (instant): the operator with the highest max HP in her range carries a fire aura for max_duration s —
// every `interval` s the enemies within range_radius of it take atk_scale × her ATK arts + element_multiplier × that
// damage as 灼燃损伤. S2 沸血燎原 (duration): skill range, BAT +0.9 s, ATK +, 3 targets; each attack costs
// attack@hp_ratio of her max HP and leaves a burning tile under every target: ground enemies on it move −50 % and take
// atk_scale × ATK arts + element_damage_scale × that as 灼燃损伤 per second (the tiles burn until the skill ends
// [ASSUMED: no duration in the data]). Module PRI-Y (elite): SP +0.2/s while an enemy in range is in an element burst.
chess_char_5_03_a: (bb, chess, def) => {
const t0 = talent(chess, 0), t1 = talent(chess, 1), tb = traitBb(chess), tm = talent(chess, -1);
const sid = selectedId(chess, def);
const lose = num(bb.lose_hp_scale);
const burnTiles = (battle, unit) => {
const tiles = unit.mem.blazeTiles;
if (!tiles || !tiles.size) return;
unit.mem.blazeAcc2 = (unit.mem.blazeAcc2 ?? 0) + AURA_IV;
const dmgTick = unit.mem.blazeAcc2 >= 1 - 1e-9;
if (dmgTick) unit.mem.blazeAcc2 -= 1;
for (const e of battle.enemies) {
if (!e.alive || e.hidden || e.isFlying || !bodyInKeys(e, tiles)) continue;
battle.addBuff(e, { key: `blaze2:ground:${unit.id}`, duration: AURA_DUR, mods: { moveMul: Math.max(0, 1 + num(bb.move_speed)) } });
if (!dmgTick) continue;
const d = battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale), type: 'arts', isSkill: true, tags: ['skill', 'blazeGround'] });
if (d > 0 && e.alive) battle.dealDamage(unit, e, { type: 'element', element: 'burn', amount: d * num(bb.element_damage_scale), tags: ['skill', 'blazeGround'] });
}
};
return {
skills: lazySkills({
skchr_blaze2_1: () => ({
kind: instantKind(chess, def),
onStart({ battle, unit }) {
const t = battle.alliesInGrid(unit).filter((a) => isOp(a)).sort((a, b) => b.s.maxHp - a.s.maxHp || a.id - b.id)[0];
if (!t) return;
const r = num(bb.range_radius, 1.5);
battle.addBuff(t, {
key: `blaze2:aid:${unit.id}`, duration: num(bb.max_duration, 20), interval: Math.max(0.1, num(bb.interval, 1)), visible: true, data: { src: unit },
onTick: ({ unit: x }) => {
for (const e of battle.foesInRadius(x.x, x.y, r)) {
const d = battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale), type: 'arts', isSkill: true, tags: ['skill', 'blazeAid'] });
if (d > 0 && e.alive) battle.dealDamage(unit, e, { type: 'element', element: 'burn', amount: d * num(bb.element_multiplier), tags: ['skill', 'blazeAid'] });
}
},
});
battle.fx('aoe', { x: t.x, y: t.y, id: unit.id, r, skill: 'blaze2Aid' });
},
}),
skchr_blaze2_2: () => ({
kind: 'duration',
mods: mods({ atkPct: num(bb.atk), batPct: batPct(bb.base_attack_time, chess) }),
targeting: skillRange(chess, def, { maxTargets: Math.max(1, num(bb['attack@max_target'], 3)) }),
attack: {
onEachHit({ unit, target }) {
if (target && target.side === 'enemy') unit.mem.blazeTiles?.add(Math.round(target.y) * COLS + Math.round(target.x));
},
},
onStart({ unit }) { unit.mem.blazeTiles = new Set(); unit.mem.blazeAcc2 = 0; },
onAttack({ battle, unit }) { battle.loseHp(unit, unit.s.maxHp * num(bb['attack@hp_ratio']), { source: unit }); },
onEnd({ unit }) { unit.mem.blazeTiles = null; },
}),
}),
skill: {
kind: 'ammo', ammo: num(bb['attack@trigger_time'], 18),
mods: mods({ atkPct: num(bb.atk), batPct: batPct(bb.base_attack_time, chess) }),
targeting: skillGrid(chess, def) ? { rangeGrid: skillGrid(chess, def) } : undefined,
attack: {
splashRadius: BLAZE_S3_SPLASH,
// the fire of each landed bolt (the screen rings splash attacks by sub-profession; 本源术师 is not one): once
// per attack at the main target / its spot — from the profile it was fired with, so the last bolt too
onHit({ battle, unit, target, x, y }) {
battle.fx('splash', { x, y, ...(target && target.alive ? { id: target.id } : {}), src: unit.id, r: BLAZE_S3_SPLASH, element: 'burn' });
},
},
onStart({ unit }) { unit.mem.blazeAcc = 0; },
onTick({ battle, unit, dt }) {
if (!(lose > 0)) return;
unit.mem.blazeAcc = (unit.mem.blazeAcc ?? 0) + dt;
while (unit.mem.blazeAcc >= 1 && unit.alive) {
unit.mem.blazeAcc -= 1;
battle.loseHp(unit, unit.s.maxHp * lose, { source: unit });
}
},
},
talents: [
{ install(battle, unit) { // 熔点引爆
battle.on('elementBurst', (c) => {
if (c.element !== 'burn' || !on(unit) || c.target.side !== 'enemy') return;
elementHit(battle, unit, c.target, unit.s.atk * num(t0.ep_damage_scale), 'blazeMelt', 'burn');
// 绝处重燃: "倒地期间…无法被治疗" — the meltdown heal does not reach her while downed
if (!unit.mem.downed) battle.heal(unit, unit, unit.s.maxHp * num(t0.hp_ratio), { self: true });
battle.fx('meltdown', { x: c.target.x, y: c.target.y, id: c.target.id });
}, { owner: unit });
} },
{ install(battle, unit) { // 绝处重燃
battle.on('deploy', (c) => { if (c.unit === unit) unit.mem.downed = false; }, { owner: unit });
battle.on('fatal', (c) => {
if (c.unit !== unit || c.prevented || unit.mem.downed) return;
c.prevented = true;
unit.mem.downed = true;
if (unit.skill?.active) unit.skill.end('downed');
battle.releaseBlocked(unit);
const dep = unit.deploySeq;
battle.addBuff(unit, {
key: 'blaze2:downed', visible: true, shield: num(t1.dynamic), interval: 0.2,
flags: { disarm: true, noHeal: true },
mods: mods({ hpRegenRatio: num(t1.hp_recovery_per_sec_by_max_hp_ratio), blockCnt: -99 }),
onTick: ({ unit: u, buff }) => {
if (u.deploySeq !== dep || u.hp < u.s.maxHp - 1e-6) return;
battle.removeBuff(u, buff);
u.mem.downed = false;
for (const e of battle.foesInRadius(u.x, u.y, NEARBY_RADIUS)) if (e.alive) battle.applyStatus(e, 'stun', { duration: num(t1.stun), source: u });
battle.fx('revive', { x: u.x, y: u.y, id: u.id, r: NEARBY_RADIUS });
},
});
battle.fx('downed', { x: unit.x, y: unit.y, id: unit.id });
}, { owner: unit, priority: -50 });
} },
],
install(battle, unit) {
burstDamageUp(battle, unit, num(tb.damage_scale));
burstSpUp(battle, unit, 'blaze2:module', num(tm.sp_recovery_per_sec));
if (sid === 'skchr_blaze2_2') whileOn(battle, unit, AURA_IV, () => burnTiles(battle, unit));
if (sid && sid !== 'skchr_blaze2_3') return;
// S3: each hit of her skill attack (main and splash) on an enemy in a burn burst deals the bonus first (PRTS 备注).
// Gated on the attack's own isSkill (captured when the bolt was fired), not on the live skill: her bolts land after
// the last bullet's end('ammo') / an early end (downed), and those still carry it. Only S3 runs here (sid gate).
battle.on('hit', (c) => {
const d = c.dmg, t = c.target;
if (c.source !== unit || !d.isAttack || !d.isSkill || d.cancel) return;
if (t.side === 'enemy' && t.alive && t.findBuff('burnBurst')) elementHit(battle, unit, t, unit.s.atk * num(bb['attack@atk_scale']), 'blazeBurn', 'burn');
}, { owner: unit });
const maxAmmo = num(bb['attack@trigger_time'], 18);
battle.on('elementBurst', (c) => { // S3: burn bursts refill ammo, up to the skill's ammo
const sk = unit.skill;
if (c.element !== 'burn' || !sk?.active || sk.kind !== 'ammo' || !on(unit)) return;
const n = Math.min(num(bb.ammo_recover), maxAmmo - sk.ammoLeft);
if (n > 0) sk.addAmmo(n);
}, { owner: unit });
},
};
},
};