0.2.5-dev: 耶拉 S2 (both forms) and 荒芜拉普兰德 S1 keep the operator's own hit next to the drone hit — only drone-only skills skip it (review fix of the funnel port); golden: more funnel scenarios move, attributed in docs/history/0.2.5.md
This commit is contained in:
1 parent
e0f3771821
commit
a451ddcf6b
15 files changed
+597
-384
No files matched your search
@@ -2,35 +2,18 @@
|
||||
// Conventions of the tier-4 kits: ../shared/tier4.js; kit contract and rules: ../README.md.
|
||||
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { canTargetEnemy } from '../../../targeting.js';
|
||||
import { num, tbb, skillActive, withDefaults } from '../shared/tier4.js';
|
||||
import { num, tbb, withDefaults } from '../shared/tier4.js';
|
||||
import { kjeraRelease } from './chess_char_3_20-kjera.js';
|
||||
|
||||
export default withDefaults({
|
||||
// ===== 耶拉 (funnel, hidden) S2 心随意动 — +1 drone locking targets, ATK up, chance to chill; talent 低眉
|
||||
// ===== 耶拉 (funnel, hidden) S2 心随意动 — +1 drone locking targets, ATK up, chance to chill; talent 低眉.
|
||||
// Each released drone restarts at the trait's initial scale (it does not continue the pre-skill ramp) and those
|
||||
// scales are not written back onto the single trait ramp. The body stays an ordinary in-range attack.
|
||||
chess_char_4_03_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0);
|
||||
const n = 1 + Math.max(0, Math.floor(num(bb['attack@cnt'], 1)));
|
||||
const prob = num(bb['attack@prob'], 0), cold = num(bb['attack@cold'], 0);
|
||||
const funnel = (unit) => unit.profile.funnel || { init: 0.2, delta: 0.15, max: 1.1 };
|
||||
const rel = kjeraRelease(bb, { restart: true, announce: true });
|
||||
return {
|
||||
skill: {
|
||||
kind: 'duration',
|
||||
mods: { atkPct: num(bb.atk) },
|
||||
targeting: { maxTargets: n },
|
||||
attack: {
|
||||
// every drone ramps on its own locked target (trait init/delta/max): the multipliers are queued per target
|
||||
// by the beforeAttack hook below (two drones on the same enemy ramp independently)
|
||||
funnelMul: (battle, unit, target) => {
|
||||
const q = unit.mem.kjeraQueue && unit.mem.kjeraQueue.get(target.id);
|
||||
return q && q.length ? q.shift() : funnel(unit).init;
|
||||
},
|
||||
onHit({ battle, unit, target }) {
|
||||
if (target && target.alive && cold > 0 && battle.rng.chance(prob)) battle.applyStatus(target, 'cold', { duration: cold, source: unit });
|
||||
},
|
||||
},
|
||||
onStart({ battle, unit }) { unit.mem.kjeraDrones = []; unit.mem.kjeraQueue = new Map(); battle.fx('drones', { x: unit.x, y: unit.y, id: unit.id, n }); },
|
||||
onEnd({ unit }) { unit.mem.kjeraDrones = []; unit.mem.kjeraQueue = null; },
|
||||
},
|
||||
skill: rel.skill,
|
||||
talents: [{ install(battle, unit) { // 低眉: ATK +10 %, +16 % with ≥ 2 ground tiles in range
|
||||
battle.on('deploy', (c) => {
|
||||
if (c.unit !== unit) return;
|
||||
@@ -40,38 +23,7 @@ export default withDefaults({
|
||||
battle.addBuff(unit, { key: 'kjera:t1', mods: { atkPct: v } });
|
||||
}, { owner: unit });
|
||||
} }],
|
||||
install(battle, unit) {
|
||||
// n drones, each locked on its enemy until that enemy dies (while at least one enemy is in her range). A free
|
||||
// drone takes an enemy no other drone holds, else doubles up on the best target (a lone enemy/boss gets both).
|
||||
battle.on('beforeAttack', (c) => {
|
||||
if (c.attacker !== unit || !skillActive(unit)) return;
|
||||
const ok = (e) => !!e && e.alive && canTargetEnemy(unit, e, { canHitFly: true });
|
||||
const cands = c.targets.filter(ok);
|
||||
const drones = unit.mem.kjeraDrones || (unit.mem.kjeraDrones = []);
|
||||
const held = new Set();
|
||||
for (let i = 0; i < n; i++) { if (drones[i] && ok(drones[i].e)) held.add(drones[i].e); else drones[i] = null; }
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (drones[i]) continue;
|
||||
const e = cands.find((x) => !held.has(x)) ?? cands[0] ?? drones.find((d) => d)?.e;
|
||||
if (!e) continue;
|
||||
held.add(e);
|
||||
drones[i] = { e, scale: null };
|
||||
}
|
||||
const f = funnel(unit);
|
||||
const q = unit.mem.kjeraQueue || (unit.mem.kjeraQueue = new Map());
|
||||
const out = [];
|
||||
for (const d of drones) {
|
||||
if (!d) continue;
|
||||
d.scale = d.scale == null ? f.init : Math.min(f.max, d.scale + f.delta);
|
||||
out.push(d.e);
|
||||
const l = q.get(d.e.id);
|
||||
if (l) l.push(d.scale); else q.set(d.e.id, [d.scale]);
|
||||
}
|
||||
if (out.length) c.targets = out;
|
||||
}, { owner: unit, priority: 10 });
|
||||
// queued multipliers of shots that never landed (target died mid-flight) are dropped with the target
|
||||
battle.on('death', (c) => { if (c.unit.side === 'enemy' && unit.mem.kjeraQueue) unit.mem.kjeraQueue.delete(c.unit.id); }, { owner: unit });
|
||||
},
|
||||
install(battle, unit) { rel.install(battle, unit); },
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user