feedback5 WD: generic kit — an ON_DEPLOY passive without a duration key lasts the skill's own duration from every deployment (一击即退, 10 s), 冲锋号令 gives its DP at full SP, genericTalents for the stand-ins without a kit
Every pool skill these rules meet already has a hand-authored spec (checked over all 266 chess records x skills and the 22 tokens), so only the 补位 stand-ins change; golden fast and full unchanged.
This commit is contained in:
1 parent
8ab6d021da
commit
a325fb41ad
1 file changed
+78
-4
@@ -28,8 +28,13 @@
|
||||
// "附带…凋亡/灼燃/神经损伤" + ep_damage_ratio ⇒ element damage on hit (× damage dealt when the text says "伤害N%的…损伤",
|
||||
// else × ATK); "屏障" + shield_max_hp_ratio / hp_ratio ⇒ self shield at start decaying over its duration (砾, 新约能天使);
|
||||
// "立即流失N%当前生命" + hp_ratio ⇒ self HP loss at start (宴, 风丸); hp_ratio + "恢复/回复…生命" ⇒ self heal at start.
|
||||
// Passive skills only apply stat mods (for bb.duration s when the text says "N秒内": 宴) and the self/counter effects
|
||||
// above — their scales describe procs (bombs, sword rain, counters) that need a hand-authored kit.
|
||||
// Passive skills only apply stat mods (for bb.duration s when the text says "N秒内": 宴; for the skill's own duration
|
||||
// when an ON_DEPLOY passive says "部署后…" without one in its blackboard: 一击即退, duration 10 — PRTS 技能 持续 10) and
|
||||
// the self/counter effects above — their scales describe procs (bombs, sword rain, counters) that need a kit.
|
||||
// "立即获得N点部署费用" + cost ⇒ +cost DP for the player at the start (冲锋号令); an AUTO skill that does nothing else
|
||||
// fires as soon as its SP is full (as 德克萨斯's kit casts the same skill: kits/ops/chess_char_1_08-texas.js).
|
||||
// genericTalents(def): the talents a generic kit can apply exactly — an unconditional stat line ("攻击力+8%",
|
||||
// "攻击速度+9") — used for 补位 stand-ins without a kit of their own (content/index.js); a pool chess always has a kit.
|
||||
// force→onHit displacement with the official 力度 − 重量 rules (Battle.push / pullToFront): a pull "至面前" when the text says
|
||||
// 拖拽 or for hookmasters, else a push — along the unit's direction when the text says 往攻击方向 (the 推击手 wording, PRTS
|
||||
// 推与拉 方向力) / 朝部署方向 / 向前 / 身前方向 or for 推击手 (directional), otherwise away from the unit (radial); a skill of
|
||||
@@ -277,14 +282,24 @@ export function genericSkillSpec(sk, bb = sk?.bb ?? {}, def = null) {
|
||||
ammo,
|
||||
};
|
||||
|
||||
// passive stat buffs limited in time ("部署后…在14秒内攻击力+65%", 宴) become a timed buff at each deployment
|
||||
const passiveTimed = passive && Object.keys(mods).length && num(bb.duration) > 0 && /\d+(\.\d+)?秒内/.test(desc) ? num(bb.duration) : 0;
|
||||
// passive stat buffs limited in time ("部署后…在14秒内攻击力+65%", 宴) become a timed buff at each deployment; so do the
|
||||
// ON_DEPLOY passives whose duration is the skill's own (一击即退 "部署后攻击力+X%,防御力+Y%", duration 10, no bb key)
|
||||
const ownDuration = num(bb.duration) === undefined && sk.duration > 0 && /^部署后/.test(desc) ? sk.duration : 0;
|
||||
const passiveTimed = passive && Object.keys(mods).length
|
||||
? (num(bb.duration) > 0 && /\d+(\.\d+)?秒内/.test(desc) ? num(bb.duration) : ownDuration)
|
||||
: 0;
|
||||
if (Object.keys(mods).length && !passiveTimed) spec.mods = mods;
|
||||
if (Object.keys(targeting).length) spec.targeting = targeting;
|
||||
// instant/charges skills act on the next attack: mods/targeting without an explicit attack still need one
|
||||
if (!Object.keys(attack).length && (kind === 'instant' || kind === 'charges') && (spec.mods || spec.targeting)) spec.attack = {};
|
||||
if (Object.keys(attack).length) spec.attack = attack;
|
||||
|
||||
// ---- deployment cost: "立即获得N点部署费用" (冲锋号令 `cost`) — the player gains it at the start
|
||||
const dpGain = !passive && /获得\d+点部署费用/.test(desc) ? g('cost') : undefined;
|
||||
const gainsDp = dpGain !== undefined && dpGain > 0;
|
||||
// an AUTO skill with nothing else to do fires as soon as its SP is full (德克萨斯's kit casts 冲锋号令·γ型 so)
|
||||
if (gainsDp && sk.skillType === 'AUTO' && !spec.mods && !spec.targeting && !spec.attack) spec.trigger = 'SP_FULL';
|
||||
|
||||
// ---- start / end effects
|
||||
const starts = [];
|
||||
const ends = [];
|
||||
@@ -292,6 +307,12 @@ export function genericSkillSpec(sk, bb = sk?.bb ?? {}, def = null) {
|
||||
const m = { ...mods };
|
||||
starts.push(({ battle, unit }) => battle.addBuff(unit, { key: `generic:passive:${sk.id ?? 'skill'}`, duration: passiveTimed, mods: m, tags: ['skill'] }));
|
||||
}
|
||||
if (gainsDp) {
|
||||
starts.push(({ battle, unit }) => {
|
||||
battle.addDp(unit.ownerId, dpGain);
|
||||
battle.fx('dp', { x: unit.x, y: unit.y, n: dpGain, id: unit.id });
|
||||
});
|
||||
}
|
||||
const hr = g('hp_ratio');
|
||||
if (hr !== undefined && hr > 0 && hr <= 1 && /立即流失\d+(\.\d+)?%(的)?当前生命/.test(desc)) {
|
||||
starts.push(({ battle, unit }) => { const loss = unit.hp * hr; if (loss > 0 && unit.hp - loss >= 1) battle.loseHp(unit, loss, { source: unit }); });
|
||||
@@ -377,6 +398,59 @@ function installGeneric(spec) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Stat talents genericTalents applies: the stat named in the text → [blackboard key, mod for "+N%", mod for "+N"]. */
|
||||
const TALENT_STATS = Object.freeze({
|
||||
攻击力: ['atk', 'atkPct', 'atkFlat'], 防御力: ['def', 'defPct', 'defFlat'], 生命上限: ['max_hp', 'hpPct', 'hpFlat'],
|
||||
攻击速度: ['attack_speed', null, 'aspd'],
|
||||
});
|
||||
const STAT_CLAUSE = /^(攻击力|防御力|生命上限|攻击速度)\+(\d+(?:\.\d+)?)(%?)$/;
|
||||
|
||||
/**
|
||||
* The mods of an unconditional stat talent — its text is only stat clauses ("攻击力+8%", "攻击速度+9", joined by "," /
|
||||
* "、") and each clause's number is its blackboard key's (×100 for a percentage); every other blackboard key is 0 —
|
||||
* else null.
|
||||
*/
|
||||
export function statTalentMods(t) {
|
||||
const text = String(t?.description ?? t?.desc ?? '').trim();
|
||||
const bb = t?.bb ?? {};
|
||||
if (!text) return null;
|
||||
const mods = {};
|
||||
const used = new Set();
|
||||
for (const part of text.split(/[,、,]/)) {
|
||||
const m = STAT_CLAUSE.exec(part.trim());
|
||||
if (!m) return null;
|
||||
const [key, pctMod, flatMod] = TALENT_STATS[m[1]];
|
||||
const v = num(bb[key]);
|
||||
const pct = m[3] === '%';
|
||||
const mod = pct ? pctMod : flatMod;
|
||||
if (v === undefined || !mod || used.has(key) || Math.abs((pct ? v * 100 : v) - Number(m[2])) > 1e-6) return null;
|
||||
mods[mod] = v;
|
||||
used.add(key);
|
||||
}
|
||||
if (Object.keys(bb).some((k) => !used.has(k) && num(bb[k]) !== 0)) return null;
|
||||
return Object.keys(mods).length ? mods : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talents the generic kit can apply exactly — the 补位 stand-ins without a kit of their own (content/index.js
|
||||
* setupUnitKit; 预备干员 "攻击提升 攻击力+8%", "防御提升 防御力+10%", "施法速度提升 攻击速度+9"): every unconditional stat
|
||||
* talent (statTalentMods) as a persistent buff, as the operator kits write theirs (`talent:` key, persist, allowDead).
|
||||
* Any other talent — a condition, a proc, a key its text does not state — needs a hand-authored kit and is left out,
|
||||
* never half applied.
|
||||
* @param {object} def normalised operator def
|
||||
* @returns {{ install(battle: object, unit: object): void }[]}
|
||||
*/
|
||||
export function genericTalents(def) {
|
||||
const out = [];
|
||||
(def?.talents ?? []).forEach((t, i) => {
|
||||
const mods = statTalentMods(t);
|
||||
if (!mods) return;
|
||||
const key = `talent:generic:${i}`;
|
||||
out.push({ install(battle, unit) { battle.addBuff(unit, { key, mods: { ...mods }, persist: true, allowDead: true, tags: ['talent'] }); } });
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic kit: `(bb, chess, def?) => Kit`. `chess` is the data record; `def` the normalised def when available.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user