feedback5 O17: 自选 kits for 琴柳, 可露希尔 (with her 指挥中心) and 澄闪

Three owned-6★ picks get operator kits (kits/ops/op-sleach.js, op-closur.js, op-gdglow.js, appended to OPERATOR_KIT_FILES),
every skill under `skills`, every talent with its module change, the trait and every module at stages 1 and 3, numbers from
the composed record; sources: backups.json, PRTS (skill / talent 备注, 指挥中心, 分支特性信息), Terra Wiki and the client's
charpacks, skill / projectile / token prefabs and buff templates.
- 琴柳: the flag as a tile effect (its 3×3: operators ASPD +10/+13, enemies −10/−13, following S2 / S3's throw); 精神感召's
  −2 on the operators off the field until the next deployment, BEA-Y Lv3 +2 DP on a 近战位 one; S1 / S2 DP over time, S2's
  DEF + 生命回复速度 (hpRegen) on the flag's operator, S3's 停顿 + 脆弱 area and the strike 1 s later; BEA-X block +1 in
  front, BEA-Y 迷彩 while a skill runs.
- 可露希尔: the 指挥中心 (placed piece or tactical point; 禁疗, range x-5 / x-4 / x-6 by her skill, 15 s 战术点形态, withdrawn
  with her) marks her 援军; trait ×1.5 vs enemies they block; S1 shields + growing DP (AUTO ⇒ SP_FULL), S2 援军 DEF / block,
  2 targets, the 40 % refund, S3 range union, 迟钝 stacks (a kit-local applyStrongest 'slowdown'), +1 target per 9 attacks;
  极限调度 ATK for 罗德岛 operators; TAC-X ×0.85.
- 澄闪: skill drones (1 + attack@cnt) locking her first target with their own ramps, 技能直接伤害 that 缴械 does not stop;
  信标的愤怒's per-drone stacks (1.5 % × stacks, sure past 40) and its 1.1 blast; S2 (AUTO, self) ⇒ SP_FULL, range 3-18;
  S3 whole field, 停顿 per hit; 精准导流 resIgnoreFlat.
No engine change. Tests: test/content/op_sleach.test.js (8), op_closur.test.js (9), op_gdglow.test.js (7).
Golden: only diy moved — diy-094 … diy-097 re-chunked (the configurations after the new kits shift), diy-098 … diy-103 new
(the three operators' forms × modules × skills); roster, bonds, fields, matches and standins unchanged.
This commit is contained in:
sganggs committed 2026-10-06 03:08:39 +08:00
1 parent 78251f499c
commit 5d5336e233
8 files changed
+2195 -99

No files matched your search

+393
View File
@@ -0,0 +1,393 @@
// server/sim/content/kits/ops/op-closur.js — 可露希尔 (char_4228_closur) 自选 operator kit: 6★ 战术家 (先锋), an owned-6★ pick of
// the tier-5 and tier-6 自选 slots; every skill, both talents, the trait, the module TAC-X 给自己的小奖杯 at every form, and the
// kit of her 援军 指挥中心 (token_10066_closur_ourbase). Kit contract and the 自选 rules: ../README.md ("How to add an operator
// (自选)").
//
// Forms (data/backups.json units.char_4228_closur): normal = E2 Lv1, skills at rank 4, no module; elite = E2 Lv60, rank 7, the
// module at stage 1 (tier 5) or 3 (tier 6) — the owner's decision of 2026-10-05. Potential 0 [ASSUMED: no account]. Sources:
// character_table / skill_table / battle_equip_table / the token's character_table row (zh_CN, as built into backups.json),
// PRTS 可露希尔 (精准投放 备注 "战术点对位于战术点攻击范围内的所有我方干员/召唤物付与可露希尔的援军标记…在攻击这些单位阻挡的敌人时同样
// 可以触发特性效果"; S1 备注 "回费间隔 = 持续时间 / 回费量,首次回费间隔为计算所得的回费间隔的一半"; S2 备注 "2秒固定间隔回复费用",
// the 【返费】 "回复干员当前部署费用属性的40%(向上取整)", "上一次撤退/离场的原因为移动…不会生效"; S3 备注 "1.66秒的固定间隔", "攻击
// 范围变为自身攻击范围+战术点的攻击范围+所有“援军”视野范围的复合范围", "能攻击到自身援军阻挡的敌人", "仅能对敌人类敌方单位施加迟钝"),
// PRTS 指挥中心 (备注: 持有禁疗; 对攻击范围内的所有我方干员/召唤物施加援军标记; x-5 / x-4 / x-6 by her skill; 撤退或被击倒 ⇒ 战术点
// 形态; 持有者离场后强制撤退), PRTS 分支特性信息 战术家, PRTS 术语释义 护盾 / 迟钝 / 部署费用下限, and the client's battle data read
// from the local install — charpack char_4228_closur (trait closur_tr; T2 deck buff closur_t_2[atk] ATK MULTIPLIER on the
// filterTag rhodes cards with `_minCostExtremeSetting`; S2 / S3 selectors: her range + the enemies blocked by a closur_friend
// holder; S3's attack buff closur[slow_down]: overrideType, refreshRemainingTimeWhenStackMax, clearAllStackCntWhenTimeUp),
// the token prefab token_10066_closur_ourbase (modes S1 / S2 / S3: closur_ourbase_aura on ground allies, operators and
// summons (professionMask 767), itself too; closur_passive with 禁疗 (abnormal flag 7); the 战术点形态 "Recharge" mode:
// 不死 / 无敌, block ×0, back at full HP — closur_ourbase_t_1[listener] / [trigger_switch] / [switch]), the skill prefabs
// skchr_closur_1 (ApplyShield: closur_friend holders, damage_block_once, one layer, `overrideType`) / _2 (ActiveAura:
// closur_s_2[friend] DEF MULTIPLIER + BLOCK_CNT, the born-state refund) / _3, buff_template_data closur_tr (AtkScaleUp vs a
// target blocked by her summon or by a closur_friend holder), closur_e_002_tr (TAC-X DamageScale vs the damage of a unit the
// holder blocks), closur_ourbase_aura (closur_friend from the host), closur_s_1[cost] / [trigger_modify_cost],
// closur_s_2[friend], closur_s_3[data], slow_down.
// - Trait (战术家) "可以在攻击范围内选择一次战术点来召唤援军,自身攻击援军阻挡的敌人时攻击力提升至150%" (trait bb atk_scale): her
// 援军 is the 指挥中心 (T1); her damage on an enemy blocked by one of her 援军 — the 指挥中心, or any ally holding her mark — ×
// atk_scale (closur_tr; every attack of hers, skill attacks included). Ranged physical, 3-3, hits air (PRTS 分支特性信息 战术家
// "普通攻击可对空"), blocks 1, ground enemies target her. TAC-X adds "援军受到来自自身阻挡单位的伤害降低15%" (hidden module talent
// damage_scale 0.85): damage on one of her 援军 from an enemy that 援军 blocks ×damage_scale.
// - 援军 (her mark, closur_friend): while her 指挥中心 stands, it and every ally of its range — operators and summons (the aura's
// ground-motion targets: every unit of our side), any player's, never a 孤立 one — hold her mark (re-read every tick). Her
// own tile counts too when the player puts the 指挥中心 next to her [ASSUMED: nothing in the text leaves her out].
// - T1 精准投放 "可以在战术点召唤指挥中心协助作战,其被击败后会在15秒后自动刷新;战术点效果范围随携带技能变化": the 指挥中心 is a hand
// piece the player places in her range (data ownerRange); her deployment brings it on its tile (deployed early when she comes
// first in the battle-start deployment, as 伺夜's pack); without a placed piece it is summoned on the tactical point
// (Battle.findTacticalPoint — tier3 tacticalPoint, the 伺夜 rule). It never attacks (ATK 0), blocks 2, holds 禁疗, and its
// range is the one of her skill (the variant's bySkill: x-5 / x-4 / x-6 — its passive skill's range here, so the card shows
// it). Knocked out (or withdrawn) it enters its 战术点形态 — off the fight, its tile kept (伺夜's 狼群 model) — for the token's
// hidden talent `interval` (15 s), then it is back on its tile at full HP; her leaving the field withdraws it for good (no
// 战术点形态: "持有者离场后强制撤退") and her next deployment brings a new one.
// - T2 极限调度 "携带可露希尔时,部署费用下限降低3,【罗德岛】干员攻击力+4%" (bb cost / atk; TAC-X stage 3: −5 / +8 %): from the battle
// start every operator of her player whose nation is 罗德岛 (character_table nationId rhodes — the client's filterTag) ATK
// +atk for the whole battle (a deck buff: on the field or not, herself included). The 部署费用下限 has no counterpart here: the
// sim's deploy costs never go below 0 (the kits' cost cuts stop there) and only the auto-redeploy spends DP [ASSUMED: no
// effect].
// - S1 递归策略 (AUTO, 8 s): the 援军 of the moment get one 护盾 layer (shieldHits 1, 不叠加: back to one; until used) and she
// gains n = min(cost + uses × cost_per_add, cost_add_max) DP over the duration — uses = her earlier casts of this deployment —
// one at a time, every 8 / n s from half that interval. An AUTO skill acting on her side only: it fires as soon as its SP is
// full (`trigger: 'SP_FULL'`, the owner's AUTO rule, kits/README.md checklist 5).
// - S2 模型扩展 (MANUAL, data SP_FULL — the 战术家 row's ALWAYS; 30 s): +cost DP at once, 15 more one per 2 s; ATK +atk, 2 targets,
// and the enemies her 援军 block are her targets too (Battle.setExtraRange: their tiles); every 援军 DEF +def (MULTIPLIER =
// Σpct) and block +block_cnt while it runs; an operator of her player deployed on a tile of the 指挥中心's range meanwhile
// gives back ceil(cost_return × its deploy cost) DP — not after a 【移动】.
// - S3 Q.E.D. (MANUAL, SP_FULL, 30 s): 18 DP one per 1.66 s; attack interval base_attack_time (a flat −0.5 s on 1.0 s); her range
// is her own + the 指挥中心's + every 援军's own range (their running grid, not their extra keys — PRTS "仅累加其复合范围中的基础
// 范围") + the tiles of the enemies her 援军 block; every hit attack@atk_scale × ATK physical and one 迟钝 stack of
// attack@slow_down on an enemy (≤ attack@max_stack_cnt, cap attack@slow_down_max), the stacks sharing one
// attack@slow_down_time timer; every attack_trigger_cnt attacks one more target (max_trigger_cnt times).
// - 迟钝 (ba.slowdown "移动速度、攻击速度…降低相应比例(同名效果取最高)"; PRTS "移动速度、攻击速度、迟钝乘数均降低相应数值(直接乘算)",
// "同名同源效果叠加并刷新时间(不超过上限);不同名/不同源迟钝效果间取当前数值最高"): one 'slowdown' effect per enemy
// (Battle.applyStrongest) of value v: move speed ×(1 − v) and attack interval ×1 / (1 − v) (ASPD ×(1 − v); the engine has
// no ASPD Σpct bucket — exact while no other attack-interval change is on). Its effect on enemy ability cooldowns and on the
// time of resistible statuses is not modelled [ASSUMED].
import { num, talentBb, moduleBb, traitBb, skillRec, batMod, up } from '../shared/tier1.js';
import { tacticalPoint } from '../shared/tier3.js';
import { COLS, FORCED_EXIT } from '../../../constants.js';
const S1 = 'skchr_closur_1';
const S2 = 'skchr_closur_2';
const S3 = 'skchr_closur_3';
export const BASE = 'token_10066_closur_ourbase';
/** 精准投放 "其被击败后会在15秒后自动刷新" when the token def carries no interval. */
const REFRESH_FALLBACK = 15;
/** Seconds between two tries of a 指挥中心 whose 战术点形态 is over but whose tile is taken. */
const RETRY = 0.25;
/** Removal reasons that put the 指挥中心 in its 战术点形态 ("撤退或被击倒"); 'expired' = her leaving / the deploy limit. */
const TAC_EXITS = new Set(['killed', 'retreat', FORCED_EXIT]);
export const SHIELD_KEY = 'closur:s1';
export const S2_KEY = 'closur:s2';
export const S3_KEY = 'closur:s3';
export const RHODES_KEY = 'talent:closur:rhodes';
export const SLOWDOWN = 'slowdown';
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const tileKey = (r, c) => r * COLS + c;
/** First talent of a normalised token def holding `key` (token talents keep no index). */
const tokTalent = (def, key) => (def?.talents ?? []).find((t) => t && t.bb && t.bb[key] != null) ?? null;
/** 迟钝 v: move speed ×(1 − v), attack interval ×1 / (1 − v). */
const slowMods = (v) => ({ moveMul: Math.max(0, 1 - v), batPct: v < 0.99 ? v / (1 - v) : 99 });
/**
* One 迟钝 stack of `unit` on enemy `e` (S3): the stacks of one source add up (≤ maxStacks, ≤ cap) and share one timer
* refreshed by each stack (cleared together when it runs out); the strongest source wins (applyStrongest).
*/
export function slowdownStack(battle, unit, e, { step, maxStacks, cap, duration }) {
if (!e || !e.alive || e.side !== 'enemy' || e.kind !== 'enemy' || !(step > 0) || !(duration > 0)) return;
const key = `closurSlow:${unit.id}`;
const cur = e.mem[key];
const n = Math.min(Math.max(1, Math.floor(maxStacks) || 1), (cur && cur.until > battle.time + 1e-9 ? cur.n : 0) + 1);
e.mem[key] = { n, until: battle.time + duration };
battle.applyStrongest(e, SLOWDOWN, { duration, value: Math.min(cap > 0 ? cap : Infinity, step * n), source: unit, mods: slowMods });
}
/**
* A skill's DP over time (the client's periodic_cost / closur_s_1[trigger_modify_cost]): `step` DP every `iv` s from
* `first` s on, `total` in all; a skill that runs its full duration pays the rest at its end.
*/
function dpTicker(unit, { total, step = 1, iv, first = iv }) {
unit.mem.closurDp = { t: 0, next: first, given: 0, total, step, iv };
}
function dpTick(battle, unit, dt) {
const d = unit.mem.closurDp;
if (!d || !(d.iv > 0) || !(d.step > 0)) return;
d.t += dt;
while (d.t + 1e-9 >= d.next && d.given + d.step <= d.total + 1e-9) {
d.given += d.step;
d.next += d.iv;
battle.addDp(unit.ownerId, d.step);
}
}
function dpEnd(battle, unit, reason) {
const d = unit.mem.closurDp;
unit.mem.closurDp = null;
if (!d) return;
const rest = d.total - d.given;
if (reason === 'duration' && rest > 1e-9) { battle.addDp(unit.ownerId, rest); d.given += rest; }
if (d.given > 0) battle.fx('dp', { x: unit.x, y: unit.y, n: d.given, id: unit.id });
}
export default {
char_4228_closur: (bb, chess) => {
const t1 = talentBb(chess, 1);
const hidden = moduleBb(chess); // TAC-X: damage_scale (the 援军's guard)
const scale = num(traitBb(chess).atk_scale, 1.5);
const guard = num(hidden.damage_scale, 1);
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
const s1 = skillRec(chess, S1);
const tokenId = (chess?.talents ?? []).find((t) => t && t.tokenKey)?.tokenKey ?? BASE;
/** Her 指挥中心 units (board pieces and summons). */
const basesOf = (battle, unit) => battle.allyUnits.filter((t) => t.kind === 'token' && t.defId === tokenId && t.ownerUnit === unit);
/** Her 援军 now (cached per tick): her standing 指挥中心 and every operator / summon of its range (no 孤立 one). */
const reinfOf = (battle, unit) => {
const m = unit.mem;
if (m.closurReinf && m.closurReinfT === battle.time) return m.closurReinf;
const set = new Set();
for (const base of basesOf(battle, unit)) {
if (!up(base)) continue;
set.add(base);
const keys = base.rangeKeySet ?? new Set(base.rangeKeys ?? []);
for (const a of battle.allyUnits) {
if (!up(a) || a.hidden || (a.kind !== 'op' && a.kind !== 'token')) continue;
if (!battle.allySelectable(a, unit) || !keys.has(tileKey(a.tileR, a.tileC))) continue;
set.add(a);
}
}
m.closurReinf = set;
m.closurReinfT = battle.time;
return set;
};
/** Tiles of the enemies her 援军 block (S2 / S3: "能攻击到自身援军阻挡的敌人"). */
const blockedTiles = (battle, unit, reinf) => {
const keys = [];
for (const r of reinf) for (const e of r.blocking) if (e.alive && e.blockedBy === r) keys.push(tileKey(Math.round(e.y), Math.round(e.x)));
return keys;
};
const setExtra = (battle, unit, keys) => {
const cur = unit.extraRangeKeys ?? [];
const uniq = [...new Set(keys)].sort((a, b) => a - b);
if (uniq.length === cur.length && uniq.every((k, i) => k === cur[i])) return;
battle.setExtraRange(unit, uniq.length ? uniq : null);
};
/** The 指挥中心's kit: no attack, 禁疗, the range of her skill, its 战术点形态. `def` = its def for her loadout. */
const baseKit = (owner, def) => {
const grid = def?.skill?.rangeGrid ?? def?.rangeGrid ?? [[0, 0]];
const refresh = num(tokTalent(def, 'interval')?.bb?.interval, REFRESH_FALLBACK);
return {
skill: { kind: 'passive', targeting: { rangeGrid: grid.map((p) => [p[0], p[1]]) } },
trait: { noAttack: true },
talents: [],
install(battle, t) {
battle.addBuff(t, { key: 'closur:base', flags: { noHeal: true }, persist: true, allowDead: true });
// 撤退 / 被击倒 ⇒ 战术点形态: off the fight for `refresh` s, its tile kept, then back at full HP (while she stands)
battle.on('death', (ctx) => {
if (ctx.unit !== t || battle.finished || !TAC_EXITS.has(ctx.reason) || !up(owner)) return;
t.removed = false;
const seq = (t.mem.closurTacSeq ?? 0) + 1;
t.mem.closurTacSeq = seq;
t.mem.closurTac = { seq, until: battle.time + refresh };
battle.fx('disappear', { x: t.x, y: t.y, id: t.id });
const back = () => {
const tp = t.mem.closurTac;
if (!tp || tp.seq !== seq || battle.finished || t.alive || t.removed) return;
if (!up(owner)) { t.mem.closurTac = null; return; }
if (battle.redeploy(t, { free: true })) { t.mem.closurTac = null; battle.fx('summon', { x: t.x, y: t.y, id: t.id, src: owner.id }); return; }
battle.after(RETRY, back, { owner: t });
};
battle.after(refresh, back, { owner: t });
}, { owner: t, priority: -10 });
},
};
};
return {
// the 援军 is the 指挥中心 (T1): no engine generic 援军; ×atk_scale vs an enemy one of her 援军 blocks
trait: {
install() {},
dmgMul(battle, unit, target) {
const b = target && target.blockedBy;
return b && reinfOf(battle, unit).has(b) ? scale : 1;
},
},
skills: {
[S1]: {
kind: 'duration',
trigger: 'SP_FULL',
onStart({ battle, unit }) {
const uses = unit.mem.closurS1Uses ?? 0;
unit.mem.closurS1Uses = uses + 1;
const n = Math.max(0, Math.min(num(b1.cost_add_max, Infinity), num(b1.cost) + uses * num(b1.cost_per_add)));
const dur = num(s1?.duration, 8);
if (n > 0) dpTicker(unit, { total: n, step: 1, iv: dur / n, first: dur / n / 2 });
const layers = Math.max(1, Math.floor(num(b1.shield_cnt, 1)));
for (const a of reinfOf(battle, unit)) {
battle.addBuff(a, { key: `${SHIELD_KEY}:${unit.id}`, shieldHits: layers, source: unit, tags: ['skill'] });
battle.fx('shield', { x: a.x, y: a.y, id: a.id, src: unit.id });
}
},
onTick({ battle, unit, dt }) { dpTick(battle, unit, dt); },
onEnd({ battle, unit, reason }) { dpEnd(battle, unit, reason); },
},
[S2]: {
kind: 'duration',
mods: { atkPct: num(b2.atk) },
targeting: { maxTargets: Math.max(1, Math.floor(num(b2['attack@max_target'], 2))) },
onStart({ battle, unit }) {
const cost = num(b2.cost);
if (cost > 0) { battle.addDp(unit.ownerId, cost); battle.fx('dp', { x: unit.x, y: unit.y, n: cost, id: unit.id }); }
dpTicker(unit, { total: num(b2.cost_period), step: num(b2['closur_s_2[add_cost_period].cost'], 1), iv: num(b2['closur_s_2[add_cost_period].interval'], 2) });
unit.mem.closurS2Set = new Set();
},
onTick({ battle, unit, dt }) {
dpTick(battle, unit, dt);
// every 援军 DEF +def, block +block_cnt; the enemies they block are her targets
const reinf = reinfOf(battle, unit);
const key = `${S2_KEY}:${unit.id}`;
const had = unit.mem.closurS2Set ?? new Set();
const mods = { defPct: num(b2.def), blockCnt: num(b2.block_cnt) };
for (const a of reinf) if (!a.findBuff(key)) battle.addBuff(a, { key, mods, source: unit, tags: ['skill'] });
for (const a of had) if (!reinf.has(a)) battle.removeBuff(a, key);
unit.mem.closurS2Set = new Set(reinf);
setExtra(battle, unit, blockedTiles(battle, unit, reinf));
},
onEnd({ battle, unit, reason }) {
dpEnd(battle, unit, reason);
for (const a of unit.mem.closurS2Set ?? []) battle.removeBuff(a, `${S2_KEY}:${unit.id}`);
unit.mem.closurS2Set = null;
battle.setExtraRange(unit, null);
},
},
[S3]: {
kind: 'duration',
mods: { batPct: batMod(b3.base_attack_time, chess) },
attack: {
atkScale: num(b3['attack@atk_scale'], 1),
onEachHit({ battle, unit, target }) {
slowdownStack(battle, unit, target, {
step: num(b3['attack@slow_down']), maxStacks: num(b3['attack@max_stack_cnt'], 10),
cap: num(b3['attack@slow_down_max']), duration: num(b3['attack@slow_down_time'], 3),
});
},
},
onStart({ battle, unit }) {
dpTicker(unit, { total: num(b3.cost_period), step: num(b3['closur_s_3[add_cost_period].cost'], 1), iv: num(b3['closur_s_3[add_cost_period].interval'], 1.66) });
unit.mem.closurS3 = { attacks: 0, extra: 0 };
battle.removeBuff(unit, S3_KEY);
},
onAttack({ battle, unit }) {
const q = unit.mem.closurS3;
if (!q) return;
q.attacks++;
if (q.extra < num(b3.max_trigger_cnt, 6) && q.attacks >= num(b3.attack_trigger_cnt, 9)) {
q.attacks = 0;
q.extra++;
battle.addBuff(unit, { key: S3_KEY, mods: { maxTargets: q.extra }, tags: ['skill'] });
}
},
onTick({ battle, unit, dt }) {
dpTick(battle, unit, dt);
// her own range + the 指挥中心's + every 援军's own range + the tiles of the enemies they block
const reinf = reinfOf(battle, unit);
const keys = [];
for (const r of reinf) {
const extra = r.extraRangeKeys ? new Set(r.extraRangeKeys) : null;
for (const k of r.rangeKeys ?? []) if (!extra || !extra.has(k)) keys.push(k);
}
keys.push(...blockedTiles(battle, unit, reinf));
setExtra(battle, unit, keys);
},
onEnd({ battle, unit, reason }) {
dpEnd(battle, unit, reason);
unit.mem.closurS3 = null;
battle.removeBuff(unit, S3_KEY);
battle.setExtraRange(unit, null);
},
},
},
talents: [
{ install(battle, unit) { // 精准投放: the 指挥中心
// her placed pieces run its kit (set up here, before the battle starts — as 鸿雪's 打字机)
for (const t of basesOf(battle, unit)) {
if (t.alive || t.deployed) continue;
if (t.kit) battle.offOwner(t);
battle._setupUnit(t, baseKit(unit, t.def));
}
const summon = () => {
if (!up(unit)) return;
const ps = basesOf(battle, unit);
const live = ps.find((t) => up(t));
if (live) { unit.trait.reinforcement = live; return; }
if (ps.some((t) => !t.alive && !t.removed && t.mem.closurTac)) return; // in its 战术点形态: back by itself
const waiting = ps.find((t) => !t.alive && !t.removed);
if (waiting && battle.redeploy(waiting, { free: true })) {
unit.trait.reinforcement = waiting;
battle.fx('summon', { x: waiting.x, y: waiting.y, id: waiting.id, src: unit.id, token: tokenId });
return;
}
const board = ps.find((t) => t.uid != null);
const tile = tacticalPoint(battle, unit, board ? [board.homeR, board.homeC] : null);
if (!tile) return;
const def = battle.tokenDef(tokenId, unit);
const t = battle.spawnToken(unit, tokenId, tile[0], tile[1], { kit: baseKit(unit, def) });
if (!t) return;
unit.trait.reinforcement = t;
battle.fx('summon', { x: t.x, y: t.y, id: t.id, src: unit.id, token: tokenId });
};
battle.on('deploy', (ctx) => {
if (ctx.unit === unit) { unit.mem.closurS1Uses = 0; summon(); return; }
if (ctx.unit.kind === 'token' && ctx.unit.defId === tokenId && ctx.unit.ownerUnit === unit) unit.trait.reinforcement = ctx.unit;
}, { owner: unit });
// "持有者离场后强制撤退场上所有的指挥中心" (no 战术点形态); one in its 战术点形态 ends it there and waits for her
battle.on('death', (ctx) => {
if (ctx.unit !== unit) return;
for (const t of basesOf(battle, unit)) {
if (t.alive) battle.retreat(t, { reason: 'expired', permanent: true });
else if (t.mem.closurTac) t.mem.closurTac = null;
}
unit.trait.reinforcement = null;
}, { owner: unit });
// TAC-X: damage on one of her 援军 from an enemy that 援军 blocks ×damage_scale
if (guard !== 1) {
battle.on('hit', (ctx) => {
const tg = ctx.target, src = ctx.source;
if (!tg || !src || src.side !== 'enemy' || src.blockedBy !== tg || !up(unit) || !reinfOf(battle, unit).has(tg)) return;
ctx.dmg.mul *= guard;
}, { owner: unit });
}
// S2: 在战术点效果范围内部署干员时,立即返还部署费用的40%(向上取整) — not after a 【移动】
if (unit.skill?.id === S2) {
const ret = num(b2.cost_return);
battle.on('deploy', (ctx) => {
const a = ctx.unit;
if (!(ret > 0) || ctx.initial || ctx.move || !a || a.kind !== 'op' || a.ownerId !== unit.ownerId || a === unit) return;
if (!unit.skill?.active || unit.skill.id !== S2 || !up(unit)) return;
const inRange = basesOf(battle, unit).some((t) => up(t) && (t.rangeKeySet ?? new Set(t.rangeKeys)).has(tileKey(a.tileR, a.tileC)));
if (!inRange) return;
const back = Math.ceil(num(a.base.cost) * ret - 1e-9);
if (back > 0) { battle.addDp(unit.ownerId, back); battle.fx('dp', { x: a.x, y: a.y, n: back, id: unit.id }); }
}, { owner: unit });
}
} },
{ install(battle, unit) { // 极限调度: the 【罗德岛】 operators of her player ATK +atk for the battle (携带可露希尔时)
const atk = num(t1.atk);
if (!(atk > 0)) return;
const give = () => {
for (const a of battle.allyUnits) {
if (a.kind !== 'op' || a.ownerId !== unit.ownerId || (a.def?.raw?.nationId ?? a.def?.nationId) !== 'rhodes') continue;
const cur = a.findBuff(RHODES_KEY);
if (cur && num(cur.data?.v) >= atk) continue;
battle.addBuff(a, { key: RHODES_KEY, mods: { atkPct: atk }, persist: true, allowDead: true, data: { v: atk }, source: unit, tags: ['talent'] });
}
};
if (battle.started) give();
else battle.on('battleStart', give, { owner: unit });
} },
],
};
},
};