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:
1 parent
d1a2696e44
commit
7d3d7318d5
2 files changed
+68
-6
No files matched your search
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user