feedback5 WL: Touch follows PRTS — S3's extra heal is 30 % of the base with its own recipient's boost, 「低于」 half HP, 攫升 on every heal she outputs, 超脱 on a dying forced exit
The shared code of the 外勤医疗 strategy's Touch and the Touch 补位 stand-in (content/tokens.js touchGospel / mapCharTalents), against PRTS Touch(卫戍协议) and its 备注 / 修正: - S3 恳切福音's extra heal: 30 % of the main heal's base (before the ×1.35 / ×1.40), a heal of its own — its recipient below half HP gets the boost (and the elite's PHY-X), 备注 「额外治疗可触发技能后半段的治疗量提高效果」; it goes to the lowest HP ratio of the main target and its 4 orthogonal neighbours, a full-HP unit included (x-5, 「可治疗生命值已满单位」). The touchExtra / touchMainHeal block is gone. - The boost needs HP strictly below half (修正 「低于」, was 「不高于」). - 攫升: every heal she outputs gives SP — a heal on herself and one that restores nothing too (备注 「无需实际产生治疗量」); still no device, no unit without a skill, and not her own HP-regeneration tick. - 超脱: a forced exit that plays the death animation counts like a knock-out (备注 「如史尔特尔的天赋效果」). Battle.retreat takes `dying` and the death hook carries it: 史尔特尔's 余烬 and 骑士戒律 + 竞技旗 set it; planned exits (耀骑士临光 S2, 伊内丝 S3), the 商人's automatic 撤退 and the 突袭 jump do not. A battle has no player 撤退 command. Golden moved: roster-041 (the strategy's Touch heals differently: S3 extra heal / boost), standin-03, standin-04, standin-08, standin-09, standin-10 (Touch standing in for 塞雷娅 / 塑心 / 纯烬艾雅法拉), coop2-NORMAL-14-standins (the human seat plays 外勤医疗, so its Touch takes part). Docs: SIM.md (death hook `dying`, retreat option, the forced-exit paragraph).
This commit is contained in:
1 parent
ce0d85c78a
commit
8b7316d93e
12 files changed
+244
-127
No files matched your search
@@ -86,13 +86,15 @@ export class BattleDeploy {
|
||||
/**
|
||||
* Withdraw an ally without a kill (it may redeploy after its respawn time): an operator lies down where it stood and
|
||||
* comes back there (isDown, GitHub #60) — unless `permanent`, or the 突袭 retreat ('raid') that redeploys it at once.
|
||||
* `dying`: a forced exit that plays the operator's death animation — a knock-out put off by its own effect (史尔特尔's
|
||||
* 余烬, 骑士戒律 + 竞技旗); the `death` hook carries it (Touch's 超脱 counts such an exit: content/tokens.js).
|
||||
*/
|
||||
retreat(unit, { reason = 'retreat', permanent = false } = {}) {
|
||||
retreat(unit, { reason = 'retreat', permanent = false, dying = false } = {}) {
|
||||
if (!unit || !unit.alive || unit.side !== 'ally') return;
|
||||
this._remove(unit, reason, null, permanent);
|
||||
this._remove(unit, reason, null, permanent, dying);
|
||||
}
|
||||
|
||||
_remove(unit, reason, killer = null, permanent = false) {
|
||||
_remove(unit, reason, killer = null, permanent = false, dying = false) {
|
||||
unit.alive = false;
|
||||
unit.removeReason = reason;
|
||||
unit.deployed = false;
|
||||
@@ -145,7 +147,7 @@ export class BattleDeploy {
|
||||
}
|
||||
// the reason ('killed' | 'retreat' | 'expired' | …) lets the client keep the knock-down sound for real knock-outs
|
||||
if (reason !== 'leak') this._ev(['die', unit.id, reason]);
|
||||
if (this._hooks.death) this.emit('death', { unit, reason, killer });
|
||||
if (this._hooks.death) this.emit('death', { unit, reason, killer, dying: !!dying });
|
||||
if (unit.removed) this._toRelease.push(unit);
|
||||
}
|
||||
|
||||
|
||||
@@ -185,9 +185,10 @@ export class BattleTiles {
|
||||
* decision of 2026-10-04): knocked out ('killed'), entering the battle knocked out (FORCED_EXIT, 联防) and forced out
|
||||
* by its own effects ('retreat': 史尔特尔's 余烬, 耀骑士临光 S2, 骑士戒律 + 竞技旗, 伊内丝 S3; 'merchant': a 商人 that cannot
|
||||
* pay) — except the 突袭 retreat ('raid', redeployed at once on its landing tile); not removed for good, after it was
|
||||
* deployed. A forced exit stays no kill: its 'die' / `death` reason is not 'killed' (no 被击倒 effect, 不屈, 阿戈尔, no
|
||||
* knock-down count). The client keeps its model on that tile knocked down with a redeploy countdown (b.snap `down`,
|
||||
* render/units.js); summons, devices and enemies simply leave.
|
||||
* deployed. A forced exit stays no kill: its 'die' / `death` reason is not 'killed' (no 被击倒 effect, 阿戈尔, no
|
||||
* knock-down count — but 不屈 rolls on it, and Touch's 超脱 counts a `dying` one: 史尔特尔's 余烬, 骑士戒律 + 竞技旗). The
|
||||
* client keeps its model on that tile knocked down with a redeploy countdown (b.snap `down`, render/units.js); summons,
|
||||
* devices and enemies simply leave.
|
||||
*/
|
||||
isDown(u) {
|
||||
return !!u && u.side === 'ally' && u.kind === 'op' && !u.alive && !u.removed && u.removeReason !== 'raid'
|
||||
|
||||
@@ -739,6 +739,7 @@ const BY_ITEM = {
|
||||
},
|
||||
// 骑士戒律 (卡西米尔): skill start ⇒ 20 s aura: enemies in range ASPD ×attack_speed, move ×move_speed;
|
||||
// + 卡西米尔竞技旗: during the (timed) skill ATK +atk, lethal damage does not retreat it — it retreats when the skill ends
|
||||
// ("受到致命伤害时不撤退,技能结束后退场": a knock-out put off, so a `dying` retreat — Touch's 超脱 counts it [ASSUMED])
|
||||
chess_item_6_10_e(battle, u, rec, S) {
|
||||
const p = bp(rec, 'act2autochess_equip_acarm119_global_buff');
|
||||
const a = bp(rec, 'act2autochess_equip_acarm119_ability');
|
||||
@@ -763,7 +764,7 @@ const BY_ITEM = {
|
||||
battle.removeBuff(u, comboKey);
|
||||
if (doomed && c.reason !== 'death') {
|
||||
doomed = false;
|
||||
battle.after(0, () => { if (u.alive) battle.retreat(u, { reason: 'retreat' }); });
|
||||
battle.after(0, () => { if (u.alive) battle.retreat(u, { reason: 'retreat', dying: true }); });
|
||||
}
|
||||
});
|
||||
S.on('fatal', (c) => {
|
||||
|
||||
@@ -66,7 +66,8 @@ export default {
|
||||
// noHeal (no heal pick, no heal from others) + healFree (her own heals too; S3's start heal "无视禁疗"), shown as
|
||||
// the status 'healFree' until she leaves. "强制退出战场视为撤回干员": a retreat (Battle.retreat drops the buff) — she
|
||||
// lies down where she stood and redeploys there, like every operator that leaves the field (PRTS 卫戍协议/帮助
|
||||
// "干员退场后…原地留下一个“倒地干员”…自动部署至该位置"; Battle.isDown, GitHub #60).
|
||||
// "干员退场后…原地留下一个“倒地干员”…自动部署至该位置"; Battle.isDown, GitHub #60). With her death animation
|
||||
// (`dying`): PRTS Touch(卫戍协议) 超脱 备注 counts it as a knock-out ("如史尔特尔的天赋效果").
|
||||
const wait = num(t1['surtr_t_2[withdraw].interval'], 8);
|
||||
battle.on('deploy', (c) => { if (c.unit === unit) unit.mem.ember = false; }, { owner: unit });
|
||||
battle.on('fatal', (c) => {
|
||||
@@ -77,7 +78,7 @@ export default {
|
||||
const dep = unit.deploySeq;
|
||||
battle.addBuff(unit, { key: 'surtr:ember', status: 'healFree', flags: { noHeal: true, healFree: true } });
|
||||
battle.fx('ember', { x: unit.x, y: unit.y, id: unit.id });
|
||||
battle.after(wait, () => { if (unit.alive && unit.deploySeq === dep) battle.retreat(unit, { reason: 'retreat' }); }, { owner: unit });
|
||||
battle.after(wait, () => { if (unit.alive && unit.deploySeq === dep) battle.retreat(unit, { reason: 'retreat', dying: true }); }, { owner: unit });
|
||||
}, { owner: unit, priority: -60 });
|
||||
} },
|
||||
],
|
||||
|
||||
@@ -19,16 +19,16 @@ export default {
|
||||
// `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).
|
||||
// (5-2 grid, ATK +atk, 2 targets, ×heal_scale on allies below hp_ratio HP, and an extra heal of 30 % of the main
|
||||
// heal's base on the main target or an orthogonal neighbour — the lowest HP ratio, a full-HP one included — which
|
||||
// takes the ×heal_scale of its own recipient: PRTS 技能3 备注).
|
||||
// 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].
|
||||
// 倍率间叠乘" — no Touch-specific note or buff template exists. S3's extra heal is a heal of hers like any other: its
|
||||
// recipient below `hp_ratio` gets the ×1.15 too (its base is taken before either bonus).
|
||||
// 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) => {
|
||||
@@ -79,8 +79,7 @@ export default {
|
||||
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.source !== unit || !ctx.target || ctx.opts?.regen) return;
|
||||
if (ctx.target.hpRatio < phyRatio - 1e-9) ctx.amount *= phyScale;
|
||||
}, { owner: unit, priority: 10 });
|
||||
}
|
||||
|
||||
@@ -1338,12 +1338,26 @@ export function spawnYanyou(battle, playerId, { atk, hp, atkMul = 1, dmgTakenMul
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// band map characters (预备干员-医疗 / Touch)
|
||||
|
||||
/**
|
||||
* Exits that give Touch's 超脱 its SP (PRTS Touch(卫戍协议) 第二天赋 备注 "部分有死亡动画的强制撤退(如史尔特尔的天赋效果)也能触发这一
|
||||
* 天赋"): a knock-out ('killed') and a forced exit that plays the death animation — Battle.retreat `dying`, a knock-out put
|
||||
* off by the operator's own effect: 史尔特尔's 余烬 (the PRTS example) and 骑士戒律 + 竞技旗 ("受到致命伤害时不撤退,技能结束后
|
||||
* 退场" [ASSUMED: the same kind of exit]). Not the exits a skill plans — 耀骑士临光 S2's "技能结束后自动撤退", 伊内丝 S3's "放置
|
||||
* 一个影哨后离场" — nor the 商人's "不足时自动撤退" ('merchant'), the 突袭 jump ('raid'), the 联防 setup (FORCED_EXIT) or a
|
||||
* summon's end ('expired') [ASSUMED: plain 撤退 without a death animation]. A battle runs on its own — the player has no
|
||||
* 撤退 command in it — so none of these is a player's own retreat.
|
||||
*/
|
||||
const touchExitCounts = (ctx) => ctx.reason === 'killed' || (ctx.reason === 'retreat' && !!ctx.dying);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 攫升 "治疗目标时使其获得3点技力" (Touch) → every heal the character outputs gives its target `sp` SP — PRTS 备注 "本天赋只需
|
||||
* Touch输出治疗便能触发(无需实际产生治疗量)": herself too, and a heal that restores nothing (a full-HP target: S3's
|
||||
* extra heal); not a device or a unit without a skill, not her own HP-regeneration tick (no heal she outputs)
|
||||
* 超脱 "攻击范围内的友方干员被击倒时获得5点技力" (Touch) → an allied operator knocked out (or forced out dying:
|
||||
* touchExitCounts) on a tile of its range: +`sp` SP
|
||||
*/
|
||||
export function mapCharTalents(def) {
|
||||
const out = [];
|
||||
@@ -1355,7 +1369,7 @@ export function mapCharTalents(def) {
|
||||
out.push({ install(battle, unit) {
|
||||
battle.on('heal', (ctx) => {
|
||||
const tg = ctx.target;
|
||||
if (ctx.source !== unit || !tg || tg === unit || tg.kind === 'device' || !tg.skill || !(ctx.amount > 0)) return;
|
||||
if (ctx.source !== unit || ctx.opts?.regen || !tg || tg.kind === 'device' || !tg.skill || tg.skill.noSkill) return;
|
||||
tg.skill.gainSp(sp, 'talent');
|
||||
}, { owner: unit, priority: -200 });
|
||||
} });
|
||||
@@ -1363,7 +1377,7 @@ export function mapCharTalents(def) {
|
||||
out.push({ install(battle, unit) {
|
||||
battle.on('death', (ctx) => {
|
||||
const d = ctx.unit;
|
||||
if (ctx.reason !== 'killed' || !unit.alive || !unit.deployed || !unit.skill || !d || d === unit || d.kind !== 'op' || d.ownerId !== unit.ownerId) return;
|
||||
if (!touchExitCounts(ctx) || !unit.alive || !unit.deployed || !unit.skill || !d || d === unit || d.kind !== 'op' || d.ownerId !== unit.ownerId) return;
|
||||
if ((unit.rangeKeySet || new Set(unit.rangeKeys || [])).has(d.tileR * COLS + d.tileC)) {
|
||||
unit.skill.gainSp(sp, 'talent');
|
||||
battle.fx('spGain', { x: unit.x, y: unit.y, id: unit.id, n: sp });
|
||||
@@ -1391,11 +1405,19 @@ function reserveMedicKit(bb, raw, def) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch 恳切福音 (skchr_acmedc_3): +ATK, 2 heal targets in the skill range, ×heal_scale on allies ≤ hp_ratio, +addition
|
||||
* Touch 恳切福音 (skchr_acmedc_3): +ATK, 2 heal targets in the skill range, ×heal_scale on allies below 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.
|
||||
* on a unit whose skill is this one only. PRTS Touch(卫戍协议) 技能3 and its 备注:
|
||||
* - "对生命值低于一半的友方单位治疗量提高为原来的130%": strictly below hp_ratio (PRTS 修正: 低于, not the old 不高于);
|
||||
* - "并额外治疗一次目标或目标相邻1个友方单位,治疗量为主目标的30%": once per heal action, at the main (first = most
|
||||
* injured) target's hit; 30 % of that heal's base (ATK × its scales, before the ×heal_scale the main target may get);
|
||||
* - 备注 "额外治疗范围 x-5…优先选择生命比例更低的单位治疗(可治疗生命值已满单位)": the main target or one of its 4
|
||||
* orthogonal neighbours, the lowest HP ratio first, a full-HP one included [ASSUMED: on equal ratios the main target,
|
||||
* then the battle's ally order]; never a 禁疗 / 孤立 / 无法被友方治疗 unit (no heal can pick it);
|
||||
* - 备注 "额外治疗可触发技能后半段的治疗量提高效果": the extra heal is a heal of its own, so its recipient gets the ×heal_scale
|
||||
* when it is below hp_ratio (and the stand-in's PHY-X ×1.15), and its target gets 攫升's SP.
|
||||
*/
|
||||
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);
|
||||
@@ -1409,28 +1431,24 @@ export function touchGospel(bb, skill) {
|
||||
targeting,
|
||||
onHit({ battle, unit, target, heal }) {
|
||||
if (!(extra > 0) || !target || !(heal > 0)) return;
|
||||
// "每次治疗2个目标,并额外治疗一次…治疗量为主目标的30%": one extra heal per heal action, for the main
|
||||
// (first = most injured) target — onHit runs once per healed target
|
||||
// onHit runs once per healed target: the extra heal comes with the first (main) one of the action
|
||||
const n = unit.stats.attacks;
|
||||
if (unit.mem.touchExtraAt === n) return;
|
||||
unit.mem.touchExtraAt = n;
|
||||
let best = target.hp < target.s.maxHp - 1e-6 ? target : null;
|
||||
for (const a of battle.alliesInRadius(target.x, target.y, 1, unit.ownerId)) {
|
||||
if (a === target || a.hp >= a.s.maxHp - 1e-6 || a.s.flags.noHeal || a.profile?.noHeal) continue; // (禁疗 / 孤立)
|
||||
let best = target;
|
||||
for (const a of battle.alliesInRadius(target.x, target.y, 1)) {
|
||||
if (a === target || !battle.allySelectable(a, unit) || a.s.flags.noHeal || a.profile?.noHeal) continue;
|
||||
if (Math.abs(a.tileR - target.tileR) + Math.abs(a.tileC - target.tileC) !== 1) continue;
|
||||
if (!best || a.hpRatio < best.hpRatio) best = a;
|
||||
if (a.hpRatio < best.hpRatio - 1e-9) best = a;
|
||||
}
|
||||
// 30 % of the main target's heal as it was applied (×heal_scale when that target was at ≤ hp_ratio)
|
||||
const main = unit.mem.touchMainHeal && unit.mem.touchMainHeal.target === target ? unit.mem.touchMainHeal.amount : heal;
|
||||
unit.mem.touchExtra = true; // the extra heal is a share of the main heal: no second ×heal_scale
|
||||
try { battle.heal(unit, best ?? target, main * extra); } finally { unit.mem.touchExtra = false; }
|
||||
// `heal` = the main heal's base (ai.js doHeal: ATK × scales, before the heal pipeline and the ×heal_scale)
|
||||
battle.heal(unit, best, heal * extra);
|
||||
},
|
||||
},
|
||||
install(battle, unit) {
|
||||
battle.on('heal', (ctx) => {
|
||||
if (ctx.source !== unit || !unit.skill?.active || unit.mem.touchExtra) return;
|
||||
if (boost !== 1 && hpRatio > 0 && ctx.target.hpRatio <= hpRatio + 1e-9) ctx.amount *= boost;
|
||||
unit.mem.touchMainHeal = { target: ctx.target, amount: ctx.amount };
|
||||
if (ctx.source !== unit || ctx.opts?.regen || !unit.skill?.active) return;
|
||||
if (boost !== 1 && hpRatio > 0 && ctx.target.hpRatio < hpRatio - 1e-9) ctx.amount *= boost;
|
||||
}, { owner: unit });
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user