From 79bbd515250ea65910eb1bd085863e42a6b2b40c Mon Sep 17 00:00:00 2001 From: sganggs Date: Sun, 4 Oct 2026 02:19:40 +0800 Subject: [PATCH] =?UTF-8?q?feedback2:=20WG=20review=20=E2=80=94=20a=20chai?= =?UTF-8?q?n=20healer's=20bounces=20skip=20=E7=A6=81=E7=96=97=20/=20noHeal?= =?UTF-8?q?=20units=20(=E5=8F=B2=E5=B0=94=E7=89=B9=E5=B0=94=20in=20?= =?UTF-8?q?=E4=BD=99=E7=83=AC);=20PLAYING=20and=20DESIGN=20say=20=E9=9A=90?= =?UTF-8?q?=E5=8C=BF=20returns=203=20s=20after=20a=20block;=20=E5=B1=B1?= =?UTF-8?q?=E6=B5=B7=E4=BC=97=E5=A4=B4=E7=9B=AE's=20mark=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DESIGN.md | 2 +- docs/PLAYING.md | 2 +- server/sim/ai.js | 3 ++- server/sim/content/enemies.js | 2 +- test/sim/feedback2-surtr-stealth.test.js | 26 ++++++++++++++++++++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index b8831c1f..0e128aa9 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1348,7 +1348,7 @@ Still [ASSUMED] (each in its section): - §21.1: an elite merged this round keeps its copies' refresh count; a 拉普兰德 re-bought after a sale fires again; a bench 突变细胞 carrier transforms too; a 6阶 carrier becomes another 6阶; with the hand and temp both full the gained operator and an item without a slot follow the rules of every gain. - §21.2: the weights inside a group; the unseen 10th R3 set and 险境 R6 drafting like R3; R9 groups by the matches they came in; R11 one of the 7 seen lists and the family weights (solo = co-op); the 机密商店 slots / weights and the earlier shops; the 战术决策 weights, independent draws and twin passives stacking; solo shows 3 of 6. Open for the user: each match's 确认本局信息 page (what decides the R9 group), more R11 悬赏决策 / 机密商店 / 战术决策, solo 绝境 / 终极 R3 / R9 / R11. - §21.3: the range is the loadout grid (items and bonds do not widen it); the canoe in the deploy map only. -- §21.4: the translator immobile and damage-cancelled through its change; the 特战术师 form hits flyers (open for the user); the ember's 10 s after the 重生; 隐匿 back at once after a block; the 重生 whitelist; “余音” unmapped. +- §21.4: the translator immobile and damage-cancelled through its change; the 特战术师 form hits flyers (open for the user); the ember's 10 s after the 重生; 隐匿 back 3 s after a block ends (§22.8; it was "at once" until 0.1.1); the 重生 whitelist; “余音” unmapped. - §21.5: the 鸭爵 swap's 0–2 uniform count and pick, the 60–99 % window, ground only, elites eligible, per-half on pair fields, decided at battle start. - §21.6: the bot's tuning constants and `CROWD`. - §21.7: strongest rather than earliest for 奥术, an equal re-hit refreshing it; the 本局禁用 look; 整备 next to an owned white. diff --git a/docs/PLAYING.md b/docs/PLAYING.md index d222aba5..38427031 100644 --- a/docs/PLAYING.md +++ b/docs/PLAYING.md @@ -74,7 +74,7 @@ - **技能自动释放**按官方「技能策略」:一般的手动技能在技能就绪、即将攻击 / 治疗且初始攻击范围内有敌人(治疗技能:有受伤的队友)时释放;**带独立技能范围**的技能(如德克萨斯「剑雨」、凛御银灰「御敌的锋锐」、锏「归于宁静」,不含「攻击范围扩大」类)在**技能范围内出现敌人**时就释放,不必先攻击;**重装干员**的手动技能在受到伤害时释放(例外:深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放,这是本作有意与官方不同的地方;深巡一技能仍要受到伤害);执旗手 / 战术家 / 吟游者技能就绪即释放,解放者 / 阵法术师在初始攻击范围内有敌人时释放。**自动触发**的技能按其本身规则,例如古米「备用军粮」在身边(技能范围内)有受伤的队友时就会发动,下一次攻击改为治疗该队友(不用先接敌;她的二技能「食粮烹制」是手动技能,按重装规则在受到伤害时释放);塞雷娅「急救」在她即将攻击时、身边有生命不高于一半的队友才发动,这次攻击直接改为治疗;新约能天使二技能的护盾按官方给攻击范围内**仇恨最高**的队友(嘲讽等级高的优先,同级时给最后部署的),不一定是站在最前面挨打的那个。手动技能的自动释放有 3 秒冷却:开战部署后先等 3 秒,之后同一干员两次自动释放至少间隔 3 秒(可充能的技能不会同时放完)。 - **推开 / 拉拽**按官方「力度 − 重量」决定距离:例如野鬃二技能(中等力度)把重量 0 / 1 / 2 / 3 的敌人推开约 2.1 / 1.7 / 0.4 / 0.1 格,重量 4 以上推不动——轻型敌人反而推得更远。野鬃二技能沿她的朝向推开:正前方 45° 以内按上面的距离,身后、侧面或贴身的敌人只被轻推一下(约 0.4 格以内);琳琅诗怀雅三技能的金币则从她身边向外推开,落在她前方 2–4 格的范围(官方「范围2-4」)内的地面敌人和她阻挡的敌人身上。攻击速度最低为 20(沼泽、寒霜等减速叠满时也不会更慢)。 - **元素损伤条**在单位自己的血条正下方:左边是元素图标,白条是剩余的元素值,只有真正爆发时才会耗尽;爆发后条变成元素的颜色、图标闪烁,在冷却期间慢慢回满(这段时间不会再积累元素损伤)。 -- **敌人**:远程敌人的射程按官方从干员的碰撞体积(半径 0.25 格)算起,比数据上的攻击范围半径远 0.25 格——例如萨卡兹枯朽战车(射程 2.2)能打到中心 2.45 格以内、站在低地的干员。它的第一下攻击就是「秽蚀轰击」(在目标处留下半径 1.7、持续 10 秒的污染区:低地每秒 50、高台 25 点真实伤害,同名不叠加),被阻挡时普通攻击伤害翻倍。“萨科塔之翼”等血量第一次低于一半时会在原地小范围乱飞 5 秒(官方叫「恐惧」);叙拉古、妮芙等干员造成的恐惧会让敌人朝远离干员的方向逃跑。隐匿的敌人只有被阻挡(或被揭示)时才能被选为攻击目标,溅射、技能的圆形范围伤害也打不到没被挡住的隐匿敌人(和官方一样):深池逐火战士每次被击倒后先重生 1 秒,然后变成继续往前走的隐匿「余烬」(需要 5 次伤害击倒,护卫的「火灰」10 次),一般要用干员挡住它才能选中(挡住时它不再半透明),10 秒内没打掉就满血站起来;敌人重生时身上的减速、恐惧等状态会被清空、技能冷却从头开始,被恐惧的逐火战士变成余烬后也能被挡住;转译基底·α 原始形态受到的伤害全部无效,受到第 4 次物理伤害、第 4 次法术伤害或被阻挡时花 2 秒变成寻仇者、特战术师或幽灵,之后才能被击倒。 +- **敌人**:远程敌人的射程按官方从干员的碰撞体积(半径 0.25 格)算起,比数据上的攻击范围半径远 0.25 格——例如萨卡兹枯朽战车(射程 2.2)能打到中心 2.45 格以内、站在低地的干员。它的第一下攻击就是「秽蚀轰击」(在目标处留下半径 1.7、持续 10 秒的污染区:低地每秒 50、高台 25 点真实伤害,同名不叠加),被阻挡时普通攻击伤害翻倍。“萨科塔之翼”等血量第一次低于一半时会在原地小范围乱飞 5 秒(官方叫「恐惧」);叙拉古、妮芙等干员造成的恐惧会让敌人朝远离干员的方向逃跑。隐匿的敌人只有被阻挡(或被揭示)时才能被选为攻击目标,溅射、技能的圆形范围伤害也打不到没被挡住的隐匿敌人(和官方一样);阻挡解除后 3 秒内它仍然可以被选中(官方页面注明 0 秒或 1 秒的敌人按页面),之后才重新隐匿:深池逐火战士每次被击倒后先重生 1 秒,然后变成继续往前走的隐匿「余烬」(需要 5 次伤害击倒,护卫的「火灰」10 次),一般要用干员挡住它才能选中(挡住时它不再半透明;被阻挡时被击倒的,重生结束后约 2 秒内也能用远程或范围伤害补刀),10 秒内没打掉就满血站起来;敌人重生时身上的减速、恐惧等状态会被清空、技能冷却从头开始,被恐惧的逐火战士变成余烬后也能被挡住;转译基底·α 原始形态受到的伤害全部无效,受到第 4 次物理伤害、第 4 次法术伤害或被阻挡时花 2 秒变成寻仇者、特战术师或幽灵,之后才能被击倒。 - **策略「鸭爵」(“神秘顾客”)**:从第 5 回合起,你和队友的每场作战(**最终攻势和隐秘核心也算**,联防不另外替换)有 0–2 名地面敌人(不含领袖及其部件、悬赏敌人)被替换为特殊版本的鸭爵、高普尼克、流泪小子或圆仔,出现在被替换敌人的时间和路线上(两人同场的领袖战里,各自只替换走向自己保护目标点的敌人);击倒者获得 1 资金(联防中的队友也算),漏进保护目标点照常扣 1 点目标生命值。鸭爵和圆仔**不攻击、无法被阻挡**:鸭爵受到伤害后移动速度 +300%;圆仔始终面向我方干员较多的一侧,来自正面的物理 / 法术伤害 −80%。流泪小子隐匿(被阻挡时才能被选中),攻击使目标晕眩 3.5 秒;高普尼克免疫晕眩、冻结和浮空。 - 右上角 🔍▶▶ 可以查看本回合即将来袭的敌人(预览区),左上角 🔍 查看本局信息(禁用盟约、禁用干员、策略)。 diff --git a/server/sim/ai.js b/server/sim/ai.js index 6c6ae55d..93b50323 100644 --- a/server/sim/ai.js +++ b/server/sim/ai.js @@ -295,7 +295,8 @@ function doHeal(b, u, prof, t) { for (let k = 1; k < n; k++) { let best = null, bd = Infinity; for (const a of b.alliesInRadius(prev.x, prev.y, 2.5, null)) { - if (seen.has(a.id) || a.hp >= a.s.maxHp || a.kind === 'device') continue; + // 禁疗 / noHeal units are no heal target for the bounces either (as injuredAlliesInKeys; 史尔特尔's 余烬, GitHub #52) + if (seen.has(a.id) || a.hp >= a.s.maxHp || a.kind === 'device' || a.s.flags.noHeal || (a.profile && a.profile.noHeal)) continue; const d = a.hpRatio; if (d < bd) { bd = d; best = a; } } diff --git a/server/sim/content/enemies.js b/server/sim/content/enemies.js index 27dc36ab..56fed7e9 100644 --- a/server/sim/content/enemies.js +++ b/server/sim/content/enemies.js @@ -2422,7 +2422,7 @@ export const KITS = Object.freeze({ enemy_1023_jmage: kitStealth, // 隐形术师 · stealth enemy_1283_sgkill: kitStealth, // 家族灭迹人 · stealth (清算时刻 n/a) enemy_1283_sgkill_2: kitStealth, // 家族暗影灭迹人 · stealth - enemy_1299_ymkilr: kitShadowKiller, // 山海众头目 · stealth, 1st attack after being blocked ×InvisibleCombat.atk_scale + enemy_1299_ymkilr: kitShadowKiller, // 山海众头目 · stealth; its 隐匿 turning on brings the mark: next attack ×InvisibleCombat.atk_scale (§22.8) enemy_1299_ymkilr_2: kitShadowKiller, // 山海众秘使 · same enemy_1389_winbab_2: kitStealth, // 访问团强攻冠军 · stealth (供暖器 priority n/a) enemy_1404_msnip: kitCrossbow, // 重弩突袭者 · stealth + 直击 (row/column bolt: arts + stun, reveals itself) diff --git a/test/sim/feedback2-surtr-stealth.test.js b/test/sim/feedback2-surtr-stealth.test.js index 07b08ae5..95ca6854 100644 --- a/test/sim/feedback2-surtr-stealth.test.js +++ b/test/sim/feedback2-surtr-stealth.test.js @@ -358,3 +358,29 @@ describe('深池逐火: an ember knocked out while blocked stays revealed until clean(h); }); }); + +test('#52 a chain healer\'s bounces skip 史尔特尔 in 余烬 (禁疗: no heal target) and go to the next injured ally', () => { + const SURTR = 'chess_char_5_07_a', PAP = 'chess_char_2_06_a', TANK = 'chess_char_1_04_a', MEDIC = 'chess_char_2_02_a'; + const h = makeBattle({ + defs: { enemies: { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e9, speed: 0, atk: 0 }) } }, autoFinish: false, timeLimit: 200, seed: 3, + units: [ + { chessId: SURTR, row: 9, col: 5, dir: 'RIGHT', skillIndex: 0, carryState: { sp: 0 } }, + { chessId: PAP, row: 10, col: 3, dir: 'RIGHT', skillIndex: 0, carryState: { sp: 0 } }, + { chessId: TANK, row: 10, col: 5, dir: 'RIGHT' }, + { chessId: MEDIC, row: 11, col: 5, dir: 'RIGHT', carryState: { sp: 0 } }, + ], + enemies: [{ key: 'enemy_dummy', pos: [9, 9] }], + }); + const s = h.unit(SURTR), tank = h.unit(TANK), med = h.unit(MEDIC); + h.run(1); + tank.hp = tank.s.maxHp * 0.5; med.hp = med.s.maxHp * 0.7; + h.b.dealDamage(null, s, { amount: 1e9, type: 'true', canDodge: false }); + assert.ok(s.findBuff('surtr:ember'), '余烬 is on'); + const ev0 = h.events.length; + h.run(6); + const bounces = h.events.slice(ev0).filter((ev) => ev[0] === 'atk' && ev[3] === 'chainHeal').map((ev) => ev[2]); + assert.ok(bounces.length > 0, 'the chain bounced'); + assert.ok(!bounces.includes(s.id), 'no bounce picks her'); + assert.ok(bounces.includes(med.id), 'the injured 赫默 gets the bounces'); + clean(h); +});