Playtest #6 fixes: summons placed by hand, boss pool stall, bounties, 联防 leak counter, distinct offers, 机变 card text, phone buy tap, skill triggers, blocking, push force, 孤立, enemies, element bar
- #1 #2 skill / device summons (赫默's drone, 巫恋's doll, 凯瑟琳's devices) are hand cards placed by the player; placed summons deploy once at battle start, then a skill summon re-appears at its tile on each cast (the user's call) - #3 叙拉古 隐匿 ends on time (32 + 0.4 × layers s); 忍冬 S3 迷彩 lasts until her next cast (official), kept apart from 隐匿 - #4 multi-round bounty cards last two battles (the user's call; MULTI_ROUND_BOUNTY_BATTLES), blue / red card text - #5 the shared boss pool's float dust (≈3.6e-12 HP) stalled Final Assault leaders at 0 %; < 1 HP now clears the pool, and the team LP reaching 0 first is a defeat (PRTS) - #6 #10 机变 cards show their official effect text; an invisible 详情 button no longer swallows the confirming tap on phones - #7 live ×N counter of a leaker's enemies still on the 联防 field (LP loss stays capped at 10) - #8 #15 #16 skill triggers per 技能策略: skill-range triggers, injured-ally heals (古米, 塞雷娅), tank take-damage triggers with a 3 s cooldown; 新约能天使 S2 shields the highest-aggro operator in range - #9 official per-enemy model scales (威龙) - #11 element bar under the unit's HP bar (white = 元素值 left, element-coloured refill after a burst), floor rounding - #12 枯朽战车 kit per PRTS; every ranged enemy reaches its target's 0.25 collider - #13 恐惧 wander (萨科塔之翼 at half HP, operator fears make enemies flee) with re-planning after pushes - #14 push / pull distance by 力度 − 重量 (PRTS table); #17 every blocker attacks what it blocks; ASPD floor 20 - #18 炎佑 is 孤立: no heals or ally buffs - #19 every free pick-one chess offer (promotion reward, 信标, 寻呼模块) shows different operators - DESIGN §20 and the normative lines, SIM / META / DATA / PLAYING updated; 2747 unit tests and all 27 browser suites green; dev recordings regenerated
This commit is contained in:
1 parent
e6628f0cfe
commit
6464e4b46a
138 files changed
+7946
-1379
No files matched your search
@@ -3,7 +3,7 @@
|
||||
// (精锐 `_b`) id: `bb` is the Lv4 / Lv7 skill blackboard, talent/trait blackboards come from `def` (module talent and
|
||||
// trait upgrades of elites are already merged into the data), `def.raw.module.active` tells whether the module runs.
|
||||
// Every number comes from a blackboard (skill `bb`, `def.talents[i].bb`, `def.traitBb`, token data); literals below
|
||||
// are only fall-backs for missing keys or documented [ASSUMED] shapes (tornado radius, device placement …).
|
||||
// are only fall-backs for missing keys or documented [ASSUMED] shapes (tornado radius …).
|
||||
//
|
||||
// Notes shared by several kits:
|
||||
// - `base_attack_time` in skill blackboards is a FLAT change of the base attack time in seconds (白面鸮 −1.8 on 2.85 s
|
||||
@@ -30,7 +30,7 @@ import { bodyDist, bodyInKeys } from '../../body.js';
|
||||
import { absoluteRangeKeys, canTargetEnemy, sortEnemyTargets } from '../../targeting.js';
|
||||
import { normalizeChess } from '../../simdata.js';
|
||||
import { aggregateMods } from '../../buffs.js';
|
||||
import { dirFromDelta, frontOf, offsetTile } from '../../dir.js';
|
||||
import { CAT_SHIELD_KEY } from '../tokens.js';
|
||||
|
||||
const TICK_EPS = 0.01; // minimal status duration (s)
|
||||
const AURA = 0.2; // aura refresh period (s)
|
||||
@@ -53,24 +53,6 @@ const nationOf = (u) => u?.def?.raw?.nationId ?? null;
|
||||
const isAbyssal = (u) => u?.def?.raw?.groupId === 'abyssal' || ABYSSAL.has(u?.def?.charId ?? u?.def?.raw?.charId);
|
||||
const enemyHasTag = (e, tag) => !!e && e.side === 'enemy' && Array.isArray(e.def?.tags) && e.def.tags.includes(tag);
|
||||
const keySet = (unit) => unit.rangeKeySet || new Set(unit.rangeKeys || []);
|
||||
/**
|
||||
* The tokens.json variant a summon of `owner` gets — its chess (else the normal `_a` sibling, else the first variant)
|
||||
* with the owner's selected skill / module merged, as simdata getToken does — for fields normalizeToken drops
|
||||
* (stats.deployLimit). Exact when two players of one field give the same chess different loadouts (DESIGN §16).
|
||||
*/
|
||||
function ownTokenVariant(battle, tokenId, owner) {
|
||||
const vs = battle.tokenDef(tokenId, owner)?.raw?.variants;
|
||||
if (!vs || typeof vs !== 'object') return null;
|
||||
const oid = String(owner?.defId ?? '');
|
||||
let v = vs[oid] ?? vs[oid.replace(/_b$/, '_a')] ?? Object.values(vs)[0] ?? null;
|
||||
const lo = owner?.def?.loadout;
|
||||
if (v && lo && !lo.isDefault) {
|
||||
if (!lo.skillIsDefault && v.bySkill?.[lo.skillIndex]) v = { ...v, ...v.bySkill[lo.skillIndex] };
|
||||
if (!lo.moduleIsDefault && v.byModule?.[lo.moduleId]) v = { ...v, ...v.byModule[lo.moduleId] };
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/** Every living, visible enemy standing on the unit's range (ignores stealth/untargetable: auras, reveals). */
|
||||
function enemiesOnRange(battle, unit, keys = null) {
|
||||
const set = keys || keySet(unit);
|
||||
@@ -102,7 +84,7 @@ function toggleBuff(battle, unit, key, on, mods) {
|
||||
/** "技力光环(同类效果取最高)": shared key, highest value wins. */
|
||||
function spAura(battle, source, value, filter) {
|
||||
whileDeployed(battle, source, AURA, () => {
|
||||
for (const a of battle.allies(source.ownerId)) {
|
||||
for (const a of battle.alliesFor(source, source.ownerId)) {
|
||||
if (!filter(a)) continue;
|
||||
const cur = a.findBuff('aura:spRecovery');
|
||||
if (cur && cur.source !== source && cur.source?.alive && (cur.data?.v ?? 0) > value) continue;
|
||||
@@ -128,16 +110,12 @@ function inspire(battle, target, val, src, stat = 'atk') {
|
||||
}
|
||||
/** Reveal stealthed enemies on the given tiles. */
|
||||
const reveal = (battle, enemies) => { for (const e of enemies) if (e.s.flags.stealth) pulse(battle, e, 'aura:reveal', null, { flags: { reveal: true } }); };
|
||||
/** Knock-back / pull (0.5 + 0.5·force tiles, like generic.js). `toward` = {x,y} point for pulls. */
|
||||
function shove(battle, e, from, force, { toward = null, maxDist = Infinity } = {}) {
|
||||
if (!e.alive) return 0;
|
||||
const f = Math.max(0, num(force, 0));
|
||||
const dx = toward ? toward.x - e.x : e.x - from.x, dy = toward ? toward.y - e.y : e.y - from.y;
|
||||
const len = Math.hypot(dx, dy);
|
||||
if (!(len > 1e-6)) return 0;
|
||||
const dist = Math.min(0.5 + 0.5 * f, maxDist);
|
||||
if (!(dist > 0)) return 0;
|
||||
return battle.displace(e, { x: dx, y: dy }, dist, { force: f + 1 });
|
||||
/**
|
||||
* Knock-back of 力度 `force` away from `from` (a radial push, Battle.push: the official 力度 − 重量 distance — PRTS 推与拉;
|
||||
* user playtest #6 item 14). Returns the tiles moved.
|
||||
*/
|
||||
function shove(battle, e, from, force) {
|
||||
return e && e.alive ? battle.push(e, num(force, 0), { from }) : 0;
|
||||
}
|
||||
/** Free tile for a summon/device: in the rect, standable, empty and not the home tile of any ally (dead ones redeploy there). */
|
||||
function freeTile(battle, r, c, { ranged = false, ground = false } = {}) {
|
||||
@@ -203,12 +181,12 @@ function moduleRangeGrid(def) {
|
||||
return null;
|
||||
}
|
||||
const applyModuleRange = (battle, unit, def) => { const g = moduleRangeGrid(def); if (g) { unit.rangeGrid = g; battle.refreshRange(unit); } };
|
||||
/** Pull `e` towards the unit, to the tile in front of it ("拖拽至面前"); returns the distance moved. */
|
||||
/**
|
||||
* Pull `e` "至面前" of the unit with 力度 `force` (Battle.pullToFront: the official 拉力起点 half a tile ahead, 急停 0.6708
|
||||
* around it, 力度 − 重量 — weight ≤ force all the way, one heavier a third of the way, …); returns the distance moved.
|
||||
*/
|
||||
function pullToFront(battle, unit, e, force) {
|
||||
if (!e || !e.alive) return 0;
|
||||
const d = Math.hypot(unit.x - e.x, unit.y - e.y);
|
||||
if (!(d > 1 + 1e-6)) return 0;
|
||||
return shove(battle, e, unit, force, { toward: { x: unit.x, y: unit.y }, maxDist: d - 1 });
|
||||
return e && e.alive ? battle.pullToFront(e, unit, num(force, 0)) : 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
@@ -216,7 +194,7 @@ function pullToFront(battle, unit, e, force) {
|
||||
|
||||
const kits = {
|
||||
// ===== 信仰搅拌机 (shotprotector) S3 退休前布道 — ammo 30 counters; talents 扫射迎宾仪礼 / 架盾送客仪礼; module: reveal
|
||||
// S1 铳骑主考官 (TAKE_DAMAGE: next attack ×3 hits + reload an adjacent 拉特兰 ammo skill), S2 八臂电锯侠 (ammo;
|
||||
// S1 铳骑主考官 (自动触发 ⇒ DEFAULT: next attack ×3 hits + reload an adjacent 拉特兰 ammo skill), S2 八臂电锯侠 (ammo;
|
||||
// a fatal hit is blocked for `ammo_cost` bullets); module SPT-Y 老朋友: range +1
|
||||
chess_char_4_01_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0), t1 = tbb(def, 1);
|
||||
@@ -655,8 +633,7 @@ const kits = {
|
||||
},
|
||||
|
||||
// ===== 寒芒克洛丝 (fastshot) S2 封喉 — double shot → 4 shots after 40 hits; talent 中的 (20 % ×1.5 + 0.2 s stun)
|
||||
// S1 无痕 (ATK up, double shot, 迷彩 = the engine's ally `stealth`: ranged enemies cannot target her unless she
|
||||
// blocks them)
|
||||
// S1 无痕 (ATK up, double shot, 迷彩 = the engine's `camou` flag: only an enemy she blocks targets her)
|
||||
chess_char_4_06_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0);
|
||||
const need = num(bb['attack@max_stack_count'], 40);
|
||||
@@ -666,7 +643,7 @@ const kits = {
|
||||
skchr_kroos2_1: () => ({
|
||||
kind: 'duration',
|
||||
mods: { atkPct: num(bb.atk) },
|
||||
flags: { stealth: true },
|
||||
flags: { camou: true },
|
||||
attack: { hits: 2 },
|
||||
onStart({ battle, unit }) { battle.fx('camouflage', { x: unit.x, y: unit.y, id: unit.id }); },
|
||||
}),
|
||||
@@ -902,108 +879,35 @@ const kits = {
|
||||
},
|
||||
|
||||
// ===== 凯瑟琳 (craftsman) S2 战火淬炼 — stops attacking, HP/DEF up, devices give 6 %/s shields; talent 定向支援信号
|
||||
// [ASSUMED] the devices are not board pieces in this mode (data `placeable: false`): at battle start (and when she
|
||||
// redeploys, while stock lasts) she auto-places up to deployLimit of them on a free tile orthogonally next to her best
|
||||
// blockers, each pointing at that operator's tile.
|
||||
// "携带3个支援装置(最多部署2个)": the 爬行号·防护单元 are hand pieces (2 = the deploy limit) the player places and
|
||||
// turns towards an operator (user playtest #6, PRTS 卫戍协议/帮助 §战斗部署); they deploy with the board and the
|
||||
// token kit (tokens.js catShield) gives the shields — S2 overwrite_ratio per second while this skill runs — so
|
||||
// this kit places nothing (none placed ⇒ no device).
|
||||
// S1 岁月锻打 (passive: she and every other operator holding a device shield ATK/DEF +8 %/+11 %); module CRA-X
|
||||
// carries one more device (talent cnt, data)
|
||||
chess_char_4_11_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0);
|
||||
const tokenId = (def.talents || []).find((t) => t.tokenKey)?.tokenKey ?? (def.tokens || [])[0] ?? 'token_10041_cathy_catsld';
|
||||
const carried = Math.max(0, Math.floor(num(t0.cnt, 3)));
|
||||
const overwrite = num(bb.overwrite_ratio, 0.06);
|
||||
const S2 = isSel(def, 'skchr_cathy_2');
|
||||
return {
|
||||
skills: alt(def, {
|
||||
skchr_cathy_1: () => ({
|
||||
kind: 'passive',
|
||||
mods: { atkPct: num(bb.s1_atk), defPct: num(bb.s1_def) },
|
||||
onStart({ battle, unit }) {
|
||||
unit.mem.forgeAura?.cancel();
|
||||
unit.mem.forgeAura = whileDeployed(battle, unit, AURA, () => {
|
||||
for (const a of battle.allies(unit.ownerId)) {
|
||||
if (a === unit || a.kind !== 'op' || !a.findBuff('cathy:shield')) continue;
|
||||
pulse(battle, a, `cathy:forge:${unit.id}`, { atkPct: num(bb.s1_atk), defPct: num(bb.s1_def) });
|
||||
}
|
||||
});
|
||||
},
|
||||
}),
|
||||
// carries one more device (talent cnt, data — a spare the hand never shows)
|
||||
chess_char_4_11_a: (bb, chess, def) => ({
|
||||
skills: alt(def, {
|
||||
skchr_cathy_1: () => ({
|
||||
kind: 'passive',
|
||||
mods: { atkPct: num(bb.s1_atk), defPct: num(bb.s1_def) },
|
||||
onStart({ battle, unit }) {
|
||||
unit.mem.forgeAura?.cancel();
|
||||
unit.mem.forgeAura = whileDeployed(battle, unit, AURA, () => {
|
||||
for (const a of battle.allies(unit.ownerId)) {
|
||||
if (a === unit || a.kind !== 'op' || !a.findBuff(CAT_SHIELD_KEY)) continue;
|
||||
pulse(battle, a, `cathy:forge:${unit.id}`, { atkPct: num(bb.s1_atk), defPct: num(bb.s1_def) });
|
||||
}
|
||||
});
|
||||
},
|
||||
}),
|
||||
skill: {
|
||||
kind: 'duration',
|
||||
mods: { hpPct: num(bb.max_hp), defPct: num(bb.def) },
|
||||
attack: { noAttack: true },
|
||||
onStart({ battle, unit }) { battle.fx('overclock', { x: unit.x, y: unit.y, id: unit.id }); },
|
||||
},
|
||||
install(battle, unit) {
|
||||
const tok = battle.tokenDef(tokenId, unit); // the owner's skill / module variant (DESIGN §16)
|
||||
const tb = tok?.talents?.[0]?.bb || {};
|
||||
const cap = num(tb.max_shield_ratio, 0.2), each = num(tb.shield_ratio_each_trigger, 0.06);
|
||||
const idle = num(tb.interval, 5), period = Math.max(0.1, num(tb['catsld_t_1[timer][interval].interval'], num(bb.interval, 1)));
|
||||
const limit = Math.max(1, Math.floor(num(ownTokenVariant(battle, tokenId, unit)?.stats?.deployLimit, num(tok?.raw?.deployLimit, 2))));
|
||||
unit.mem.devicesLeft = carried;
|
||||
unit.mem.devices = [];
|
||||
// a device points at ONE tile (AK: range [0,0],[0,1] turned towards the chosen operator) and shields whichever
|
||||
// of her operators stands there — the same operator again after it redeploys on its tile ("装置效果不叠加":
|
||||
// one device per operator)
|
||||
const covered = (a) => unit.mem.devices.some((d) => d.alive && d.mem.tr === a.tileR && d.mem.tc === a.tileC);
|
||||
const giveShield = (target, amount, capAmt) => {
|
||||
const cur = target.findBuff('cathy:shield');
|
||||
const v = Math.min(capAmt, (cur ? cur.shield : 0) + amount);
|
||||
if (!cur || v > cur.shield + 1e-9) battle.addBuff(target, { key: 'cathy:shield', shield: v, visible: true, source: unit });
|
||||
};
|
||||
const deviceKit = (tr, tc) => ({
|
||||
skill: null, talents: [], trait: { noAttack: true },
|
||||
install(b, dev) {
|
||||
dev.mem.tr = tr; dev.mem.tc = tc;
|
||||
dev.mem.target = null;
|
||||
// token trait "不会受到攻击": untargetable (spawn option) AND immune — enemy AoE abilities that sweep every ally
|
||||
// in a radius would otherwise destroy the 100-HP device and silently end its shields
|
||||
b.addBuff(dev, { key: 'cathy:device', flags: { invulnerable: true }, persist: true, allowDead: true });
|
||||
b.every(period, () => {
|
||||
if (!dev.alive) return;
|
||||
const o = b.unitAt(tr, tc);
|
||||
const t = o && o.kind === 'op' && o.ownerId === dev.ownerId && o.deployed ? o : null;
|
||||
if (t !== dev.mem.target) { // newly connected operator: full talent shield at once
|
||||
dev.mem.target = t;
|
||||
if (t) { giveShield(t, unit.s.maxHp * cap, unit.s.maxHp * cap); b.fx('shield', { x: t.x, y: t.y, id: t.id }); }
|
||||
return;
|
||||
}
|
||||
if (!t) return;
|
||||
const capAmt = unit.s.maxHp * cap;
|
||||
if (S2 && unit.alive && skillActive(unit)) giveShield(t, unit.s.maxHp * overwrite, capAmt);
|
||||
else if (b.time - t.lastHitAt >= idle - 1e-9) giveShield(t, unit.s.maxHp * each, capAmt);
|
||||
}, { owner: dev, immediate: true });
|
||||
},
|
||||
});
|
||||
const place = () => {
|
||||
if (!unit.alive || !unit.deployed) return;
|
||||
unit.mem.devices = unit.mem.devices.filter((d) => d.alive);
|
||||
// front-most on the player's own board first (a board position; the mirrored Final Assault side counts from
|
||||
// the field's right)
|
||||
const boardCol = (x) => (x.player && x.player.mirror ? -x.tileC : x.tileC);
|
||||
const cands = battle.allies(unit.ownerId).filter((a) => a.kind === 'op' && !covered(a))
|
||||
.sort((a, b) => (b.s.blockCnt > 0) - (a.s.blockCnt > 0) || b.s.taunt - a.s.taunt || boardCol(b) - boardCol(a) || a.id - b.id);
|
||||
for (const a of cands) {
|
||||
if (unit.mem.devices.length >= limit || unit.mem.devicesLeft <= 0) break;
|
||||
const r = a.tileR, c = a.tileC;
|
||||
// orthogonal neighbours only (a diagonal device could never have the operator in its range): behind the
|
||||
// operator first (along its direction), then its two sides, then in front; each device faces the operator
|
||||
const tiles = [frontOf(r, c, a.dir, -1), offsetTile(r, c, -1, 0, a.dir), offsetTile(r, c, 1, 0, a.dir), frontOf(r, c, a.dir, 1)];
|
||||
const t = tiles.find(([rr, cc]) => freeTile(battle, rr, cc, { ranged: true }));
|
||||
if (!t) continue;
|
||||
const dev = battle.spawnToken(unit, tokenId, t[0], t[1], { kit: deviceKit(r, c), untargetable: true, dir: dirFromDelta(r - t[0], c - t[1]) });
|
||||
if (!dev) continue;
|
||||
unit.mem.devicesLeft--;
|
||||
unit.mem.devices.push(dev);
|
||||
battle.fx('device', { x: dev.x, y: dev.y, id: dev.id, target: a.id });
|
||||
}
|
||||
};
|
||||
battle.on('battleStart', place, { owner: unit });
|
||||
battle.on('deploy', (c) => { if (c.unit === unit && !c.initial) place(); }, { owner: unit });
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
skill: {
|
||||
kind: 'duration',
|
||||
mods: { hpPct: num(bb.max_hp), defPct: num(bb.def) },
|
||||
attack: { noAttack: true },
|
||||
onStart({ battle, unit }) { battle.fx('overclock', { x: unit.x, y: unit.y, id: unit.id }); },
|
||||
},
|
||||
}),
|
||||
|
||||
// ===== 歌蕾蒂娅 (hookmaster) S3 缺水的碎漩狂舞 — bind a far target, tornado: slow, 85 % arts pulses + pull, final pull
|
||||
// S1 缺水的大洋裂断 (charges: next attack pulls the target to her front, 150 %/180 %); S2 缺水的掌握怒海 (BAT +0.5 s,
|
||||
@@ -1060,8 +964,8 @@ const kits = {
|
||||
for (const e of inside) {
|
||||
if (!e.alive || e.s.flags.untargetable) continue;
|
||||
battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale, 0.85), type: 'arts', isSkill: true, tags: ['skill', 'tornado'] });
|
||||
const d = Math.hypot(T.x - e.x, T.y - e.y);
|
||||
if (e.alive && d > 0.05) dragDmg(battle, unit, e, shove(battle, e, unit, force, { toward: T, maxDist: d }));
|
||||
// "小力地拖拽至中心": a pull to the marked point (PRTS 推与拉: its 6 tornado pulls stop 0.05 from it)
|
||||
if (e.alive) dragDmg(battle, unit, e, battle.pull(e, force, { to: T, stop: 0.05 }));
|
||||
}
|
||||
battle.fx('tornadoPulse', { x: T.x, y: T.y, id: unit.id, r: R });
|
||||
},
|
||||
@@ -1141,6 +1045,7 @@ const kits = {
|
||||
battle.on('beforeAttack', (c) => {
|
||||
if (c.attacker !== unit || !skillActive(unit)) return;
|
||||
const cands = battle.enemiesInKeys(unit.rangeKeys, unit, c.profile);
|
||||
for (const e of battle.blockedTargets(unit, c.profile)) if (!cands.includes(e)) cands.push(e); // DESIGN §20.3
|
||||
if (cands.length <= 1) return;
|
||||
sortEnemyTargets(battle, unit, cands, c.profile?.priority ?? null);
|
||||
const n = Math.max(1, Math.floor((c.profile?.maxTargets || 1) + unit.s.maxTargets));
|
||||
@@ -1506,8 +1411,10 @@ const kits = {
|
||||
const g = grid(def.skill?.rangeGrid);
|
||||
return {
|
||||
skills: alt(def, {
|
||||
// (自动触发: an AUTO skill takes no 技能策略 — an AUTO DP skill fires as soon as it is ready, like 伺夜 S1)
|
||||
skchr_flamtl_1: () => ({
|
||||
kind: 'instant',
|
||||
trigger: 'SP_FULL',
|
||||
onStart({ battle, unit }) {
|
||||
battle.addDp(unit.ownerId, num(bb.cost, 6));
|
||||
battle.addBuff(unit, { key: 'flamtl:evade', visible: true });
|
||||
@@ -1526,7 +1433,7 @@ const kits = {
|
||||
if (e.alive) battle.applyStatus(e, 'stun', { duration: num(bb.stun, 0.5), source: unit });
|
||||
}
|
||||
for (const a of battle.unitsInGrid(unit, area, { side: 'ally' })) {
|
||||
if (a.kind === 'device') continue;
|
||||
if (a.kind === 'device' || !battle.allySelectable(a, unit)) continue;
|
||||
battle.addBuff(a, { key: 'flamtl:redPine', duration: num(bb['flamtl_s_2.duration'], 10), mods: { dodgePhys: num(bb['flamtl_s_2.prob'], 0.4) }, visible: true, source: unit });
|
||||
}
|
||||
battle.fx('aoe', { x: unit.x, y: unit.y, id: unit.id });
|
||||
@@ -1651,7 +1558,9 @@ const kits = {
|
||||
const own = new Set(absoluteRangeKeys(unit.rangeGrid, unit.tileR, unit.tileC, unit.dir, unit.s.rangeExtend));
|
||||
const ok = (e) => bodyInKeys(e, own) || !!(e.blockedBy && e.blockedBy.side === 'ally' && e.blockedBy.alive);
|
||||
if (c.targets.every(ok)) return;
|
||||
const cands = battle.enemiesInKeys(unit.rangeKeys, unit, c.profile).filter(ok);
|
||||
const cands = battle.enemiesInKeys(unit.rangeKeys, unit, c.profile);
|
||||
for (const e of battle.blockedTargets(unit, c.profile)) if (!cands.includes(e)) cands.push(e); // DESIGN §20.3
|
||||
for (let i = cands.length - 1; i >= 0; i--) if (!ok(cands[i])) cands.splice(i, 1);
|
||||
sortEnemyTargets(battle, unit, cands, c.profile?.priority ?? null);
|
||||
c.targets = cands.slice(0, Math.max(1, c.targets.length));
|
||||
}, { owner: unit, priority: 5 });
|
||||
@@ -1935,6 +1844,7 @@ const kits = {
|
||||
skills: alt(def, {
|
||||
skchr_cetsyr_1: () => ({
|
||||
kind: 'toggle',
|
||||
trigger: 'SP_FULL', // 自动触发 (no 技能策略: the 吟游者 row is for MANUAL skills): on as soon as it is ready
|
||||
onStart({ battle, unit }) { setTrait(unit, bb['attack@atk_to_hp_recovery_ratio']); battle.fx('mote', { x: unit.x, y: unit.y, id: unit.id }); },
|
||||
onEnd({ unit }) { resetTrait(unit); },
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user