feedback7 R22-OSIM: enemy effects PRTS marks 不可对空 no longer hit the flying 炎佑 (death blasts, 水遁忍者's vortex, 鼎沸, 祈祷邀约, the 胄 blades); golden: unchanged
Community report relayed by the owner: 炎祐好像吃到了不该吃到的地面伤害. 炎佑 is a flying ally (PRTS “炎佑” 行动方式 飞行). The ground terrain (活性源石), 烹泉 / 沏虹, 庞贝 and 集团军重型火炮 already skipped it, but these 不可对空 effects (PRTS, read for every enemy of data/enemies.json) went through areaAllies / fieldAllies / targetsNear, which exclude 起飞 allies only: - 死亡爆炸 of 高能源石虫 / 冰爆源石虫 / 卷心籽 (archetypes deathBoom: `noAir`, on by default; 寒冷 too); - 水遁忍者 漩涡形态 "每秒对半径1.0范围内的所有我方单位造成…(无视迷彩,不可对空)"; - 鼎沸 "攻击范围内的所有我方单位每秒受到…法术持续伤害与…灼燃损伤(不可对空)"; - “萨科塔昂首” 祈祷邀约 "令全场我方单位(不可对空,无视迷彩)获得15s【受邀祈祷】攻击速度-30"; - “斩胄之剑” / “破胄之锤” "普通攻击对攻击范围内的所有我方单位造成…物理普通伤害,不可对空" (its target rule too). A ground enemy's normal attack and the 不可对空-free effects still reach it (PRTS: enemies that hit air target it). Tests: test/content/feedback7-yanyou-noair.test.js (8; all fail on the parent).
This commit is contained in:
1 parent
42caca3be8
commit
977215e14d
8 files changed
+114
-11
No files matched your search
+1
-1
@@ -110,7 +110,7 @@
|
||||
- **附加盟约**(精准、迅捷、灵巧、奥术、坚守、助力、远见、奇迹、投资人、突袭、不屈、调和、协防干员、独行、绝技):多为 2 / 3 名激活;「独行」需要场上**恰好 1 名**,「绝技」按场上的精锐数量计算,没有势力盟约的干员算「协防干员」。
|
||||
- **本局禁用**:「标准模拟」与官方相同,关闭拉特兰、阿戈尔、卡西米尔、灵巧、奥术、奇迹、投资人、突袭、独行、绝技这 10 个盟约,它们不会激活。你有这些盟约的成员时(奇迹、投资人也算整备区里的成员),盟约栏末尾显示**灰色带 ✕ 的盟约图标和「本局禁用」**,干员卡、盟约标签和盟约详情也这样标注。「远见」「奇迹」「投资人」计入整备区,盟约详情的「成员」人数也一样计入整备区里的成员(临时整备区不算)。
|
||||
- **调和**:激活时,场上已有成员的核心盟约人数各 +1(只加 1 人,不论有几名调和干员)。盟约详情会写「含调和 +1」,成员列表上方列出带来这 1 人的调和干员(如缪尔赛思)。
|
||||
- **炎佑**:场上 6 名以上「炎」干员时召唤炎佑(9 名时两只)。它追踪全场仇恨最高的敌人并悬停在其上方,技能「祛恶之焰」锁定 1 个目标**持续喷火**(最长 20 秒,每秒对目标周围的敌人造成法术伤害并附带灼燃,喷火时不普攻,目标死亡或被沉默时结束);场上暂时没有敌人时停在原地等待。它持有官方的「孤立」:医疗干员不会治疗它,友方的增益、光环和天赋也不会选中它(敌人照常可以攻击它)。
|
||||
- **炎佑**:场上 6 名以上「炎」干员时召唤炎佑(9 名时两只)。它追踪全场仇恨最高的敌人并悬停在其上方,技能「祛恶之焰」锁定 1 个目标**持续喷火**(最长 20 秒,每秒对目标周围的敌人造成法术伤害并附带灼燃,喷火时不普攻,目标死亡或被沉默时结束);场上暂时没有敌人时停在原地等待。它持有官方的「孤立」:医疗干员不会治疗它,友方的增益、光环和天赋也不会选中它(敌人照常可以攻击它)。它是飞行单位:标注「不可对空」的敌人效果(高能源石虫 / 冰爆源石虫 / 卷心籽的死亡爆炸、水遁忍者的漩涡、鼎沸、“萨科塔昂首”的祈祷邀约、“斩胄之剑” / “破胄之锤”的攻击等)不会波及它。
|
||||
- **层数**:盟约通过各种效果叠加层数,层数越高效果越强(例如「炎」攻击力随层数提升、「远见」每 10 层给资金)。**层数整局保留,但只有盟约激活时才生效**,多数情况下也只有激活的盟约能叠层。每个盟约最多 **999 层**(与官方相同,取自官方客户端的常量):到 999 后再获得的层数直接作废,按层数发放的奖励(如「远见」的资金)也随之停止。作战中获得的层数在盟约栏里**实时**显示,作战结束后一直保留到结算(联防期间也不会退回去)。「调和」「协防干员」「独行」「绝技」与官方一样**不显示层数**(盟约栏、详情和结算卡片都不显示),但叠层的特质 / 策略 / 装备照样给它们加层(PRTS 盟约记录:「部分盟约不会显示叠加层数,但是叠加层数的特质/策略/装备等效果仍然对其生效」),这些层数也计入激活层数总和;它们的效果本身与层数无关。
|
||||
- **特质在哪里生效**:「进入休整期时」「休整期结束时」「刷新时」的特质(详情卡的第一项)只在干员部署在作战区时生效;文字里写着「此干员在整备区时也有效」的拉普兰德、调香师、波登可,以及激活【炎】/【投资人】时的琳琅诗怀雅,在整备区里也生效(临时整备区不算),与官方数据一致;「获得时」「售出时」的特质在获得、售出的那一刻生效,不论干员在哪里。「远见」「奇迹」「投资人」的「整备区的…干员也可用于激活盟约」只是把整备区的成员算进人数,不会让赫默等干员的特质在整备区生效。写着「使已激活的…」的特质要求盟约当时已经激活:例如「叙拉古」只数作战区里不同的叙拉古干员,整备区里的拉普兰德不算在这 3 名里;拉普兰德每回合只在她经历的第一次主动刷新时叠层(免费刷新也算),那次刷新时叙拉古没有激活,这一回合就不会再叠。
|
||||
- **加成怎么叠**:盟约、策略、装备、机变卡和特质给的「攻击力 / 防御力 / 生命值 +X%」与技能的「攻击力 +X%」**相加**后再乘到属性上(官方:直接乘算),例如精准 +130%、独行 +60%、一件 +40% 的装备合计 +230%,而不是 ×2.3×1.6×1.4。「伤害提升至 X%」「受到的伤害 +X%」(维多利亚、谢拉格、奥术、脆弱等)这类伤害倍率之间仍然相乘;但同名效果只算一个——同一战场两名玩家都开了「奥术」时,敌人身上只挂一层奥术易伤,取两人中较高的那一个(不再相乘);两名「灵知」的坚冰、两名「莱恩哈特」/「缄默德克萨斯」的法抗削减也一样只算最强的一个。「坚守」的反伤计入被打的那名成员(个人对领袖伤害统计里能看到)。
|
||||
|
||||
@@ -362,6 +362,10 @@ one; the sourceless 毒雾 of 假想敌:蚀裂 skips a 隐匿 one but still re
|
||||
范围内的所有单位持续视为受到源石污染区影响,技力自然回复速度倍率-80%,每0.5秒受到50真实持续伤害(同类效果取最高)") is the same
|
||||
kind of damage — 无来源 true (like the terrain it stands for [ASSUMED]), the chimera credited — not a 流失 (player report
|
||||
D1 audit): radius 1.2, a tick every 0.5 s, one tick per unit per 0.5 s however many chimeras reach it (`mem.chimeraAt`).
|
||||
Every enemy effect PRTS marks "不可对空" skips a flying ally (the 炎佑 dragon, `isFlying`): the 死亡爆炸 of 高能源石虫 / 冰爆源石虫 /
|
||||
卷心籽 (archetypes.js `deathBoom`, `noAir` by default), 水遁忍者's 漩涡形态, 鼎沸's pulses, “萨科塔昂首”'s 祈祷邀约, the attack of
|
||||
“斩胄之剑” / “破胄之锤”, the splash and blast of 烹泉 / 沏虹, 庞贝's blast and 集团军重型火炮's shell and 燃烧区域 (the first six
|
||||
reached it until 0.2.1 — a community report).
|
||||
|
||||
**Knock-outs that are not deaths** (`content/enemies/archetypes.js`; player reports after 0.1.0): a `killed` ability that keeps the
|
||||
enemy alive hides the knock-out from every later `kill` handler, the kill count, kill credit and the bounty — they all
|
||||
|
||||
@@ -524,11 +524,12 @@ function kitBlade(ab, e, b, tpl) {
|
||||
// 自缚 (moved by hand) · 不可阻挡 · 失衡免疫 (PRTS 天赋 "{{特殊机制|静态刚体}},不可阻挡、失衡免疫…" — data `staticBody` too)
|
||||
b2.addBuff(e2, { key: 'boss:anchor', persist: true, flags: { noMove: true, selfBound: true, unblockable: true, noDisplace: true } });
|
||||
if (BLADE_ATK_SCALE[e2.defId]) e2.profile.atkScale = BLADE_ATK_SCALE[e2.defId];
|
||||
e2.profile.canTarget = (u) => !u.isFlying; // "…不可对空" (below)
|
||||
},
|
||||
// 范围物理伤害 — "普通攻击对攻击范围内的所有我方单位造成…物理普通伤害": a normal attack on every operator it can target
|
||||
// 范围物理伤害 — "普通攻击对攻击范围内的所有我方单位造成…物理普通伤害,不可对空": a normal attack on every operator it can target
|
||||
// (canTargetAlly: no 隐匿 or 迷彩 one — it is never blocked —, PRTS 选择器: a normal attack does not ignore 迷彩; until
|
||||
// 0.1.2 it took them all, `ranged: false`)
|
||||
before(c, b2, e2) { const l = targetsNear(b2, e2, e2.base.rangeRadius || 1.6); if (l.length) c.targets = l; },
|
||||
// 0.1.2 it took them all, `ranged: false`), never a flying ally (the 炎佑 dragon — until 0.2.1 it was hit)
|
||||
before(c, b2, e2) { const l = targetsNear(b2, e2, e2.base.rangeRadius || 1.6).filter((u) => !u.isFlying); if (l.length) c.targets = l; },
|
||||
taken(c, b2, e2) {
|
||||
if (c.amount > 0) { // 受到伤害时令假想敌:胄受到等量的无来源生命流失 (【瘫痪】; 出击模式 [ASSUMED], see PART_TRANSFER)
|
||||
const L = leader(b2);
|
||||
|
||||
@@ -181,15 +181,17 @@ const deathSpawn = (key, cnt, extra = {}) => ({
|
||||
|
||||
/**
|
||||
* Death explosion on the allies within r it can select (areaAllies — PRTS 高能源石虫 / 冰爆源石虫 / 卷心籽 死亡爆炸 "…无视迷彩,
|
||||
* 不可对空", no 无视无法选择: a 隐匿 ally is spared; the dead enemy blocks nobody).
|
||||
* 不可对空", no 无视无法选择: a 隐匿 ally is spared; the dead enemy blocks nobody). `noAir` (default — all three say so):
|
||||
* a flying ally (the 炎佑 dragon) is skipped; until 0.2.1 it took the blast (community report: 炎祐吃到了不该吃到的地面伤害).
|
||||
*/
|
||||
const deathBoom = ({ scale, type = 'phys', r = BOOM_RADIUS, status = null, sil = true, cond = null }) => ({
|
||||
const deathBoom = ({ scale, type = 'phys', r = BOOM_RADIUS, status = null, sil = true, cond = null, noAir = true }) => ({
|
||||
sil,
|
||||
death(c, b, e, a) {
|
||||
if (c.reason !== 'killed' || (cond && !cond(e, a))) return;
|
||||
const atk = e.s.atk;
|
||||
b.fx('explode', { x: e.x, y: e.y, r, kind: 'deathBoom', id: e.id });
|
||||
for (const u of areaAllies(b, e, e.x, e.y, r)) {
|
||||
if (noAir && u.isFlying) continue;
|
||||
if (scale > 0) hurt(b, e, u, atk * scale, type);
|
||||
if (status && u.alive) b.applyStatus(u, status.key, { duration: status.dur, source: e, value: status.value });
|
||||
}
|
||||
|
||||
@@ -128,7 +128,13 @@ export const DOT_KITS = Object.freeze({
|
||||
tick(b, e2) {
|
||||
const r = (e2.def.raw.stats && e2.def.raw.stats.rawRangeRadius) || 1.6;
|
||||
b.fx('explode', { x: e2.x, y: e2.y, r, kind: 'boil' });
|
||||
for (const u of areaAllies(b, e2, e2.x, e2.y, r)) { hurt(b, e2, u, e2.s.atk * (T(ab, 'aoe.atk_scale') ?? 1), 'arts'); elem(b, e2, u, 'burn', e2.s.atk * (T(ab, 'aoe.ep_damage_ratio') ?? 0)); }
|
||||
// PRTS 鼎沸 "攻击范围内的所有我方单位每秒受到…法术持续伤害与…灼燃损伤(不可对空)": never a flying ally (the 炎佑 dragon;
|
||||
// until 0.2.1 it burned)
|
||||
for (const u of areaAllies(b, e2, e2.x, e2.y, r)) {
|
||||
if (u.isFlying) continue;
|
||||
hurt(b, e2, u, e2.s.atk * (T(ab, 'aoe.atk_scale') ?? 1), 'arts');
|
||||
elem(b, e2, u, 'burn', e2.s.atk * (T(ab, 'aoe.ep_damage_ratio') ?? 0));
|
||||
}
|
||||
},
|
||||
}],
|
||||
});
|
||||
@@ -117,12 +117,13 @@ function kitSteal(ab) {
|
||||
}
|
||||
|
||||
/** “萨科塔昂首” 【祈祷邀约】 (PRTS "令全场我方单位(不可对空,无视迷彩)获得15s【受邀祈祷】攻击速度-30"): a whole-field skill
|
||||
* selection — every ally but an unblocking 隐匿 / untargetable / sleeping one (fieldAllies; no 无视无法选择). */
|
||||
* selection — every ally but an unblocking 隐匿 / untargetable / sleeping one (fieldAllies; no 无视无法选择) and, 不可对空, a
|
||||
* flying one (the 炎佑 dragon; until 0.2.1 it was slowed too). */
|
||||
function kitRoar(ab) {
|
||||
const s = ab.sk.Roar;
|
||||
return [selfFear(ab), skill(s, (b, e) => {
|
||||
b.fx('telegraph', { x: e.x, y: e.y, r: 99, kind: 'roar', id: e.id });
|
||||
for (const u of fieldAllies(b, e)) b.addBuff(u, { key: 'ab:roar', duration: s.bb.duration ?? 0, refresh: 'extend', mods: { aspd: s.bb.attack_speed ?? 0 }, visible: true });
|
||||
for (const u of fieldAllies(b, e)) if (!u.isFlying) b.addBuff(u, { key: 'ab:roar', duration: s.bb.duration ?? 0, refresh: 'extend', mods: { aspd: s.bb.attack_speed ?? 0 }, visible: true });
|
||||
}, { sil: true })];
|
||||
}
|
||||
|
||||
|
||||
@@ -379,10 +379,11 @@ export const SPECIAL_KITS = Object.freeze({
|
||||
fire(b, e, a) { a.until = b.time + (T(ab, 'EndRotate.rotate_duration') ?? 0); b.fx('telegraph', { x: e.x, y: e.y, r: 1, kind: 'spin', id: e.id }); },
|
||||
iv: T(ab, 'RotateDamage.interval') ?? 1,
|
||||
// "每秒对半径1.0范围内的所有我方单位造成…(无视迷彩,不可对空),自身受阻止攻击类异常效果影响期间无法造成此伤害": an area
|
||||
// selection, skipped while 晕眩 / 冻结 / 浮空 (flag stun), 沉睡 or 缴械 hold it (PRTS 异常效果 §阻止攻击; until 0.1.3 it spun on)
|
||||
// selection, skipped while 晕眩 / 冻结 / 浮空 (flag stun), 沉睡 or 缴械 hold it (PRTS 异常效果 §阻止攻击; until 0.1.3 it spun on);
|
||||
// 不可对空: never a flying ally (the 炎佑 dragon — until 0.2.1 it was hit)
|
||||
tick(b, e, a) {
|
||||
if (!(a.until > b.time) || e.s.flags.stun || e.s.flags.sleep || e.s.flags.disarm) return;
|
||||
for (const u of areaAllies(b, e, e.x, e.y, T(ab, 'RotateDamage.attack@range_radius') ?? 1)) hurt(b, e, u, e.s.atk * (T(ab, 'RotateDamage.attack@atk_scale') ?? 1), 'phys');
|
||||
for (const u of areaAllies(b, e, e.x, e.y, T(ab, 'RotateDamage.attack@range_radius') ?? 1)) if (!u.isFlying) hurt(b, e, u, e.s.atk * (T(ab, 'RotateDamage.attack@atk_scale') ?? 1), 'phys');
|
||||
},
|
||||
};
|
||||
// 漩涡形态 "不进行普通攻击" (PRTS 天赋): no blocked attack while it spins (until 0.1.3 its blocker took both)
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// test/content/feedback7-yanyou-noair.test.js — 炎佑 is a flying ally (PRTS “炎佑” 行动方式 飞行): the enemy effects PRTS marks
|
||||
// 不可对空 never reach it (community report: 炎祐好像吃到了不该吃到的地面伤害). The death blasts of 高能源石虫 / 冰爆源石虫 /
|
||||
// 卷心籽 ("…无视迷彩,不可对空"), 水遁忍者's 漩涡形态 ("每秒对半径1.0范围内的所有我方单位造成…(无视迷彩,不可对空)"), “萨科塔昂首”'s
|
||||
// 祈祷邀约 ("令全场我方单位(不可对空,无视迷彩)获得15s【受邀祈祷】"), 鼎沸's pulses ("攻击范围内的所有我方单位每秒受到…(不可对空)")
|
||||
// and the attack of “斩胄之剑” / “破胄之锤” ("普通攻击对攻击范围内的所有我方单位造成…物理普通伤害,不可对空") took it until 0.2.1; a
|
||||
// ground operator in reach still does.
|
||||
// Run: node --test test/content/feedback7-yanyou-noair.test.js
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeBattle, checkInvariants } from '../helpers/battleHarness.js';
|
||||
import { spawnYanyou } from '../../server/sim/content/tokens.js';
|
||||
|
||||
/** 角峰 on (10,5), the 炎佑 dragon moved onto (10,6). */
|
||||
function field(seed = 4) {
|
||||
const h = makeBattle({ timeLimit: 60, autoFinish: false, seed, hooks: ['damaged'], captureNoisy: true,
|
||||
units: [{ uid: 1, chessId: 'chess_char_1_02_a', row: 10, col: 5 }] });
|
||||
h.step();
|
||||
const dragon = spawnYanyou(h.b, 'p1', { count: 1 })[0];
|
||||
assert.ok(dragon && dragon.isFlying, 'a flying ally');
|
||||
assert.ok(h.b.relocate(dragon, 10, 6));
|
||||
return { h, dragon, op: h.unit(1) };
|
||||
}
|
||||
const still = (e) => { e.base.moveSpeed = 0; e.s.moveSpeed = 0; };
|
||||
const tough = (e) => { e.base.maxHp = 1e9; e.s.maxHp = 1e9; e.hp = 1e9; };
|
||||
/** Damage `src` dealt to `t` other than its normal attacks. */
|
||||
const taken = (h, t, src) => h.hooksOf('damaged').filter((c) => c.target === t && c.source === src && !c.dmg?.isAttack).reduce((s, c) => s + c.amount, 0);
|
||||
|
||||
for (const [name, key, prep] of [
|
||||
['高能源石虫', 'enemy_1021_bslime', null],
|
||||
['冰爆源石虫', 'enemy_1067_snslime', null],
|
||||
['卷心籽 (ignited)', 'enemy_10065_ftzlc', (e) => { e.mem.ab.ignited = true; }],
|
||||
]) {
|
||||
test(`${name}'s death blast (不可对空) spares 炎佑 and still hits a ground operator`, () => {
|
||||
const { h, dragon, op } = field();
|
||||
const e = h.spawn(key, { pos: [10, 6] });
|
||||
still(e);
|
||||
if (prep) prep(e);
|
||||
h.step();
|
||||
const hp0 = dragon.hp;
|
||||
h.b.dealDamage(null, e, { amount: e.hp + 10, type: 'true', tags: ['test'] });
|
||||
h.run(0.3);
|
||||
assert.equal(e.alive, false);
|
||||
assert.ok(taken(h, op, e) > 0, 'the ground operator is hit');
|
||||
assert.equal(taken(h, dragon, e), 0, 'the flyer is not');
|
||||
assert.equal(dragon.hp, hp0);
|
||||
if (key === 'enemy_1067_snslime') {
|
||||
assert.ok(op.findBuff('cold'), '寒冷 on the ground operator');
|
||||
assert.equal(dragon.findBuff('cold'), null, 'no 寒冷 on the flyer');
|
||||
}
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
}
|
||||
|
||||
test('水遁忍者\'s 漩涡形态 (不可对空) hits the ground operator beside it, never 炎佑', () => {
|
||||
const { h, dragon, op } = field(6);
|
||||
const e = h.spawn('enemy_10116_ymgtop', { pos: [10, 6] });
|
||||
still(e);
|
||||
tough(e);
|
||||
assert.ok(h.runUntil(() => taken(h, op, e) > 0, 40), 'the vortex reached the operator');
|
||||
h.run(3);
|
||||
assert.equal(taken(h, dragon, e), 0);
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
test('“萨科塔昂首”\'s 祈祷邀约 (不可对空) slows the ground operator, not 炎佑', () => {
|
||||
const { h, dragon, op } = field(7);
|
||||
const e = h.spawn('enemy_10085_hllevi_2', { pos: [10, 8] });
|
||||
still(e);
|
||||
tough(e);
|
||||
assert.ok(h.runUntil(() => !!op.findBuff('ab:roar'), 40), 'the ground operator is invited');
|
||||
assert.equal(dragon.findBuff('ab:roar'), null);
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
|
||||
for (const [name, key] of [['鼎沸', 'enemy_10054_cjhot'], ['“斩胄之剑”', 'enemy_9014_acstma'], ['“破胄之锤”', 'enemy_9015_acstmb']]) {
|
||||
test(`${name} (不可对空) hits the ground operator beside it, never 炎佑`, () => {
|
||||
const { h, dragon, op } = field(8);
|
||||
const e = h.spawn(key, { pos: [10, 6] });
|
||||
still(e);
|
||||
tough(e);
|
||||
h.run(12);
|
||||
const dealt = (t) => h.hooksOf('damaged').filter((c) => c.target === t && c.source === e).reduce((s, c) => s + c.amount, 0);
|
||||
assert.ok(dealt(op) > 0, 'the ground operator is hit');
|
||||
assert.equal(dealt(dragon), 0, 'the flyer is not');
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user