feedback5: merge K1 — stand-in kits Sharp, 领主·Sharp, Misery (every skill on every form; PRTS-checked)
This commit is contained in:
commit
5090ac1ed3
7 files changed
+878
-1
No files matched your search
@@ -64,7 +64,7 @@ export const KIT_FILES = Object.freeze([
|
||||
* A stand-in without a file fights with the generic kit (content/generic.js) plus its unconditional stat talents
|
||||
* (genericTalents) — the eight 预备干员 need no file.
|
||||
*/
|
||||
export const STANDIN_KIT_FILES = Object.freeze([]);
|
||||
export const STANDIN_KIT_FILES = Object.freeze(['standin-acguad.js', 'standin-sharp2.js', 'standin-acspec.js']);
|
||||
|
||||
async function loadKitFile(file) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// server/sim/content/kits/ops/standin-acguad.js — Sharp (char_609_acguad, 卫戍协议 6★ 近卫·无畏者) 补位 stand-in kit.
|
||||
// Stands in for 忍冬 (tier 3, S3), 银灰 / 百炼嘉维尔 (tier 4, S2), 隐德来希 / 归溟幽灵鲨 (tier 5, S3 + DRE-X) and 佩佩
|
||||
// (tier 6, S3 + DRE-X): data/backups.json `units.char_609_acguad.standsIn`. Kit contract: ../README.md "Stand-in kits".
|
||||
// Numbers: the blackboards of data/backups.json (skill_table / character_table / battle_equip_table at E2 Lv1 skill
|
||||
// level 4 and E2 Lv60 skill level 7); what the data does not carry: PRTS Sharp(卫戍协议), cited where used.
|
||||
|
||||
import { num, talentBb, traitBb, skillRec, statBuff, toggleBuff } from '../shared/tier1.js';
|
||||
|
||||
const S1 = 'skchr_acguad_1';
|
||||
const S2 = 'skchr_acguad_2';
|
||||
const S3 = 'skchr_acguad_3';
|
||||
/** 力战不竭's ATK layers: one buff whose stacks are the layers. */
|
||||
const STACK_KEY = 'acguad:s3stack';
|
||||
|
||||
export default {
|
||||
// Sharp — talent 1 隐匿刀刃: ATK +atk, physical dodge prob (DRE-X levels 2 / 3 upgrade both). Talent 2 寸步不退: after
|
||||
// `interval` s on the field (each deployment), ASPD +attack_speed.
|
||||
// S1 快刀: ATK +atk; each attack becomes a double hit with attack@prob1 (data trigger: SKILL_RANGE on its 1-1 range).
|
||||
// S2 亮剑: DEF to 0 (no blackboard key: the text "防御力降至0" — a final ×0, so no DEF bonus survives it), max HP
|
||||
// +max_hp, every attack at attack@atk_scale.
|
||||
// S3 力战不竭: ATK +atk; before each attack +atk_each_stack ATK (one layer, at most max_atk_stack_cnt), all layers lost
|
||||
// when the target changes; HP never below 1 while it runs. PRTS 备注: "“每次攻击时获得增益”在每次成功攻击前判定,至少生效1层
|
||||
// 增益,持续至技能结束" — the first attack on a target already has one layer, and the layers last until the skill ends.
|
||||
// Module DRE-X (trait atk_scale): "攻击被阻挡的敌人时攻击力提升至115%" — an enemy blocked by anyone, as 斯卡蒂's DRE-X.
|
||||
// Ground only, ground-targetable (data canHitFly false, no flag). S2 / S3 cast by the data trigger (DEFAULT).
|
||||
char_609_acguad: (bb, chess) => {
|
||||
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
|
||||
const blockedScale = num(traitBb(chess).atk_scale, 0);
|
||||
const b1 = skillRec(chess, S1)?.bb ?? {}, b2 = skillRec(chess, S2)?.bb ?? {}, b3 = skillRec(chess, S3)?.bb ?? {};
|
||||
const doubleProb = num(b1['attack@prob1'], 0.2);
|
||||
const layer = num(b3.atk_each_stack, 0), maxLayers = Math.max(1, Math.floor(num(b3.max_atk_stack_cnt, 8)));
|
||||
return {
|
||||
skills: {
|
||||
[S1]: { kind: 'duration', mods: { atkPct: num(b1.atk) }, attack: { hitsFn: (battle) => (battle.rng.chance(doubleProb) ? 2 : 1) } },
|
||||
[S2]: { kind: 'duration', mods: { defMul: 0, hpPct: num(b2.max_hp) }, attack: { atkScale: num(b2['attack@atk_scale'], 1) } },
|
||||
[S3]: {
|
||||
kind: 'duration',
|
||||
mods: { atkPct: num(b3.atk) },
|
||||
onStart({ battle, unit }) {
|
||||
battle.removeBuff(unit, STACK_KEY);
|
||||
unit.mem.acguadTarget = null;
|
||||
if (unit.mem.acguadUndying) battle.off(unit.mem.acguadUndying);
|
||||
// "技能持续期间内干员的生命值始终不会低于1": every HP loss, 流失 included (a kit saver, as 幽灵鲨 S2)
|
||||
unit.mem.acguadUndying = battle.on('fatal', (c) => { if (c.unit === unit && unit.skill?.active) c.prevented = true; }, { owner: unit, priority: 10 });
|
||||
},
|
||||
onEnd({ battle, unit }) {
|
||||
if (unit.mem.acguadUndying) battle.off(unit.mem.acguadUndying);
|
||||
unit.mem.acguadUndying = null;
|
||||
battle.removeBuff(unit, STACK_KEY);
|
||||
unit.mem.acguadTarget = null;
|
||||
},
|
||||
},
|
||||
},
|
||||
talents: [
|
||||
{ install(battle, unit) { statBuff(battle, unit, 'acguad:t1', { atkPct: num(t0.atk), dodgePhys: num(t0.prob) }); } },
|
||||
{ install(battle, unit) {
|
||||
const after = num(t1.interval, 30);
|
||||
toggleBuff(battle, unit, 'acguad:t2', () => battle.time - unit.deployedAt >= after - 1e-9, { aspd: num(t1.attack_speed) });
|
||||
} },
|
||||
],
|
||||
trait: blockedScale > 0 ? { dmgMul: (battle, unit, target) => (target && target.blockedBy ? blockedScale : 1) } : undefined,
|
||||
install(battle, unit) {
|
||||
if (unit.skill?.id !== S3 || !(layer > 0)) return;
|
||||
// 力战不竭's layers: decided before every attack the skill makes (after the other beforeAttack handlers, on the
|
||||
// attack's final main target); a new target clears them first, so its first attack has one layer
|
||||
battle.on('beforeAttack', (c) => {
|
||||
if (c.attacker !== unit || !unit.skill.active) return;
|
||||
const target = (c.targets || []).find((t) => t && t.alive);
|
||||
if (!target) return;
|
||||
if (unit.mem.acguadTarget !== target) {
|
||||
battle.removeBuff(unit, STACK_KEY);
|
||||
unit.mem.acguadTarget = target;
|
||||
}
|
||||
battle.addBuff(unit, { key: STACK_KEY, mods: { atkPct: layer }, refresh: 'stack', maxStacks: maxLayers, tags: ['skill'] });
|
||||
}, { owner: unit, priority: -100 });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
// server/sim/content/kits/ops/standin-acspec.js — Misery (char_615_acspec, 卫戍协议 6★ 特种·处决者) 补位 stand-in kit.
|
||||
// Stands in for 缄默德克萨斯 (tier 4, S2), 山 (tier 5, S3 + EXE-X) and 新约能天使 / 锏 (tier 6, S3 + EXE-X):
|
||||
// data/backups.json `units.char_615_acspec.standsIn`. Kit contract: ../README.md "Stand-in kits".
|
||||
// Numbers: the blackboards of data/backups.json (skill_table / character_table / battle_equip_table); what the data
|
||||
// does not carry: PRTS Misery(卫戍协议), cited where used.
|
||||
|
||||
import { num, talentBb, traitBb, skillRec, statBuff, toggleBuff } from '../shared/tier1.js';
|
||||
import { lonely } from '../shared/tier4.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
|
||||
const S1 = 'skchr_acspec_1';
|
||||
const S2 = 'skchr_acspec_2';
|
||||
const S3 = 'skchr_acspec_3';
|
||||
const S1_KEY = 'acspec:s1';
|
||||
const PULL_TAG = 'acspec:pull';
|
||||
/**
|
||||
* 四维分离's "周围四格": her tile and the four tiles beside it [ASSUMED: her own tile counts — an enemy she blocks stands
|
||||
* on it, and the talent is about her being alone with one enemy].
|
||||
*/
|
||||
const AROUND4 = Object.freeze([[0, 0], [-1, 0], [1, 0], [0, -1], [0, 1]]);
|
||||
/** 空间的归依's area when the record has none: range x-4, her tile and the eight around it. */
|
||||
const RING_X4 = Object.freeze([[1, -1], [1, 0], [1, 1], [0, -1], [0, 0], [0, 1], [-1, -1], [-1, 0], [-1, 1]]);
|
||||
|
||||
/** Living enemies whose body is on her tile or the four beside it, targetable or not (flyers too). */
|
||||
function enemiesAround4(battle, unit) {
|
||||
const keys = new Set(absoluteRangeKeys(AROUND4, unit.tileR, unit.tileC, unit.dir, 0));
|
||||
let n = 0;
|
||||
for (const e of battle.enemies) if (e.alive && e.deployed && !e.hidden && bodyInKeys(e, keys)) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空间的归依 at a deployment: atk_scale × ATK physical damage to every ground enemy around her (技能范围 x-4), then each
|
||||
* enemy of that area nobody blocks is dragged to her front with 力度 `force` (0 = 小力: Battle.pullToFront, 力度 − 重量)
|
||||
* and slowed (停顿) for `sluggish` s. PRTS 备注: "※不可对空" and "施加拖动与停顿效果的实际方式为:对目标进行一次0%攻击力
|
||||
* 的近战攻击,此次附带了拖拽与停顿效果" — so the drag and the 停顿 ride a 0-damage physical hit, and an enemy that dodges it
|
||||
* (or is out of reach of a hit: invulnerable, asleep) keeps its place. [ASSUMED] that hit is not a normal attack (no
|
||||
* isAttack procs); the drag area is the damage area (the text names no other).
|
||||
*/
|
||||
function spaceBurst(battle, unit, rec) {
|
||||
const sb = rec?.bb ?? {};
|
||||
const grid = Array.isArray(rec?.rangeGrid) && rec.rangeGrid.length ? rec.rangeGrid : RING_X4;
|
||||
const keys = absoluteRangeKeys(grid, unit.tileR, unit.tileC, unit.dir, 0);
|
||||
const around = battle.enemiesInKeys(keys, unit, { canHitFly: false, groundOnly: true });
|
||||
for (const e of around) battle.dealDamage(unit, e, { amount: unit.s.atk * num(sb.atk_scale, 0), type: 'phys', isSkill: true, tags: ['skill', 'acspec:burst'] });
|
||||
const landed = new Set();
|
||||
const h = battle.on('damaged', (c) => { if (c.source === unit && c.dmg?.tags?.includes(PULL_TAG)) landed.add(c.target); }, { owner: unit, priority: 1000 });
|
||||
try {
|
||||
for (const e of around) if (e.alive && !e.blockedBy) battle.dealDamage(unit, e, { amount: 0, type: 'phys', isSkill: true, tags: ['skill', PULL_TAG] });
|
||||
} finally {
|
||||
battle.off(h);
|
||||
}
|
||||
for (const e of around) {
|
||||
if (!landed.has(e) || !e.alive) continue;
|
||||
battle.pullToFront(e, unit, num(sb.force, 0));
|
||||
if (e.alive) battle.applyStatus(e, 'sluggish', { duration: num(sb.sluggish, 2.5), source: unit });
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
// Misery — trait 再部署时间大幅度减少: the data's respawnTime (18 s). Talent 1 二象命末: each attack is a double hit with
|
||||
// attack@prob (PRTS 备注: it is her normal attack's basic logic; EXE-X levels 2 / 3 make it +atk ATK and a higher
|
||||
// chance). Talent 2 四维分离: ATK +atk while exactly `cnt` enemy stands on her tile or the four beside it.
|
||||
// All three skills are 被动, ON_DEPLOY — they start at every deployment (the battle-start one included, when no enemy
|
||||
// is on the field yet: S3 then hits nothing, as in the official mode; it matters on a redeploy):
|
||||
// S1 物理的服从: for the skill's duration (10 s) the talent-1 chance is attack@prob and physical dodge prob.
|
||||
// S2 战争的恭顺: for the skill's duration (10 s) ATK +atk, ASPD +attack_speed.
|
||||
// S3 空间的归依: spaceBurst above (ground only), right after each deployment (install).
|
||||
// Module EXE-X (trait atk): ATK +atk while no ally stands on the four tiles beside her (PRTS: unlike the usual EXE-X;
|
||||
// shared/tier4.js lonely, as 缄默德克萨斯's module of the same text).
|
||||
// [ASSUMED] PRTS's 存疑 note on talent 1 — a 1 s trigger cooldown that would make her wait 1 s after each attack — is not
|
||||
// modelled: her attack interval is the data's (0.93 s base).
|
||||
// Ground only, ground-targetable (data canHitFly false, no flag).
|
||||
char_615_acspec: (bb, chess) => {
|
||||
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
|
||||
const moduleAtk = num(traitBb(chess).atk, 0);
|
||||
const r1 = skillRec(chess, S1), r2 = skillRec(chess, S2), r3 = skillRec(chess, S3);
|
||||
const b1 = r1?.bb ?? {}, b2 = r2?.bb ?? {};
|
||||
const talentProb = num(t0['attack@prob'], 0), s1Prob = num(b1['attack@prob'], talentProb);
|
||||
const cnt = Math.max(1, Math.floor(num(t1.cnt, 1)));
|
||||
return {
|
||||
skills: {
|
||||
[S1]: { kind: 'passive', onStart({ battle, unit }) { battle.addBuff(unit, { key: S1_KEY, duration: num(r1?.duration, 10), mods: { dodgePhys: num(b1.prob) }, tags: ['skill'] }); } },
|
||||
[S2]: { kind: 'passive', onStart({ battle, unit }) { battle.addBuff(unit, { key: 'acspec:s2', duration: num(r2?.duration, 10), mods: { atkPct: num(b2.atk), aspd: num(b2.attack_speed) }, tags: ['skill'] }); } },
|
||||
[S3]: { kind: 'passive' },
|
||||
},
|
||||
trait: { hitsFn: (battle, unit) => (battle.rng.chance(unit.findBuff(S1_KEY) ? s1Prob : talentProb) ? 2 : 1) },
|
||||
talents: [
|
||||
{ install(battle, unit) { statBuff(battle, unit, 'acspec:t1', { atkPct: num(t0.atk) }); } },
|
||||
{ install(battle, unit) { toggleBuff(battle, unit, 'acspec:t2', () => enemiesAround4(battle, unit) === cnt, { atkPct: num(t1.atk) }); } },
|
||||
],
|
||||
install(battle, unit) {
|
||||
if (moduleAtk > 0) toggleBuff(battle, unit, 'acspec:module', () => lonely(battle, unit), { atkPct: moduleAtk });
|
||||
// 空间的归依 "部署后立即": at the `deploy` of every deployment, after the deploy-time buffs (module / talent-2
|
||||
// toggles, priority 0) are on — a passive's onStart runs earlier, inside skill.reset; a 【移动】 (move: true)
|
||||
// restarts no passive, as everywhere in the engine
|
||||
if (unit.skill?.id === S3) {
|
||||
battle.on('deploy', (c) => { if (c.unit === unit && !c.move && unit.alive && unit.deployed) spaceBurst(battle, unit, r3); }, { owner: unit, priority: -10 });
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
// server/sim/content/kits/ops/standin-sharp2.js — 领主·Sharp (char_617_sharp2, 卫戍协议 6★ 近卫·领主) 补位 stand-in kit.
|
||||
// Stands in for 水月 (tier 4), 玛恩纳 (tier 5, LOR-X) and 维娜·维多利亚 / 妮芙 (tier 6, LOR-X), always with S1 (her only
|
||||
// skill): data/backups.json `units.char_617_sharp2.standsIn`. Kit contract: ../README.md "Stand-in kits".
|
||||
// Numbers: the blackboards of data/backups.json (skill_table / character_table / battle_equip_table); what the data
|
||||
// does not carry: PRTS 领主·Sharp, cited where used.
|
||||
|
||||
import { num, talentBb, moduleBb, skillRec, statBuff, toggleBuff } from '../shared/tier1.js';
|
||||
import { hasHp, isHpLoss } from '../../../damage.js';
|
||||
|
||||
const S1 = 'skchr_sharp2_1';
|
||||
|
||||
export default {
|
||||
// 领主·Sharp — trait (the lord profile, professions.js): ranged attacks at atk_scale (80 %) unless the target stands on
|
||||
// her tile / the tile in front or she blocks it; range 3-12; hits air units (data canHitFly). Talent 1 无声之锋: ATK
|
||||
// +atk, arts dodge prob (LOR-X levels 2 / 3 upgrade both). Talent 2 陷阵勇气: ASPD +attack_speed while at least `cnt`
|
||||
// enemies she can target stand on her attack range [ASSUMED: targetable ones — a 隐匿 enemy she cannot select does not
|
||||
// count].
|
||||
// S1 沉默的爆发: ATK +atk, ASPD +attack_speed, attack@max_target targets, "远程攻击不再降低攻击力" (every attack at full
|
||||
// ATK; the shots stay ranged — unlike 银灰 S3, which "视为近距离攻击"). Range unchanged, data trigger DEFAULT.
|
||||
// Module LOR-X: a hidden talent `magic_atk_scale` (the trait part is display only). PRTS: "攻击附带10%攻击力的法术伤害
|
||||
// ※于输出伤害时触发,造成的伤害为法术生命流失" — the popup: it triggers almost no event (输出伤害时 / 受到伤害时) and
|
||||
// ignores 无敌, 屏障, 护盾, 减伤. So every damage instance she outputs makes its target lose magic_atk_scale × her ATK
|
||||
// (Battle.loseHp: no DEF / RES, shield, invulnerability or damage multiplier; its own `damaged` is an hpLoss and sets
|
||||
// off nothing). Unlike the LOR-X of 银灰 / 拉普兰德 / 断崖 / 仇白 (trait atk_scale_m, PRTS "于计算伤害时触发,造成预计算
|
||||
// 的法术附加伤害": arts damage). [ASSUMED] the client shows the loss in the 流失 (true-damage) colour, it follows a
|
||||
// fully shielded hit too (damage was output), and it stops when she leaves the field (a shot landing after that).
|
||||
char_617_sharp2: (bb, chess) => {
|
||||
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
|
||||
const magic = num(moduleBb(chess).magic_atk_scale, 0);
|
||||
const b1 = skillRec(chess, S1)?.bb ?? {};
|
||||
return {
|
||||
skills: {
|
||||
[S1]: {
|
||||
kind: 'duration',
|
||||
mods: { atkPct: num(b1.atk), aspd: num(b1.attack_speed) },
|
||||
targeting: { maxTargets: Math.max(1, Math.floor(num(b1['attack@max_target'], 2))) },
|
||||
attack: { dmgMul: () => 1 },
|
||||
},
|
||||
},
|
||||
talents: [
|
||||
{ install(battle, unit) { statBuff(battle, unit, 'sharp2:t1', { atkPct: num(t0.atk), dodgeArts: num(t0.prob) }); } },
|
||||
{ install(battle, unit) {
|
||||
const cnt = Math.max(1, Math.floor(num(t1.cnt, 2)));
|
||||
toggleBuff(battle, unit, 'sharp2:t2', () => battle.enemiesInKeys(unit.rangeKeys, unit, unit.profile).length >= cnt, { aspd: num(t1.attack_speed) });
|
||||
} },
|
||||
],
|
||||
install(battle, unit) {
|
||||
if (!(magic > 0)) return;
|
||||
battle.on('damaged', (c) => {
|
||||
const e = c.target;
|
||||
if (c.source !== unit || !unit.alive || !unit.deployed || !e || e.side !== 'enemy' || isHpLoss(c.dmg) || !hasHp(e)) return;
|
||||
battle.loseHp(e, unit.s.atk * magic, { source: unit, tags: ['module', 'sharp2:lorx'] });
|
||||
}, { owner: unit });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user