Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_3_19-vigil.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

241 lines
13 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_3_19-vigil.js — 伺夜 (char_427_vigil) kit, tier 3.
// Conventions of the tier-3 kits: ../shared/tier3.js; kit contract and rules: ../README.md.
import { num, defOf, talentBb, selectedId, altSkills, alive, fx, textNum, tacticalPoint } from '../shared/tier3.js';
/** Tokens `tokenId` summoned by / placed for `owner` (board pieces included). */
const tokensOf = (battle, owner, tokenId) => battle.allyUnits.filter((t) => t.kind === 'token' && t.defId === tokenId && t.ownerUnit === owner && !t.mem.isClone);
export default {
// ---- 3_19 伺夜 · 战术家 — the tactical reinforcement is the wolf pack (狼群领袖: 2 wolves, +1 every 25 s up to 3, each
// wolf = +1 block and one more bite, a wolf is lost instead of the pack dying); 狼群天性: DEF ignore vs pack-blocked
// enemies; S3 领袖的尊严: DP over time, 三连击, bonus arts vs pack-blocked enemies; 精锐 module: pack takes less
// damage from the enemies it blocks (token module talent). A 狼群 piece placed in the prep phase is the pack.
// S1 领袖的呼唤 (ALWAYS): +cost DP and one more “狼影” (≤ the talent's maximum); S2 领袖的馈赠: +cost DP, the pack
// recovers hp_ratio of its max HP and its next attack hits ×atk_scale — a kill by that attack gives +cost DP.
// 精锐 module TAC-Y: trait ×165 % (profession layer) and "援军阻挡的敌人更容易受到我方的攻击": the pack's token module
// talent taunt_level (+1) goes to the enemies it blocks — the enemy-side 嘲讽等级 our operators target first
// (targeting.js; research 05: "更容易受到我方的攻击" = enemy taunt.taunt_level), never to the pack itself.
chess_char_3_19_a: (bb, chess, def) => {
const d = defOf(chess, def);
const sel = selectedId(chess, d);
const t0 = d.talents?.[0] ?? {}, t1 = talentBb(d, 1);
const wolfId = t0.tokenKey ?? (d.tokens || []).find((t) => /wolf/.test(String(t))) ?? 'token_10028_vigil_wolf';
const initial = textNum(t0.description, /初始(\d+|[一二两三四五])只/, 2);
const maxWolves = textNum(t0.description, /至多(\d+|[一二两三四五])只/, 3);
const hits = textNum(d.skill?.description, /(\d+|[一二两三四五])连击/, 3);
const bonus = num(bb['attack@vigil_s_3.atk_scale'], 0);
const pen = num(t1.def_penetrate_fixed, 0);
const wolfOf = (u) => (u.trait.reinforcement && u.trait.reinforcement.alive ? u.trait.reinforcement : null);
const wolfKit = (vigil) => ({
skill: null,
trait: { hitsFn: (b, w) => Math.max(1, w.mem.wolves || 1) },
install(battle, w) {
const tal = w.def.talents || [];
const wb = tal[0]?.bb ?? {};
const per = num(wb.block_cnt, num(wb['vigil_wolf_t_1_enhance[trigger].block_cnt'], 1));
const grow = num(wb.interval, num(wb['vigil_wolf_t_1_enhance[trigger].interval'], 25));
// (mem.shadows mirrors the count for content reading tokens.js wolfShadows())
const apply = () => { w.mem.shadows = w.mem.wolves; battle.addBuff(w, { key: 'token:wolves', mods: { blockCnt: per * w.mem.wolves }, allowDead: true }); };
w.mem.wolves = Math.max(1, Math.min(maxWolves, initial));
apply();
w.mem.addWolf = () => {
if (!w.alive || w.mem.wolves >= maxWolves) return false;
w.mem.wolves++;
apply();
fx(battle, 'summon', w, { src: vigil.id, wolves: w.mem.wolves });
return true;
};
battle.every(grow, () => w.mem.addWolf(), { owner: w });
battle.on('fatal', (ctx) => {
if (ctx.unit !== w || ctx.prevented || w.mem.wolves <= 1) return;
w.mem.wolves--;
apply();
ctx.prevented = true;
w.hp = w.s.maxHp;
fx(battle, 'revive', w, { wolves: w.mem.wolves });
}, { owner: w });
const mod = tal.find((t) => t && t.bb && t.bb.damage_scale != null);
if (mod) {
battle.on('hit', (ctx) => {
if (ctx.target === w && ctx.source && ctx.source.blockedBy === w) ctx.dmg.mul *= num(mod.bb.damage_scale, 1);
}, { owner: w });
}
},
});
// TAC-Y token module talent (taunt_level) of the pack — whichever kit runs it (this one or a tokens.js board piece):
// the enemies it blocks carry that taunt level while they stay blocked (short refreshed buff: nothing lingers when
// the pack or 伺夜 leaves)
const packTaunt = (w) => {
if (w.mem.packTaunt == null) {
const t = (w.def?.talents || []).find((x) => x && x.bb && x.bb.taunt_level != null);
w.mem.packTaunt = t ? num(t.bb.taunt_level) : 0;
}
return w.mem.packTaunt;
};
const installPackMark = (battle, unit) => {
let cur = new Set();
const levelOf = (w) => (w && alive(w) && alive(unit) ? packTaunt(w) : 0);
const mark = (e, w, lvl) => battle.addBuff(e, { key: 'token:pack_mark', duration: 0.25, refresh: 'extend', mods: { taunt: lvl }, source: w, visible: true });
// at once when the pack blocks (the operators attacking this tick already see it), kept / dropped every 0.1 s
battle.on('blocked', (ctx) => {
const w = wolfOf(unit), lvl = ctx.blocker === w ? levelOf(w) : 0;
if (lvl && ctx.enemy.alive) { mark(ctx.enemy, w, lvl); cur.add(ctx.enemy); }
}, { owner: unit });
battle.every(0.1, () => {
const w = wolfOf(unit);
const lvl = levelOf(w);
const next = new Set();
if (lvl) {
for (const e of w.blocking) {
if (!e.alive || e.side !== 'enemy' || e.blockedBy !== w) continue;
mark(e, w, lvl);
next.add(e);
}
}
for (const e of cur) {
if (next.has(e)) continue;
const b = e.findBuff('token:pack_mark');
if (b) battle.removeBuff(e, b);
}
cur = next;
}, { owner: unit, immediate: true });
};
/** One more “狼影” (S1): this kit's pack, or a 狼群 board piece run by content/tokens.js (same 'wolf:shadows' buff). */
const addShadow = (battle, vigil, w) => {
if (typeof w.mem.addWolf === 'function') return w.mem.addWolf();
const n = Math.max(1, w.mem.shadows ?? 1);
if (n >= maxWolves) return false;
const wb = w.def?.talents?.[0]?.bb ?? {};
const per = num(wb['vigil_wolf_t_1_enhance[trigger].block_cnt'], num(wb.block_cnt, 1));
w.mem.shadows = n + 1;
battle.addBuff(w, { key: 'wolf:shadows', persist: true, allowDead: true, refresh: 'replace', mods: { blockCnt: w.mem.shadows * per } });
fx(battle, 'summon', w, { src: vigil.id, wolves: w.mem.shadows });
return true;
};
const dpGain = (battle, unit, n) => { if (n > 0) { battle.addDp(unit.ownerId, n); fx(battle, 'dp', unit, { n }); } };
// S2: the pack's empowered next attack (armed by the cast; ×scale on its hits; a kill pays once)
const installGift = (battle, unit) => {
battle.on('beforeAttack', (ctx) => {
const w = wolfOf(unit);
if (!w || ctx.attacker !== w || !w.mem.vigilGift) return;
w.mem.vigilGiftOn = w.mem.vigilGift;
w.mem.vigilGift = null;
}, { owner: unit, priority: -100 });
battle.on('hit', (ctx) => {
const g = ctx.source?.mem?.vigilGiftOn;
if (g && ctx.dmg.isAttack && ctx.source === wolfOf(unit)) ctx.dmg.mul *= g.scale;
}, { owner: unit });
battle.on('kill', (ctx) => {
const g = ctx.killer?.mem?.vigilGiftOn;
if (!g || g.paid || ctx.victim.side !== 'enemy' || ctx.killer !== unit.trait.reinforcement) return;
g.paid = true;
dpGain(battle, unit, g.dp);
}, { owner: unit });
battle.on('attack', (ctx) => { if (ctx.attacker?.mem?.vigilGiftOn && ctx.attacker === unit.trait.reinforcement) ctx.attacker.mem.vigilGiftOn = null; }, { owner: unit });
};
return {
trait: { install(battle, unit) {
// The pack is the tactician's 援军. The match also hands the player the 狼群 token to place in the prep phase
// (= choosing the tactical point): that board piece (tokens.js kit, owner-coupled effects left to this kit) is
// the pack when present — deployed early on its own tile if 伺夜 deploys first — never a second pack.
const spawn = () => {
if (!alive(unit) || wolfOf(unit)) return;
const pieces = tokensOf(battle, unit, wolfId);
const live = pieces.find((t) => alive(t));
if (live) { unit.trait.reinforcement = live; return; }
const waiting = pieces.find((t) => !t.alive && !t.removed);
if (waiting && battle.redeploy(waiting, { free: true })) {
unit.trait.reinforcement = waiting;
fx(battle, 'summon', waiting, { src: unit.id, token: wolfId });
return;
}
const board = pieces.find((t) => t.uid != null);
const tile = tacticalPoint(battle, unit, board ? [board.homeR, board.homeC] : null);
if (!tile) return;
const w = battle.spawnToken(unit, wolfId, tile[0], tile[1], { kit: wolfKit(unit) });
unit.trait.reinforcement = w;
if (!w) return;
fx(battle, 'summon', w, { src: unit.id, token: wolfId, wolves: w.mem.wolves });
const again = Math.max(0, w.base.respawnTime || 0);
battle.on('death', (c) => { if (c.unit === w && c.reason === 'killed') battle.after(again, spawn, { owner: unit }); }, { owner: w });
};
battle.on('deploy', (c) => { if (c.unit === unit) spawn(); }, { owner: unit });
installPackMark(battle, unit);
battle.on('death', (c) => {
if (c.unit !== unit) return;
const w = wolfOf(unit);
if (w) battle.retreat(w, { reason: 'expired', permanent: true });
}, { owner: unit });
} },
skill: {
kind: 'duration',
attack: { hits },
onStart({ unit }) { unit.mem.vigilAcc = 0; unit.mem.vigilDp = 0; },
onTick({ battle, unit, dt }) {
const iv = num(bb.interval, 1.5), step = num(bb.cost, 1), cap = num(bb.value, 0);
if (!(iv > 0) || !(step > 0)) return; // (junk blackboard: never spin)
unit.mem.vigilAcc += dt;
while (unit.mem.vigilAcc >= iv - 1e-9 && unit.mem.vigilDp + step <= cap + 1e-9) {
unit.mem.vigilAcc -= iv;
unit.mem.vigilDp += step;
battle.addDp(unit.ownerId, step);
}
},
onEnd({ battle, unit, reason }) {
// the whole `value` is granted over a full duration (精锐: 11 × 1.364 s ends a hair after 15 s)
const rest = num(bb.value, 0) - (unit.mem.vigilDp ?? 0);
if (reason === 'duration' && rest > 1e-9) { battle.addDp(unit.ownerId, rest); unit.mem.vigilDp += rest; }
fx(battle, 'dp', unit, { n: unit.mem.vigilDp ?? 0 });
},
},
skills: altSkills(chess, d, bb, {
// (自动触发: an AUTO skill takes no 技能策略 — the 战术家 row is for MANUAL skills — and this DP skill fires as soon
// as it is ready, as before)
skchr_vigil_1: (s) => ({
kind: 'instant',
trigger: 'SP_FULL',
onStart({ battle, unit }) {
dpGain(battle, unit, num(s.bb.cost, 0));
const w = wolfOf(unit);
if (w) addShadow(battle, unit, w);
},
}),
skchr_vigil_2: (s) => ({
kind: 'instant',
onStart({ battle, unit }) {
dpGain(battle, unit, num(s.bb.cost, 0));
const w = wolfOf(unit);
if (!w) return;
const hr = num(s.bb['vigil_wolf_s_2.hp_ratio'], 0);
if (hr > 0) battle.heal(w, w, w.s.maxHp * hr, { self: true });
w.mem.vigilGift = { scale: num(s.bb['vigil_wolf_s_2.atk_scale'], 1), dp: num(s.bb['vigil_wolf_s_2.cost'], 0), paid: false };
fx(battle, 'buff', w, { src: unit.id, skill: 'vigil_2' });
},
}),
}),
install(battle, unit) { if (sel === 'skchr_vigil_2') installGift(battle, unit); },
talents: [
{ install() { /* 狼群领袖: the pack itself (trait.install / wolfKit) */ } },
{ install(battle, unit) {
battle.on('hit', (ctx) => {
const w = wolfOf(unit);
// "伺夜和狼群对其的攻击无视其175防御力": their attacks only (not item procs or other non-attack damage)
if (!w || pen <= 0 || !ctx.dmg.isAttack || (ctx.source !== unit && ctx.source !== w) || ctx.target.blockedBy !== w) return;
ctx.dmg.defIgnoreFlat += pen;
}, { owner: unit });
battle.on('damaged', (ctx) => {
const w = wolfOf(unit);
if (!w || !(bonus > 0) || !unit.skill?.active || !ctx.dmg?.isAttack || (ctx.source !== unit && ctx.source !== w)) return;
const e = ctx.target;
if (e.side !== 'enemy' || !e.alive || e.blockedBy !== w) return;
// "狼群与伺夜攻击…时,额外造成…": the extra hit belongs to that attack's dealer (a bite's bonus is the pack's
// damage — never re-typed by 伺夜's 弱点伤害 特质), its size to 伺夜's ATK (same as tokens.js unmanaged mode)
battle.dealDamage(ctx.source, e, { amount: unit.s.atk * bonus, type: 'arts', canDodge: false, isSkill: true, tags: ['skill', 'vigilBonus'] });
}, { owner: unit });
} },
],
};
},
};