feedback5 WJ: 伺夜's 狼群 takes its official 战术点形态 and S1 领袖的呼唤 acts by the pack's state (PRTS 狼群领袖 / S1 备注)

The fatal hit on the last 狼影, or a 撤退 while 伺夜 stands, no longer kills the pack. It enters its 战术点形态, the
remake's knocked-out piece: off the fight (no block, no attack, not targetable), kept, its tile reserved, 0 狼影. After
the 狼影 recovery time it comes back on its tile at full HP with 1 狼影 and a fresh 狼影 cycle. That time is the token
talent's interval, read from the data: 25 s on both chess. Until now it came back after the token's 10 s redeploy time
with 2 狼影.

Both packs share this (tokens.js installWolfTacticalPoint): the prep-placed 狼群 piece and the 伺夜 kit's own pack.
- A return timer carries the form's seq and a return cancels it, so a stale timer never revives the pack early.
- When 伺夜 leaves, a standing pack still leaves with him ('expired', no form). A pack in its form ends it with no
  return; his redeploy brings a fresh pack.

S1 casts when ready while the pack is on the field in either form, and always pays +cost DP:
① in its 战术点形态 the pack comes back at once with 1 狼影 and a fresh cycle;
② below the cap, +1 狼影, the pack's own cycle untouched;
③ at the cap, the pack's HP is reset to max.
S2 still needs a standing pack: its 备注 says nothing of the form [ASSUMED]. A held S2 gift stays on the pack through
the form [ASSUMED].

fx: the kit's pack keeps 'summon' / 'revive', the piece keeps 'wolfShadow' / 'wolfShadowLost'. Entering the form shows
'wolfShadowLost' with count 0. No new kinds, no client change. Data unchanged.

Docs: DESIGN §5.6 (SP_FULL note), PLAYING (作战中, 自动触发), SIM (token pieces), the tokens.js / kit comments.

Tests:
- kits_alt_t3, new: 3_19 战术点形态 (both chess × both packs), S1 ①②③ with the stale-timer guard, 撤退 / 伺夜 leaving.
- kits_alt_t3, rewritten: the S1 no-pack test, and the S2 auto-cast test (no cast in the form, a cast once the pack is
  back).
- kits_t3 3_19 wolf pack and tokens_devices 狼群 board piece: the form, then a return with 1 狼影.

Golden (each fields 伺夜 with his pack knocked out at its last 狼影 while he stands: 25 s out and 1 狼影 instead of
10 s and 2):
- roster-007 and roster-009: the fights change.
- roster-028 (elite S1): S1 brings the pack back at once.
- roster-026 and roster-040: 伺夜 falls before the old return; only the new 'wolfShadowLost' fx moves.
- bond-siracusaShip-high.
- matches solo-NORMAL-1, coop2-NORMAL-3, coop3-ABYSS-5, coop2-NORMAL-8-serverrun: the bots field 伺夜.
This commit is contained in:
sganggs committed 2026-10-05 19:47:12 +08:00
1 parent a98b02477d
commit 4304eb057c
11 files changed
+577 -222

No files matched your search

@@ -2,18 +2,23 @@
// 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';
import { installWolfTacticalPoint, wolfReturnNow, wolfShadowInterval, wolfTacticalPoint } from '../../tokens.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
// wolf = +1 block and one more bite, a wolf is lost instead of the pack dying; the fatal hit on the last one, or a
// 撤退, puts the pack in its 战术点形态 — out of the fight for the 狼影 interval, then back on its tile with one
// wolf: tokens.js installWolfTacticalPoint, PRTS 狼群领袖 备注); 狼群天性: 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 领袖的呼唤 (自动触发, only while the pack is on the field): +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.
// S1 领袖的呼唤 (自动触发, only while the pack is on the field — standing or in its 战术点形态): +cost DP, and by the
// pack's state (PRTS 备注) ① 战术点形态: back at once with one “狼影” and a fresh growth cycle, ② below the
// talent's maximum: one more “狼影” (its own cycle untouched), ③ at the maximum: the pack's HP to max; S2 领袖的馈赠
// (only while the pack stands): +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.
@@ -27,7 +32,13 @@ export default {
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);
/** The standing pack (狼群天性, S3, the TAC-Y mark, S2). */
const wolfOf = (u) => (u.trait.reinforcement && u.trait.reinforcement.alive ? u.trait.reinforcement : null);
/** The pack on the field in either form — standing, or in its 战术点形态 (only while 伺夜 stands: tokens.js). */
const packOf = (u) => {
const w = u.trait.reinforcement;
return w && (w.alive || wolfTacticalPoint(w)) ? w : null;
};
const wolfKit = (vigil) => ({
skill: null,
trait: { hitsFn: (b, w) => Math.max(1, w.mem.wolves || 1) },
@@ -35,11 +46,10 @@ export default {
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));
// the 狼影 interval from the data (tokens.js wolfShadowInterval): the growth cycle — and the 战术点形态's length
const grow = wolfShadowInterval(w.def);
// (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++;
@@ -47,7 +57,23 @@ export default {
fx(battle, 'summon', w, { src: vigil.id, wolves: w.mem.wolves });
return true;
};
battle.every(grow, () => w.mem.addWolf(), { owner: w });
// every deployment: the initial count — or one “狼影” back from the 战术点形态 (tokens.js wolfReturnNow: "持续时间
// 结束后狼影层数变回1层", S1 ①) — and a fresh growth cycle (the previous one stops at its next tick)
battle.on('deploy', (ctx) => {
if (ctx.unit !== w) return;
const back = w.mem.wolfReturn;
w.mem.wolves = back ? 1 : Math.max(1, Math.min(maxWolves, initial));
apply();
if (back && back.src == null) fx(battle, 'summon', w, { wolves: w.mem.wolves }); // (S1 ① shows its own)
const seq = w.deploySeq;
if (grow > 0) {
battle.every(grow, (b, sched) => {
if (!w.alive || w.deploySeq !== seq) { sched.cancel(); return; }
w.mem.addWolf();
}, { owner: w });
}
}, { owner: w, priority: 20 });
// fatal with more than one wolf: one is lost, full HP; on the last one the knock-out goes through — 战术点形态
battle.on('fatal', (ctx) => {
if (ctx.unit !== w || ctx.prevented || w.mem.wolves <= 1) return;
w.mem.wolves--;
@@ -56,6 +82,13 @@ export default {
w.hp = w.s.maxHp;
fx(battle, 'revive', w, { wolves: w.mem.wolves });
}, { owner: w });
installWolfTacticalPoint(battle, w, {
onEnter: () => {
w.mem.wolves = 0;
apply();
fx(battle, 'wolfShadowLost', w, { wolves: 0 });
},
});
const mod = tal.find((t) => t && t.bb && t.bb.damage_scale != null);
if (mod) {
battle.on('hit', (ctx) => {
@@ -102,7 +135,10 @@ export default {
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). */
/**
* One more “狼影” (S1 ②) on a standing pack: this kit's pack, or a 狼群 board piece run by content/tokens.js (same
* 'wolf:shadows' buff). False at the maximum. Neither pack's own growth cycle is touched ("与狼群本身的刷新周期互相独立").
*/
const addShadow = (battle, vigil, w) => {
if (typeof w.mem.addWolf === 'function') return w.mem.addWolf();
const n = Math.max(1, w.mem.shadows ?? 1);
@@ -115,17 +151,20 @@ export default {
return true;
};
const dpGain = (battle, unit, n) => { if (n > 0) { battle.addDp(unit.ownerId, n); fx(battle, 'dp', unit, { n }); } };
// S1: the cast — ready and the pack on the field (PRTS 备注 「仅场上存在狼群时可触发技能」)
// S1: the cast — ready and the pack on the field in either form (PRTS 备注 「仅场上存在狼群时可触发技能」 with ① for its
// 战术点形态)
const installCall = (battle, unit) => {
battle.on('tick', () => {
const sk = unit.skill;
if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !wolfOf(unit)) return;
if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !packOf(unit)) return;
sk.activate('SP_FULL');
}, { owner: unit });
};
// 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('tick', () => { // the cast: ready, the pack on the field, no unused gift on it (PRTS 备注)
// the cast: ready, the pack standing, no unused gift on it (PRTS 备注 「仅场上存在狼群,且狼群未获得此技能的充能时可触发
// 技能」; a pack in its 战术点形态 does not count — the 备注 does not say [ASSUMED])
battle.on('tick', () => {
const sk = unit.skill, w = wolfOf(unit);
if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !w || w.mem.vigilGift) return;
sk.activate('SP_FULL');
@@ -152,16 +191,19 @@ export default {
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.
// the pack when present — deployed early on its own tile if 伺夜 deploys first — never a second pack. Summoned at
// each deployment of 伺夜; a knocked-out pack is not summoned again: it sits in its 战术点形态 and comes back by
// itself (tokens.js installWolfTacticalPoint — both packs; until 0.2.0 it came back after the token's 10 s redeploy
// time with its initial wolves).
const spawn = () => {
if (!alive(unit) || wolfOf(unit)) return;
if (!alive(unit) || packOf(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 });
fx(battle, 'summon', waiting, { src: unit.id, token: wolfId, wolves: waiting.mem.shadows });
return;
}
const board = pieces.find((t) => t.uid != null);
@@ -171,11 +213,11 @@ export default {
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);
// "持有者离场后强制撤退场上的狼群(不触发上述效果)": a standing pack leaves with him ('expired': no 战术点形态); one in
// its 战术点形态 ends it there (tokens.js) and waits for his redeploy
battle.on('death', (c) => {
if (c.unit !== unit) return;
const w = wolfOf(unit);
@@ -205,23 +247,34 @@ export default {
},
skills: altSkills(chess, d, bb, {
// (自动触发: an AUTO skill takes no 技能策略 — the 战术家 row is for MANUAL skills. PRTS 备注 「仅场上存在狼群时可触发技能」
// (the owner's decision of 2026-10-05): the kit casts it as soon as it is ready while the pack stands
// (installCall); without a pack it waits at full SP — until 0.2.0 it fired at SP_FULL and paid its DP with no pack.
// The 备注 also counts the pack's 战术点形态 (after its last 狼影 is lost) as on the field — the cast brings it back
// at once with one 狼影 — and at the 狼影 cap resets the pack's HP; the kit has neither: a knocked-out pack comes
// back after its respawn time and S1 waits for it, and at the cap it only pays its DP [ASSUMED])
// (the owner's decision of 2026-10-05): the kit casts it as soon as it is ready while the pack is on the field —
// standing, or in its 战术点形态 while 伺夜 stands (installCall, packOf); without a pack it waits at full SP — until
// 0.2.0 it fired at SP_FULL and paid its DP with no pack. The cast always pays +cost DP; its effect follows the
// pack's state (备注 ①②③): ① in its 战术点形态 the pack comes back at once with one 狼影 and a fresh 狼影 cycle (its
// pending return is cancelled — tokens.js wolfReturnNow), ② standing below the maximum: one more 狼影 (the pack's
// own cycle untouched), ③ at the maximum: the pack's HP to max — a reset like the talent's, not a heal (the pack
// holds 禁疗). Until 0.2.0 a knocked-out pack was waited for and the cast at the maximum only paid its DP.)
skchr_vigil_1: (s) => ({
kind: 'instant',
trigger: 'NEVER',
onStart({ battle, unit }) {
dpGain(battle, unit, num(s.bb.cost, 0));
const w = wolfOf(unit);
if (w) addShadow(battle, unit, w);
const w = packOf(unit);
if (!w) return;
if (!w.alive) { // ①
if (wolfReturnNow(battle, w, unit.id)) fx(battle, 'summon', w, { src: unit.id, wolves: w.mem.shadows });
return;
}
if (addShadow(battle, unit, w)) return; // ②
w.hp = w.s.maxHp; // ③
fx(battle, 'revive', w, { src: unit.id, wolves: w.mem.shadows });
},
}),
// (自动触发, PRTS 备注 「仅场上存在狼群,且狼群未获得此技能的充能时可触发技能」: no enemy needed — the kit casts it as soon
// as it is ready while the pack stands and holds no unused gift (installGift); until 0.2.0 the data's DEFAULT made it
// wait for 伺夜's own next attack, so it never fired with no enemy in his range)
// wait for 伺夜's own next attack, so it never fired with no enemy in his range. A pack in its 战术点形态 does not
// count: S1's 备注 lists that form, S2's says nothing of it, so S2 waits for the pack to come back [ASSUMED]; a gift
// the pack holds stays on it through the form [ASSUMED])
skchr_vigil_2: (s) => ({
kind: 'instant',
trigger: 'NEVER',
+125 -11
View File
@@ -21,7 +21,8 @@
// full HP; its and 伺夜's attacks ignore def_penetrate_fixed DEF of enemies it blocks; while 伺夜's
// timed skill runs every attack damage instance (each bite) of the pack / 伺夜 on an enemy it blocks
// adds 伺夜 ATK × bb scale arts; module: ×damage_scale damage from enemies it blocks; is its owner's
// tactician 援军 (1.5× trait), leaves with it; respawns respawnTime s after being killed
// tactician 援军 (1.5× trait), leaves with it; the fatal hit on its last shadow (or a 撤退) ⇒ 战术点形态
// for the talent interval, then back on its tile with 1 shadow (installWolfTacticalPoint, both packs)
// 流形 copy skill (SP from data, golden +sp; starts only when an operator can be copied, else waits ready):
// copies scale × HP/ATK/DEF/RES, block, BAT/ASPD, range and the damage type of the nearest allied
// operator; no attack before its copy; ranged copy splits every N attacks (clone lasts the talent
@@ -74,10 +75,12 @@
// makes the token (Battle.spawnToken also refuses summons the owner's loadout does not produce).
//
// Exports for other content: spawnYanyou, spawnMapChar, findSummonTile, summonToken, tacticalPoint, wolfShadows,
// releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY, TOKEN_IDS; mapCharTalents and touchGospel (Touch's 攫升 /
// 超脱 and 恳切福音, shared with the Touch 补位 stand-in kit, kits/ops/standin-acmedc.js).
// wolfShadowInterval, installWolfTacticalPoint, wolfTacticalPoint, wolfReturnNow (the 狼群's 战术点形态, shared with the
// 伺夜 kit's own pack, kits/ops/chess_char_3_19-vigil.js), releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY,
// TOKEN_IDS; mapCharTalents and touchGospel (Touch's 攫升 / 超脱 and 恳切福音, shared with the Touch 补位 stand-in kit,
// kits/ops/standin-acmedc.js).
import { COLS, ROWS, MOVE_SCALE } from '../constants.js';
import { COLS, ROWS, MOVE_SCALE, FORCED_EXIT } from '../constants.js';
import { absoluteRangeKeys, sortEnemyTargets, canTargetEnemy } from '../targeting.js';
import { bodyInKeys, bodyOnTile } from '../body.js';
import { hasHp } from '../damage.js';
@@ -607,14 +610,109 @@ function paperDoll(bb, raw, def) {
};
}
/** Current “狼影” count of a 狼群 token (0 when not a wolf pack). */
/** Current “狼影” count of a 狼群 token (0 when not a wolf pack, and in its 战术点形态). */
export function wolfShadows(unit) { return unit && unit.defId === TOKEN_IDS.wolfPack ? (unit.mem.shadows ?? 0) : 0; }
// ---- 狼群 战术点形态 — both packs: the board piece (wolfPack below) and the 伺夜 kit's own pack
// (kits/ops/chess_char_3_19-vigil.js). PRTS 伺夜 天赋 狼群领袖 备注 and 狼群 召唤物信息 备注: "受到致命伤时,如果狼影层数>1则
// 消耗一层狼影并重设生命值至上限,为1则消耗一层狼影变为战术点形态;手动撤退、强制撤退时狼影层数归零并变为战术点形态;战术点形态的
// 持续时间等于“狼影”恢复时间,持续时间结束后狼影层数变回1层", "战术点形态期间:不进行普通攻击,持有无敌、强制缴械、不死…",
// "持有者离场后强制撤退场上的狼群(不触发上述效果)". The remake's 战术点形态 is its knocked-out piece: off the fight
// (`alive` false: no block, no attack, not targetable) but kept (`removed` false: its hooks live on and its tile stays
// reserved — Battle.isReservedTile — as the official device holds it), with 0 狼影, for the 狼影 recovery time
// (wolfShadowInterval: the talent's interval, data); then it is redeployed on its tile, free, at full HP with 1 狼影 and a
// fresh growth cycle (the pack's own `deploy` handler reads `mem.wolfReturn`). 伺夜 S1 领袖的呼唤 ① brings it back at
// once (wolfReturnNow); every return timer carries the form's `seq`, so a stale one never revives a pack that came back
// and fell again.
/**
* Removal reasons that put a 狼群 in its 战术点形态: the fatal hit on its last 狼影 (the engine's knock-out) and a 撤退 —
* manual (`battle.retreat`'s default reason) or forced. No sim path retreats the pack that way today; 'expired' (its
* owner leaving, the deploy limit) and 'raid' (an instant redeploy) never do.
*/
const WOLF_TAC_EXITS = new Set(['killed', 'retreat', FORCED_EXIT]);
/** Seconds between two return tries of a 狼群 whose tile is taken when its 战术点形态 ends (Battle.isReservedTile keeps it). */
const WOLF_RETURN_RETRY = 0.25;
/** The 狼群领袖 talent of a 狼群 def (its 狼影 stack talent), else its first talent. */
const wolfLeader = (def) => talentWith(def, 'vigil_wolf_t_1_enhance[trigger].max_stack_cnt') ?? def?.talents?.[0] ?? null;
/**
* “狼影” recovery time of a 狼群 def: its 狼群领袖 talent interval ("每25秒增加一只"; data — 25 s for both 伺夜 chess). The
* growth cycle and the length of the 战术点形态. 0 when the data has none.
*/
export function wolfShadowInterval(def) {
const lb = wolfLeader(def)?.bb ?? {};
return Math.max(0, num(lb['vigil_wolf_t_1_enhance[trigger].interval'] ?? lb.interval, 0));
}
/** The 战术点形态 of a 狼群 (`{ seq, since, until }`) while it is in it, else null. */
export function wolfTacticalPoint(unit) {
const tp = unit?.mem?.wolfTac;
return tp && !unit.alive && !unit.removed ? tp : null;
}
/** The pack's owner stands on the field (no owner unit: a test spawn). */
const wolfOwnerStands = (u) => !u.ownerUnit || (u.ownerUnit.alive && u.ownerUnit.deployed);
/**
* Bring a 狼群 back from its 战术点形态 at once (the end of the form; 伺夜 S1 ①: "立刻切换至拥有1只狼影的召唤物形态(会更新
* 狼群的狼影刷新周期)"): redeployed on its tile, free, at full HP with 1 狼影 and a fresh growth cycle (its `deploy`
* handler reads `mem.wolfReturn` = { src }); the pending return is cancelled. False when it is not in the form, its owner
* is off the field or its tile is taken.
*/
export function wolfReturnNow(battle, unit, src = null) {
const tp = wolfTacticalPoint(unit);
if (!tp || battle.finished || !wolfOwnerStands(unit)) return false;
unit.mem.wolfReturn = { src };
let ok;
try { ok = battle.redeploy(unit, { free: true }); } finally { unit.mem.wolfReturn = null; }
if (!ok) return false;
if (unit.mem.wolfTac === tp) unit.mem.wolfTac = null;
if (tp.timer) tp.timer.cancel();
return true;
}
/** The end of the 战术点形态 `seq` (its timer): the pack comes back — another try shortly when its tile is taken. */
function wolfTimerReturn(battle, unit, seq) {
const tp = wolfTacticalPoint(unit);
if (!tp || tp.seq !== seq || battle.finished) return;
if (wolfReturnNow(battle, unit)) return;
tp.timer = battle.after(WOLF_RETURN_RETRY, () => wolfTimerReturn(battle, unit, seq), { owner: unit });
}
/**
* The 战术点形态 of a 狼群 unit (see above). `onEnter(battle, unit)` sets its 狼影 to 0 — each pack keeps its own count,
* buff and fx.
*/
export function installWolfTacticalPoint(battle, unit, { onEnter = null } = {}) {
battle.on('death', (ctx) => {
if (ctx.unit !== unit || battle.finished || !WOLF_TAC_EXITS.has(ctx.reason) || !wolfOwnerStands(unit)) return;
unit.removed = false;
// the 狼影 recovery time from the data; a record without one falls back to the token's redeploy time (data)
const t = wolfShadowInterval(unit.def) || Math.max(0, num(unit.base.respawnTime, 0));
const seq = (unit.mem.wolfTacSeq ?? 0) + 1;
unit.mem.wolfTacSeq = seq;
const tp = { seq, since: battle.time, until: battle.time + t, timer: null };
unit.mem.wolfTac = tp;
if (onEnter) onEnter(battle, unit);
tp.timer = battle.after(t, () => wolfTimerReturn(battle, unit, seq), { owner: unit });
}, { owner: unit, priority: -10 });
// its owner leaving ends the form without a return ("不触发上述效果"): the piece waits for the owner's redeploy, which
// brings a fresh pack (its initial 狼影) — as for a standing pack, which leaves with its owner
battle.on('death', (ctx) => {
const tp = wolfTacticalPoint(unit);
if (!tp || !unit.ownerUnit || ctx.unit !== unit.ownerUnit) return;
unit.mem.wolfTac = null;
if (tp.timer) tp.timer.cancel();
}, { owner: unit });
}
/** 狼群 (伺夜 talent 狼群领袖/狼群天性; S3 bb on the token). */
function wolfPack(bb, raw, def) {
const leader = talentWith(def, 'vigil_wolf_t_1_enhance[trigger].max_stack_cnt') ?? def?.talents?.[0] ?? null;
const leader = wolfLeader(def);
const lb = leader?.bb ?? {};
const interval = num(lb['vigil_wolf_t_1_enhance[trigger].interval'] ?? lb.interval, 0);
const interval = wolfShadowInterval(def);
const perBlock = num(lb['vigil_wolf_t_1_enhance[trigger].block_cnt'] ?? lb.block_cnt, 1);
const mMax = String(leader?.description ?? '').match(/至多(\d+)只/);
const maxShadows = mMax ? +mMax[1] : 1 + num(lb['vigil_wolf_t_1_enhance[trigger].max_stack_cnt'], 0);
@@ -635,8 +733,13 @@ function wolfPack(bb, raw, def) {
const mi = ot.match(/初始(两|二|\d+)只/);
const initShadows = Math.max(1, Math.min(maxShadows, mi ? (/\d/.test(mi[1]) ? +mi[1] : 2) : maxShadows - 1));
onDeploy(battle, unit, () => {
setShadows(battle, unit, initShadows);
// back from its 战术点形态 (wolfReturnNow): 1 狼影 (PRTS 狼群 "从战术点形态转变为召唤物形态后拥有1只“狼影”") and a
// fresh growth cycle; any other deployment: the initial count
const back = unit.mem.wolfReturn;
setShadows(battle, unit, back ? 1 : initShadows);
unit.hp = unit.s.maxHp;
// (伺夜 S1 ① shows its own summon fx)
if (back && back.src == null) battle.fx('wolfShadow', { x: unit.x, y: unit.y, id: unit.id, n: 1 });
const seq = unit.deploySeq;
if (interval > 0) {
battle.every(interval, (b, sched) => {
@@ -650,6 +753,7 @@ function wolfPack(bb, raw, def) {
const o = ownerOf(unit);
if (o && o.profile?.sub === 'tactician') o.trait.reinforcement = unit;
}, 20);
// fatal with more than one 狼影: one is lost, full HP; on the last one the knock-out goes through — 战术点形态
battle.on('fatal', (ctx) => {
if (ctx.unit !== unit || ctx.prevented || !((unit.mem.shadows ?? 0) > 1)) return;
ctx.prevented = true;
@@ -657,6 +761,12 @@ function wolfPack(bb, raw, def) {
unit.hp = unit.s.maxHp;
battle.fx('wolfShadowLost', { x: unit.x, y: unit.y, id: unit.id, n: unit.mem.shadows });
}, { owner: unit, priority: -50 });
installWolfTacticalPoint(battle, unit, {
onEnter: (b, u) => {
setShadows(b, u, 0);
b.fx('wolfShadowLost', { x: u.x, y: u.y, id: u.id, n: 0 });
},
});
// 狼群天性 ("伺夜和狼群对其的攻击无视其175防御力") and the owner's S3 bonus ("狼群与伺夜攻击被狼群阻挡的单位造成伤害
// 时,额外造成相当于伺夜攻击力N%的法术伤害", one per damage instance = per bite) cover the pack's and 伺夜's own
// attacks; with a hand-authored 伺夜 kit (managed) that kit applies both. The module guard is intrinsic.
@@ -676,14 +786,14 @@ function wolfPack(bb, raw, def) {
battle.dealDamage(ctx.source, e, { amount: o.s.atk * s3Scale, type: 'arts', isSkill: true, canDodge: false, tags: ['vigil'] });
}, { owner: unit });
}
// the 援军 leaves with its tactician (a hand-authored 伺夜 kit does the same for its pack) and a killed pack
// does not come back while its tactician is down (the tactician's redeploy brings it back)
// the 援军 leaves with its tactician (a hand-authored 伺夜 kit does the same for its pack); a pack in its 战术点形态
// ends it then (installWolfTacticalPoint) and the tactician's redeploy brings it back fresh. No respawn timer of
// its own: the token's redeploy time (data 10 s) is not the 战术点形态's length (the 狼影 interval)
const tacticianOwner = () => ownerOf(unit)?.profile?.sub === 'tactician';
battle.on('death', (ctx) => {
if (ctx.unit !== unit.ownerUnit || !unit.alive || managed(unit) || !tacticianOwner()) return;
battle.retreat(unit, { reason: 'expired', permanent: true });
}, { owner: unit });
enableRespawn(battle, unit, { delay: (u) => u.base.respawnTime, requireOwner: tacticianOwner });
},
};
}
@@ -1464,6 +1574,10 @@ function ensureReinforcement(battle, owner, tokenId) {
const mine = battle.allyUnits.filter((t) => t.kind === 'token' && t.defId === tokenId && t.ownerUnit === owner && !t.mem.isClone && !t.mem.mlyssClone);
const live = mine.find((t) => t.alive);
if (live) { owner.trait.reinforcement = live; return live; }
// a 狼群 in its 战术点形态 is still the 援军 on the field: it comes back by its own timer (a 【移动】 of the owner fires
// `deploy` without ending the form)
const tac = mine.find((t) => wolfTacticalPoint(t));
if (tac) { owner.trait.reinforcement = tac; return tac; }
const waiting = mine.find((t) => !t.alive && !t.removed);
if (waiting && battle.redeploy(waiting, { free: true })) { owner.trait.reinforcement = waiting; return waiting; }
// the tactical point the player chose (the board piece's tile, else the last one's) when still usable, else the