feedback3 WB: 琳琅诗怀雅 S1 heals an injured ally beside her without needing an attack or an enemy, at most once per attack cycle (owner's decision 2026-10-04, community report #5)

This commit is contained in:
sganggs committed 2026-10-04 18:18:18 +08:00
1 parent d1a2696e44
commit 7d3d7318d5
2 files changed
+68 -6

No files matched your search

+24 -6
View File
@@ -399,8 +399,9 @@ const KITS = {
// ---- 3_04 琳琅诗怀雅 · 行商 — S2 “见面礼” (passive): each attack spends a coin to drop a champagne bomb in range;
// 大买家: coin at skill start + coin & ATK stack per trait payment; 破财消灾: DP-paid revive (cost doubles)
// S1 仗义疏财 (passive, 2 coins): an attack spends a coin to heal the most injured ally (< 70 % HP) of the 8
// surrounding tiles for attack@heal_scale × ATK. S3 千金一掷 (持续时间无限): attacks hit twice, kills give a coin;
// S1 仗义疏财 (passive, 2 coins): a coin heals the most injured ally (< 70 % HP) of the 8 surrounding tiles for
// attack@heal_scale × ATK — on her attack, or with no enemy to attack on her own attack timer (owner's decision
// 2026-10-04, against the official 「下一次攻击会为…」; installS1). S3 千金一掷 (持续时间无限): attacks hit twice, kills give a coin;
// closing it spends every coin on random ground enemies of range 2-4 in front and those she blocks (atk_scale phys +
// a small push, radial despite the text's 向前 — PRTS 备注 "推开效果为径向推动"; client charpack char_1033_swire2:
// the RandomGold ability (Skill_3_End) carries swire2_s_3[knockback] of template knockback[relative]; 地面敌方单位,
@@ -448,10 +449,19 @@ const KITS = {
},
});
const healRatio = textNum(d.skill?.description, /血量不足(\d+)%/, 70) / 100;
const installS1 = (battle, unit) => { // 仗义疏财
/**
* 仗义疏财 — official text 「消耗一枚金币,下一次攻击会为周围八格内血量不足70%的一名友方单位恢复相当于攻击力40%的生命」: the heal rides on an
* attack, so with no enemy around she never healed (community report #5 「琳琅诗怀雅1技能不会主动奶身边受伤的干员」). Owner's
* decision 2026-10-04 (a deliberate deviation, like §21.29's 重装 casts): she heals an injured ally beside her whether
* she attacks or not. Same target (the lowest HP ratio below 70 % of the 8 surrounding tiles, no 禁疗 / 孤立 unit, no
* device), coin and heal_scale × ATK. Cadence [ASSUMED]: at most one heal per attack cycle (her attack interval, ASPD
* included) — while she attacks, on the attack as before; when her last attack attempt found no target, on her own
* timer (the 'tick' hook runs after the attacks, so an attack due in the same tick takes it).
*/
const installS1 = (battle, unit) => {
const hs = num(bb['attack@heal_scale'], num(bb.heal_scale, 0));
battle.on('attack', (ctx) => {
if (ctx.attacker !== unit || !alive(unit) || (unit.mem.coins ?? 0) < coinCost || !(hs > 0)) return;
let nextAt = -Infinity;
const healTarget = () => {
let best = null;
for (const a of battle.allyUnits) {
if (a === unit || !alive(a) || a.hidden || a.kind === 'device' || a.hpRatio >= healRatio) continue;
@@ -459,11 +469,19 @@ const KITS = {
if (Math.max(Math.abs(a.tileR - unit.tileR), Math.abs(a.tileC - unit.tileC)) !== 1) continue; // 周围八格
if (!best || a.hpRatio < best.hpRatio || (a.hpRatio === best.hpRatio && a.deploySeq < best.deploySeq)) best = a;
}
return best;
};
const tryHeal = () => {
if (!alive(unit) || (unit.mem.coins ?? 0) < coinCost || !(hs > 0) || battle.time < nextAt - 1e-9) return;
const best = healTarget();
if (!best) return;
unit.mem.coins -= coinCost;
nextAt = battle.time + unit.s.interval;
battle.heal(unit, best, unit.s.atk * hs);
fx(battle, 'coin', unit, { n: unit.mem.coins, heal: best.id, skill: 'swire2_1' });
}, { owner: unit, priority: -10 });
};
battle.on('attack', (ctx) => { if (ctx.attacker === unit) tryHeal(); }, { owner: unit, priority: -10 });
battle.on('tick', () => { if (unit.deployed && unit.canAct && !unit.trait?.hadTarget) tryHeal(); }, { owner: unit });
};
const installS3 = (battle, unit) => { // 千金一掷: "击倒敌人时获得一枚金币"
battle.on('kill', (ctx) => {
+44
View File
@@ -226,6 +226,50 @@ test('3_04 琳琅诗怀雅 S1 仗义疏财: 2 coins; an attack spends one to hea
}
});
// Owner's decision 2026-10-04 (community report #5 「琳琅诗怀雅1技能不会主动奶身边受伤的干员」), a deliberate deviation from the official
// 「下一次攻击会为…」: S1 heals an injured ally beside her without an attack or an enemy — at most once per attack cycle
// (her attack interval, ASPD included) [ASSUMED cadence]; with an enemy the heal stays on her attack.
test('3_04 琳琅诗怀雅 S1 仗义疏财 (owner\'s decision): an injured ally beside her is healed with no enemy on the field, once per attack cycle; with an enemy, once per attack', () => {
const id = 'chess_char_3_04_a';
const b = SB(id, 'skchr_swire2_1');
const mk = (enemies = []) => makeBattle({ defs: { enemies: { enemy_d: dummy('enemy_d') } }, timeLimit: 60, autoFinish: false, hooks: ['heal', 'attack'], captureNoisy: true,
units: [U(id, 'skchr_swire2_1', 9, 5), { chessId: 'chess_char_3_16_a', row: 10, col: 5 }], enemies });
// no enemy: an ally at 80 % is left alone, one at 50 % is healed and a coin is spent
let h = mk();
let u = h.unit(id), ally = h.unit('chess_char_3_16_a');
h.step();
u.mem.coins = 2;
ally.hp = ally.s.maxHp * 0.8;
h.run(3);
assert.equal(h.hooksOf('heal').filter((c) => c.source === u).length, 0, '80 %: not below 70 %');
assert.equal(u.mem.coins, 2, 'the coin is kept');
ally.hp = ally.s.maxHp * 0.5;
h.step();
const heals = h.hooksOf('heal').filter((c) => c.source === u);
assert.equal(heals.length, 1, 'healed at once, no enemy needed');
assert.equal(heals[0].target, ally);
approx(heals[0].amount, u.s.atk * b['attack@heal_scale']);
assert.equal(u.mem.coins, 1, 'one coin spent');
assert.equal(h.hooksOf('attack').filter((c) => c.attacker === u).length, 0, 'without an attack');
// kept injured with coins to spare: one heal per attack interval, never more
const n0 = h.hooksOf('heal').length, t0 = h.b.time;
for (let i = 0; i < 300; i++) { ally.hp = ally.s.maxHp * 0.3; u.mem.coins = 2; h.step(); }
const per = h.hooksOf('heal').slice(n0).filter((c) => c.source === u).length;
assert.equal(per, Math.floor((h.b.time - t0) / u.s.interval + 1e-6), `one heal per ${u.s.interval} s`);
done(h);
// with an enemy in her range: the heal rides on each attack, never a second one in the cycle
h = mk([{ key: 'enemy_d', pos: [9, 6] }]);
u = h.unit(id); ally = h.unit('chess_char_3_16_a');
h.step();
const a0 = h.hooksOf('attack').length, h0 = h.hooksOf('heal').length; // (its first attack, at deployment, found the ally unhurt)
for (let i = 0; i < 300; i++) { ally.hp = ally.s.maxHp * 0.3; u.mem.coins = 2; h.step(); }
const atk = h.hooksOf('attack').slice(a0).filter((c) => c.attacker === u).length;
const hl = h.hooksOf('heal').slice(h0).filter((c) => c.source === u).length;
assert.ok(atk >= 5, `attacks (${atk})`);
assert.equal(hl, atk, 'one heal per attack');
done(h);
});
test('3_04 琳琅诗怀雅 S3 千金一掷: 二连击, kills pay a coin, a full purse is cashed out on the front range (hits + push)', () => {
for (const id of BOTH('chess_char_3_04_a')) {
const b = SB(id, 'skchr_swire2_3'), t0 = TB(id, 0);