feedback5 K3: the Touch stand-in kit (standin-acmedc.js) — S1 慨赠, S2 宛如天启, module PHY-X; S3 恳切福音 and 攫升 / 超脱 run the 外勤医疗 strategy's Touch code, now exported from content/tokens.js (touchGospel, mapCharTalents; the strategy unchanged, golden identical)
This commit is contained in:
1 parent
65ac4afbd8
commit
55f53fdcb0
5 files changed
+341
-11
No files matched your search
@@ -126,6 +126,9 @@ stand-in's, `def.standInFor` the replaced operator's charId.
|
||||
stand-ins is `tools/build-data.mjs STANDIN_TRIGGER_DEVIATIONS` (预备干员-重装, Mechanist: every skill `DEFAULT`,
|
||||
`rawRule` `TAKE_DAMAGE`).
|
||||
- **Summons**: no stand-in has one (DATA.md §18).
|
||||
- **One character, one implementation**: a stand-in the mode also fields elsewhere reuses that code through a named
|
||||
export, never through another registry: Touch (`standin-acmedc.js`) runs the 外勤医疗 strategy's Touch S3 and talents,
|
||||
`content/tokens.js` `touchGospel` / `mapCharTalents`.
|
||||
- `node --test test/content/kits_layout.test.js` checks the file name, the key (a charId of data/backups.json `units`)
|
||||
and the registry entry.
|
||||
|
||||
|
||||
@@ -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-acmedc.js']);
|
||||
|
||||
async function loadKitFile(file) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// server/sim/content/kits/ops/standin-acmedc.js — Touch (char_613_acmedc) 补位 stand-in kit: 5 塞雷娅, 6 塑心 / 纯烬艾雅法拉
|
||||
// (all S3 恳切福音 + module PHY-X on the elite). Stand-in contract and rules: ../README.md "Stand-in kits".
|
||||
|
||||
import { num, traitBb, skillRec } from '../shared/tier1.js';
|
||||
import { mapCharTalents, touchGospel } from '../../tokens.js';
|
||||
|
||||
const S1 = 'skchr_acmedc_1', S2 = 'skchr_acmedc_2', S3 = 'skchr_acmedc_3';
|
||||
/** S2 宛如天启's "所有医疗干员攻击力+X%" on every medic operator of hers while the skill runs (one per target: 同名 buff). */
|
||||
const APOCALYPSE_KEY = 'acmedc:apocalypse';
|
||||
|
||||
const isMedicOp = (a) => a.kind === 'op' && a.alive && a.deployed && !a.hidden && a.def?.profession === 'MEDIC';
|
||||
|
||||
export default {
|
||||
// Touch (医师) — skills from data/backups.json (normal E2 Lv1 skill rank 4, elite E2 Lv60 rank 7; skill_table and PRTS
|
||||
// Touch(卫戍协议) agree):
|
||||
// S1 慨赠 (20 s): ATK +atk, each heal has an `attack@prob` chance of one extra heal [ASSUMED: of the same target, the
|
||||
// same amount — the text names no other target].
|
||||
// S2 宛如天启 (30 s): her range becomes the 3-10 grid (PRTS 修正: "攻击距离+1" means 攻击范围扩大), she heals
|
||||
// `attack@max_target` targets, every 医疗 operator of hers ATK +`attack@atk` herself included [ASSUMED: her own
|
||||
// player's operators, as 余's "全场所有干员"; summons not].
|
||||
// S3 恳切福音 (40 s): content/tokens.js touchGospel — the one implementation the 外勤医疗 strategy's Touch runs too
|
||||
// (5-2 grid, ATK +atk, 2 targets, an extra heal of 30 % of the main one on the target or an orthogonal neighbour,
|
||||
// ×heal_scale on allies at ≤ hp_ratio HP).
|
||||
// Talents 攫升 / 超脱: content/tokens.js mapCharTalents (the strategy's Touch too); the module's 超脱 upgrade (8 SP at
|
||||
// module Lv3) comes with the record's talents.
|
||||
// Module PHY-X (elite, trait addition): "治疗生命值低于50%的友方单位时治疗量提升15%" — every heal of hers on an ally
|
||||
// below `hp_ratio` HP (strictly: "低于") ×`heal_scale` (the trait blackboard). With S3's ×1.35 / ×1.40 it
|
||||
// multiplies (×1.61 at Lv7): both are `heal_scale` blackboards, the 治疗倍率, and PRTS 游戏数据基础 §倍率 says "同种
|
||||
// 倍率间叠乘" — no Touch-specific note or buff template exists. It runs before S3's hook, so S3's 30 % extra heal is
|
||||
// a share of the main heal with both bonuses and, like S3's ×1.35 / ×1.40 in touchGospel, never multiplies that
|
||||
// share again [ASSUMED].
|
||||
// Triggers (data, nothing set here): S1 DEFAULT (an injured ally in her range); S2 / S3 ACTIVE_RANGE — the owner's
|
||||
// rule of 2026-10-05 read for a heal skill: an injured ally inside the running 3-10 / 5-2 range casts.
|
||||
char_613_acmedc: (bb, chess, def) => {
|
||||
const tb = traitBb(chess);
|
||||
const phyScale = num(tb.heal_scale, 1), phyRatio = num(tb.hp_ratio, 0);
|
||||
const r1 = skillRec(chess, S1), r2 = skillRec(chess, S2), r3 = skillRec(chess, S3);
|
||||
const gospel = r3 ? touchGospel(r3.bb ?? {}, r3) : null;
|
||||
const skills = {};
|
||||
if (r1) {
|
||||
const b1 = r1.bb ?? {};
|
||||
const p = num(b1['attack@prob'], 0);
|
||||
skills[S1] = {
|
||||
kind: 'duration', heal: true,
|
||||
mods: { atkPct: num(b1.atk) },
|
||||
onHit({ battle, unit, target, heal }) {
|
||||
if (!(p > 0) || !target || !(heal > 0) || !battle.rng.chance(p)) return;
|
||||
battle.heal(unit, target, heal);
|
||||
battle.fx('heal', { x: target.x, y: target.y, id: unit.id });
|
||||
},
|
||||
};
|
||||
}
|
||||
if (r2) {
|
||||
const b2 = r2.bb ?? {};
|
||||
const v = num(b2['attack@atk'], 0);
|
||||
const grant = (battle, unit) => {
|
||||
for (const a of battle.alliesFor(unit, unit.ownerId)) {
|
||||
if (!isMedicOp(a)) continue;
|
||||
const cur = a.findBuff(APOCALYPSE_KEY);
|
||||
if (cur && (cur.source === unit || (cur.data?.v ?? 0) >= v)) continue; // another Touch's equal or stronger one
|
||||
battle.addBuff(a, { key: APOCALYPSE_KEY, mods: { atkPct: v }, source: unit, data: { v }, tags: ['skill'] });
|
||||
}
|
||||
};
|
||||
skills[S2] = {
|
||||
kind: 'duration', heal: true,
|
||||
targeting: { rangeGrid: r2.rangeGrid, maxTargets: Math.max(1, Math.floor(num(b2['attack@max_target'], 2))) },
|
||||
onStart({ battle, unit }) { if (v) grant(battle, unit); },
|
||||
// medics deployed (or redeployed) while it runs get it too
|
||||
onTick({ battle, unit }) { if (v) grant(battle, unit); },
|
||||
onEnd({ battle, unit }) {
|
||||
for (const a of battle.allyUnits) if (a.findBuff(APOCALYPSE_KEY)?.source === unit) battle.removeBuff(a, APOCALYPSE_KEY);
|
||||
},
|
||||
};
|
||||
}
|
||||
if (gospel) skills[S3] = gospel.skill;
|
||||
return {
|
||||
skills,
|
||||
talents: mapCharTalents(def),
|
||||
install(battle, unit) {
|
||||
if (phyScale !== 1 && phyRatio > 0) {
|
||||
battle.on('heal', (ctx) => {
|
||||
// (S3's extra heal is a share of the main heal, which already carries it: touchGospel's `touchExtra`)
|
||||
if (ctx.source !== unit || !ctx.target || ctx.opts?.regen || unit.mem.touchExtra) return;
|
||||
if (ctx.target.hpRatio < phyRatio - 1e-9) ctx.amount *= phyScale;
|
||||
}, { owner: unit, priority: 10 });
|
||||
}
|
||||
if (gospel && def?.skill?.id === S3) gospel.install(battle, unit);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -74,7 +74,8 @@
|
||||
// makes the token (Battle.spawnToken also refuses summons the owner's loadout does not produce).
|
||||
//
|
||||
// Exports for other content: spawnYanyou, spawnMapChar, findSummonTile, summonToken, tacticalPoint, wolfShadows,
|
||||
// releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY, TOKEN_IDS.
|
||||
// releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY, TOKEN_IDS; mapCharTalents and touchGospel (Touch's 攫升 /
|
||||
// 超脱 and 恳切福音, shared with the Touch 补位 stand-in kit, kits/ops/standin-acmedc.js).
|
||||
|
||||
import { COLS, ROWS, MOVE_SCALE } from '../constants.js';
|
||||
import { absoluteRangeKeys, sortEnemyTargets, canTargetEnemy } from '../targeting.js';
|
||||
@@ -1228,12 +1229,13 @@ export function spawnYanyou(battle, playerId, { atk, hp, atkMul = 1, dmgTakenMul
|
||||
// band map characters (预备干员-医疗 / Touch)
|
||||
|
||||
/**
|
||||
* Talents of the band map characters (data talents of the character record):
|
||||
* Talents of the band map characters (data talents of the character record) — and of the Touch 补位 stand-in, whose kit
|
||||
* (kits/ops/standin-acmedc.js) uses this one implementation, the module's 超脱 upgrade (8 SP) coming with the record:
|
||||
* plain stat talent "攻击力+4%" (预备干员-医疗 攻击提升) → persistent ATK/DEF/HP/ASPD buff
|
||||
* 攫升 "治疗目标时使其获得3点技力" (Touch) → every heal by the character gives the healed unit `sp` SP
|
||||
* 超脱 "攻击范围内的友方干员被击倒时获得5点技力" (Touch) → an allied operator knocked out on a tile of its range: +`sp` SP
|
||||
*/
|
||||
function mapCharTalents(def) {
|
||||
export function mapCharTalents(def) {
|
||||
const out = [];
|
||||
for (const t of def?.talents ?? []) {
|
||||
const bb = t?.bb ?? {};
|
||||
@@ -1278,18 +1280,21 @@ function reserveMedicKit(bb, raw, def) {
|
||||
return { ...k, talents: [...(k.talents ?? []), ...mapCharTalents(def)] };
|
||||
}
|
||||
|
||||
/** Touch 恳切福音: +ATK, 2 heal targets in the skill range, ×heal_scale on allies ≤ hp_ratio, +addition heal. */
|
||||
function touchKit(bb, raw, def) {
|
||||
const sk = def?.skill;
|
||||
if (!sk) return { skill: null, talents: mapCharTalents(def) };
|
||||
/**
|
||||
* Touch 恳切福音 (skchr_acmedc_3): +ATK, 2 heal targets in the skill range, ×heal_scale on allies ≤ hp_ratio, +addition
|
||||
* heal — one implementation for the 外勤医疗 map character (touchKit) and the Touch 补位 stand-in (kits/ops/
|
||||
* standin-acmedc.js). `skill` = the skill record (`rangeGrid`, `duration`) of blackboard `bb`. Returns `{ skill:
|
||||
* SkillSpec, install(battle, unit) }`: `install` adds the heal boost, which acts while the unit's skill runs — install it
|
||||
* on a unit whose skill is this one only.
|
||||
*/
|
||||
export function touchGospel(bb, skill) {
|
||||
const hpRatio = num(bb.hp_ratio, 0), boost = num(bb.heal_scale, 1), extra = num(bb['attack@addition_heal_scale'], 0);
|
||||
const targeting = {};
|
||||
if (num(bb['attack@max_target'], 0) > 1) targeting.maxTargets = Math.floor(num(bb['attack@max_target'], 1));
|
||||
if (sk.rangeGrid && sk.rangeGrid.length) targeting.rangeGrid = sk.rangeGrid;
|
||||
if (skill.rangeGrid && skill.rangeGrid.length) targeting.rangeGrid = skill.rangeGrid;
|
||||
return {
|
||||
talents: mapCharTalents(def),
|
||||
skill: {
|
||||
kind: sk.duration > 0 ? 'duration' : 'instant', heal: true,
|
||||
kind: skill.duration > 0 ? 'duration' : 'instant', heal: true,
|
||||
mods: num(bb.atk, 0) ? { atkPct: num(bb.atk, 0) } : undefined,
|
||||
targeting,
|
||||
onHit({ battle, unit, target, heal }) {
|
||||
@@ -1321,6 +1326,14 @@ function touchKit(bb, raw, def) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Touch (外勤医疗 map character): 恳切福音 (touchGospel) + 攫升 / 超脱 (mapCharTalents). */
|
||||
function touchKit(bb, raw, def) {
|
||||
const sk = def?.skill;
|
||||
if (!sk) return { skill: null, talents: mapCharTalents(def) };
|
||||
const g = touchGospel(bb, sk);
|
||||
return { talents: mapCharTalents(def), skill: g.skill, install: g.install };
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a band map character (char_605_cmedic 预备干员-医疗 / char_613_acmedc Touch) at its stage position on the
|
||||
* player's half (data/stages.json `mapChars`; `_multi_only` positions only with a partner). Returns the unit or null.
|
||||
|
||||
Reference in new issue
Block a user