feedback1f WT: 蒂比's 起飞 is 对地规避 — ground enemies no longer target or hit her while airborne
Official: ba.liftoff 起飞 "不阻挡地面敌人且不会被地面敌人攻击,可以阻挡飞行敌人"; PRTS 术语释义 起飞 "包含对地规避(无法被不同 阵营行动方式为地面的单位选中)… 起飞后单位的行动方式依旧是地面"; PRTS 异常效果 MOTION_TARGET_FREE; PRTS 行动方式 "起飞的干员仍然 是地面单位". Cause: 起飞 was `unit.ground = false` (an operator on a high tile). Melee enemies were released, but every ranged ground enemy kept shooting her and the AoE / skills of ground enemies hit her (控潮术师 6 hits in one S2 window). Now 起飞 is the skill buff flag `liftoff`: - targeting.js evadesGround: no ground enemy (not Unit.isFlying) selects her (canTargetAlly; the content picks that bypass it: 周围四格 additions, chain / bounce jumps, the 自行炮 shells); flyers, 近地悬浮 and 浮空 enemies still do. - damage.js / Battle.applyStatus: a ground enemy's damage, element fill and statuses never land on her (area damage, shots under way); sourceless damage and `ignoreSelect` zones (【污染秽蚀】 "可对空,无视无法选择") still do. - Battle._blockerFor: she blocks no ground enemy; flyers at the air radius as before. - She stays a ground unit on her low tile: 污染秽蚀 at the low rate, 隐德来希 S2 can put a 血镰 on her (可对空).
This commit is contained in:
1 parent
a7ec6a1438
commit
80f5bf1930
12 files changed
+363
-46
No files matched your search
@@ -58,7 +58,7 @@
|
||||
// 'sword' 'vest' 'blink' 'summon' 'grow' 'phase' 'lpLoss' (x, y + extra {id, r, tiles, kind, tx, ty …}).
|
||||
|
||||
import { MOVE_SCALE } from '../constants.js';
|
||||
import { canTargetAlly, aggroCmp } from '../targeting.js';
|
||||
import { canTargetAlly, aggroCmp, evadesGround } from '../targeting.js';
|
||||
import { compileRoute } from '../ai.js';
|
||||
import { normalizeRoute } from '../simdata.js';
|
||||
import {
|
||||
@@ -687,7 +687,7 @@ function kitSpring(ab, e) {
|
||||
b.fx('beam', { x: e2.x, y: e2.y, from: e2.id, to: t.id, kind: 'springBullet' });
|
||||
elem(b, e2, t, 'erosion', e2.s.atk * ratio * Math.pow(SPRING_BOUNCE_FALLOFF, k));
|
||||
const prev = t;
|
||||
t = b.alliesInRadius(prev.x, prev.y, SPRING_BOUNCE_RANGE).filter((u) => !hit.has(u)).sort((p, q) => Math.hypot(p.x - prev.x, p.y - prev.y) - Math.hypot(q.x - prev.x, q.y - prev.y) || aggroCmp(p, q))[0];
|
||||
t = b.alliesInRadius(prev.x, prev.y, SPRING_BOUNCE_RANGE).filter((u) => !hit.has(u) && !evadesGround(e2, u)).sort((p, q) => Math.hypot(p.x - prev.x, p.y - prev.y) - Math.hypot(q.x - prev.x, q.y - prev.y) || aggroCmp(p, q))[0];
|
||||
}
|
||||
} else { // 十连击
|
||||
b.fx('beam', { x: e2.x, y: e2.y, from: e2.id, to: t0.id, kind: 'springCombo' });
|
||||
|
||||
@@ -28,7 +28,10 @@
|
||||
// keeps its ground path, Unit.isFlying; kitSyufo / kitParrot lose it when stunned). `e.profile.canTarget(ally)` = the
|
||||
// enemy's own target rule (只攻击地面单位 …), applied by the engine to the candidates before its priority order; a
|
||||
// special priority (优先攻击防御力最高的… / 生命上限最高的…) sorts by its key, ties by taunt then latest deployed
|
||||
// (targeting.js aggroCmp — PRTS 索敌: 特殊优先级 → 仇恨值).
|
||||
// (targeting.js aggroCmp — PRTS 索敌: 特殊优先级 → 仇恨值). An airborne ally (起飞, flag `liftoff`) is no target of a
|
||||
// ground enemy (对地规避, targeting.js evadesGround): targetsNear / allTargets skip it through canTargetAlly, the picks
|
||||
// that bypass it (周围四格 additions, chain and bounce jumps) filter it, and the engine refuses a ground enemy's damage
|
||||
// and statuses on it; damage zones "无视无法选择" (`ignoreSelect`) still reach it.
|
||||
//
|
||||
// Special types (factions.json):
|
||||
// FLY — engine (FLY motion, ranged-only targeting). Flyer kits below (御4, 护障, 寒霜, 萨科塔之翼/眼, 黑云 …).
|
||||
@@ -38,7 +41,8 @@
|
||||
// create those units (death spawns, embers, blades, 再生).
|
||||
// ELEMENT — element damage on hit = ATK × ep_damage_ratio into the ally's gauge. 侵蚀 (erosion) is an engine
|
||||
// gauge whose burst is the official one (termDescription ba.dt.erosion: "永久降低100点防御力并受到800点物理伤害").
|
||||
// DOT — damage zones (污染秽蚀: true damage, one tick per second however many cover a unit; 毒雾, 燃烧区域),
|
||||
// DOT — damage zones (污染秽蚀: true damage, one tick per second however many cover a unit; 毒雾, 燃烧区域 —
|
||||
// they ignore 无法选择, PRTS 污染秽蚀 "可对空,无视无法选择": `ignoreSelect`, 起飞 allies included),
|
||||
// bleeding (removed by healing), pulsing auras.
|
||||
// INVISIBLE — permanent `stealth` flag (engine: untargetable unless blocked or revealed; AoE still hits).
|
||||
// REFLECTION — 折射 (ba.refraction "生效时,法术抗性+70"): RES +refracting.magic_resistance while NOT silenced
|
||||
@@ -56,7 +60,7 @@
|
||||
// Custom hook: 'lpLoss' {amount, reason, source} — leader "扣除目标生命" effects; also summed into result.lpLoss.
|
||||
|
||||
import { TICK, MOVE_SCALE, ELEMENT, ATTACK_PAUSE } from '../constants.js';
|
||||
import { canTargetAlly, sortAllyTargets, aggroCmp } from '../targeting.js';
|
||||
import { canTargetAlly, sortAllyTargets, aggroCmp, evadesGround } from '../targeting.js';
|
||||
import { mitigate } from '../damage.js';
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
@@ -330,16 +334,19 @@ function onTick(b, dt) {
|
||||
// helpers (exported for bosses.js)
|
||||
|
||||
/** Element damage (erosion mapped onto the engine gauge). */
|
||||
export function elem(b, src, tgt, el, amount) {
|
||||
export function elem(b, src, tgt, el, amount, { ignoreSelect = false } = {}) {
|
||||
if (!tgt || !tgt.alive || !(amount > 0)) return 0;
|
||||
const element = el === 'erosion' ? EROSION : el;
|
||||
return b.dealDamage(src, tgt, { type: 'element', element, amount, tags: ['enemyAbility'] });
|
||||
return b.dealDamage(src, tgt, { type: 'element', element, amount, ignoreSelect, tags: ['enemyAbility'] });
|
||||
}
|
||||
|
||||
/** Skill / ability damage (no dodge unless asked). */
|
||||
export function hurt(b, src, tgt, amount, type = 'phys', { canDodge = false, tags = [], isSkill = true } = {}) {
|
||||
/**
|
||||
* Skill / ability damage (no dodge unless asked). `ignoreSelect` = the ability "无视无法选择" (zones): it also reaches an
|
||||
* airborne 起飞 ally, which a ground enemy's damage otherwise skips (damage.js, targeting.js evadesGround).
|
||||
*/
|
||||
export function hurt(b, src, tgt, amount, type = 'phys', { canDodge = false, tags = [], isSkill = true, ignoreSelect = false } = {}) {
|
||||
if (!tgt || !tgt.alive || !(amount > 0)) return 0;
|
||||
return b.dealDamage(src, tgt, { amount, type, canDodge, isSkill, tags: ['enemyAbility', ...tags] });
|
||||
return b.dealDamage(src, tgt, { amount, type, canDodge, isSkill, ignoreSelect, tags: ['enemyAbility', ...tags] });
|
||||
}
|
||||
|
||||
/** Allies whose tile is within `n` of (r, c): 'plus' = Manhattan (周围四格), 'box' = Chebyshev (周围8格). */
|
||||
@@ -895,8 +902,8 @@ function bleed(ab) {
|
||||
/**
|
||||
* 【污染秽蚀】 zone (PRTS 萨卡兹枯朽战士 / 萨卡兹枯朽战车: "范围内位于低地/高地的我方干员和召唤物每秒受到50/25点真实普通伤害
|
||||
* (可对空,无视无法选择、迷彩;同名效果不叠加)"): `low` true damage per second on low ground, `high` on high ground, to every
|
||||
* ally inside (flyers, stealthed and untargetable ones included); a unit inside several zones takes one tick per second
|
||||
* (`mem.pollutedAt`: the last tick it took).
|
||||
* ally inside (flyers, stealthed, untargetable and airborne 起飞 ones included — `ignoreSelect`; an airborne 蒂比 stands on
|
||||
* her low tile: `low`); a unit inside several zones takes one tick per second (`mem.pollutedAt`: the last tick it took).
|
||||
*/
|
||||
function pollution(b, src, x, y, r, life, low, high) {
|
||||
zone(b, { x, y, r, life, iv: POLLUTION_INTERVAL, kind: 'pollution', tick(units) {
|
||||
@@ -904,15 +911,21 @@ function pollution(b, src, x, y, r, life, low, high) {
|
||||
const v = u.ground ? low : high;
|
||||
if (!(v > 0) || b.time - (u.mem.pollutedAt ?? -Infinity) < POLLUTION_INTERVAL - 1e-6) continue;
|
||||
u.mem.pollutedAt = b.time;
|
||||
hurt(b, src, u, v, 'true', { tags: ['pollution'] });
|
||||
hurt(b, src, u, v, 'true', { tags: ['pollution'], ignoreSelect: true });
|
||||
}
|
||||
} });
|
||||
}
|
||||
|
||||
/** Damage zone (arts per tick). */
|
||||
/**
|
||||
* Damage zone (arts per tick) — on every ally inside, like 【污染秽蚀】 (`ignoreSelect`: 起飞 allies too) [ASSUMED for the
|
||||
* zones PRTS gives no 无法选择 note: 毒雾, 燃烧区域].
|
||||
*/
|
||||
function dmgZone(b, src, x, y, r, life, iv, amount, type = 'arts', kind = 'zone', el = null, elAmount = 0) {
|
||||
zone(b, { x, y, r, life, iv, kind, tick(units) {
|
||||
for (const u of units) { hurt(b, src, u, amount, type); if (el && elAmount > 0) elem(b, src, u, el, elAmount); }
|
||||
for (const u of units) {
|
||||
hurt(b, src, u, amount, type, { ignoreSelect: true });
|
||||
if (el && elAmount > 0) elem(b, src, u, el, elAmount, { ignoreSelect: true });
|
||||
}
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -1057,7 +1070,7 @@ function kitTidmag(ab) {
|
||||
const t0 = c.targets[0];
|
||||
if (!t0) return;
|
||||
const l = c.targets.slice();
|
||||
for (const u of alliesInTiles(b, t0.tileR, t0.tileC, 'plus', 1)) if (!l.includes(u)) l.push(u);
|
||||
for (const u of alliesInTiles(b, t0.tileR, t0.tileC, 'plus', 1)) if (!l.includes(u) && !evadesGround(e, u)) l.push(u);
|
||||
c.targets = l;
|
||||
},
|
||||
}];
|
||||
@@ -1673,7 +1686,7 @@ function kitLeaderMisc(key, ab, e) {
|
||||
case 'enemy_1513_dekght': {
|
||||
// 攻击时使目标与周围四格的单位受到物理伤害; 【蓄力攻击】 charge `duration` s, then ATK×atk_scale on the target's cross
|
||||
const s = ab.sk.ChargeAttack;
|
||||
return [kitDekght(ab), { before(c, b, e2) { const t = c.targets[0]; if (t) for (const u of alliesInTiles(b, t.tileR, t.tileC, 'plus', 1)) if (!c.targets.includes(u)) c.targets.push(u); } },
|
||||
return [kitDekght(ab), { before(c, b, e2) { const t = c.targets[0]; if (t) for (const u of alliesInTiles(b, t.tileR, t.tileC, 'plus', 1)) if (!c.targets.includes(u) && !evadesGround(e2, u)) c.targets.push(u); } },
|
||||
skill(s, (b, e2) => {
|
||||
const t = e2.blockedBy;
|
||||
const dur = s.bb.duration ?? 0, r = t.tileR, cc = t.tileC;
|
||||
@@ -1789,7 +1802,7 @@ function kitLeaderMisc(key, ab, e) {
|
||||
let prev = c.target;
|
||||
const hit = new Set([prev]);
|
||||
for (let k = 1; k < n; k++) {
|
||||
const nx = b.alliesInRadius(prev.x, prev.y, jr).find((u) => !hit.has(u));
|
||||
const nx = b.alliesInRadius(prev.x, prev.y, jr).find((u) => !hit.has(u) && !evadesGround(e2, u));
|
||||
if (!nx) break;
|
||||
hit.add(nx);
|
||||
hurt(b, e2, nx, e2.s.atk * Math.pow(fall, k), 'arts');
|
||||
@@ -2320,7 +2333,7 @@ export const KITS = Object.freeze({
|
||||
b.fx('telegraph', { x, y, r: 1, dur: CANNON_SHOTS, kind: 'cannon' });
|
||||
for (let i = 1; i <= CANNON_SHOTS; i++) b.after(i, () => {
|
||||
if (!e.alive) return;
|
||||
const t = b.alliesInRadius(x, y, 1).sort((p, q) => q.hpRatio - p.hpRatio)[0];
|
||||
const t = b.alliesInRadius(x, y, 1).filter((u) => !evadesGround(e, u)).sort((p, q) => q.hpRatio - p.hpRatio)[0];
|
||||
if (t) { b.fx('explode', { x: t.x, y: t.y, r: 0.5, kind: 'cannon' }); hurt(b, e, t, e.s.atk * (ab.sk.Cannon.bb.atk_scale ?? 0), 'arts'); }
|
||||
}, { owner: e });
|
||||
}, { cond: (b, e) => allTargets(b, e).length > 0 })],
|
||||
|
||||
@@ -35,17 +35,20 @@ function onDefaultSkill(chess) {
|
||||
return !d || !chess?.skill || d.skillId === chess.skill.skillId;
|
||||
}
|
||||
|
||||
/** 蒂比 take-off (起飞): airborne — blocks flyers only (flags.blockFly of the skill), releases ground enemies. */
|
||||
/**
|
||||
* 起飞 of 蒂比's skills (gamedata_const ba.liftoff "不阻挡地面敌人且不会被地面敌人攻击,可以阻挡飞行敌人"): the skill's flags
|
||||
* `liftoff` (no ground enemy blocked — Battle._blockerFor; 对地规避 — targeting.js evadesGround: no ground enemy selects or
|
||||
* damages her) and `blockFly` (blocks flyers at the air radius). She stays a ground unit on her tile (PRTS 行动方式 "起飞的
|
||||
* 干员仍然是地面单位": `unit.ground` unchanged — 隐德来希's 血镰, 地面干员 bonds / items still count her).
|
||||
*/
|
||||
const LIFTOFF_FLAGS = Object.freeze({ blockFly: true, liftoff: true });
|
||||
/** 蒂比 take-off: the ground enemies she blocked walk on. */
|
||||
function tippiTakeOff({ battle, unit }) {
|
||||
unit.mem.tippiGround = unit.ground;
|
||||
unit.ground = false;
|
||||
battle.releaseBlocked(unit);
|
||||
battle.fx('takeoff', { x: unit.x, y: unit.y, id: unit.id });
|
||||
}
|
||||
/** 蒂比 landing when the airborne skill ends. */
|
||||
/** 蒂比 landing when the airborne skill ends: the flyers she held are released. */
|
||||
function tippiLand({ battle, unit }) {
|
||||
if (unit.mem.tippiGround != null) unit.ground = unit.mem.tippiGround;
|
||||
unit.mem.tippiGround = null;
|
||||
battle.releaseBlocked(unit);
|
||||
}
|
||||
|
||||
@@ -594,11 +597,12 @@ export default {
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 2_13 蒂比 紧急赶场通知 (AUTO): "受到攻击后触发" — any incoming enemy attack sets it off (the kit is the only
|
||||
// trigger: the engine rule is disabled so non-attack damage never fires it) and a physical/arts one is dodged; takes
|
||||
// off for the duration: skill range, ATK +atk, attacks become 3 shots, blocks flying (not ground) enemies. Trait
|
||||
// "起飞后能够阻挡2个飞行敌人": flying enemies are blocked only while airborne. 片场工作指南: if not attacked for
|
||||
// stack_time s, the next physical/arts attack is dodged (prob); every attack restarts that timer.
|
||||
// S1 专业喷绘技巧 (alt, DEFAULT trigger from data): takes off at once for its duration — skill range, ATK +atk, blocks
|
||||
// flyers only; no triple shot, and incoming attacks never set it off.
|
||||
// off for the duration (起飞, LIFTOFF_FLAGS: she blocks no ground enemy and none attacks or damages her): skill range,
|
||||
// ATK +atk, attacks become 3 shots, blocks flying (not ground) enemies. Trait "起飞后能够阻挡2个飞行敌人": flying enemies
|
||||
// are blocked only while airborne. 片场工作指南: if not attacked for stack_time s, the next physical/arts attack is
|
||||
// dodged (prob); every attack restarts that timer.
|
||||
// S1 专业喷绘技巧 (alt, DEFAULT trigger from data): takes off at once for its duration (起飞 as above) — skill range,
|
||||
// ATK +atk, blocks flyers only; no triple shot, and incoming attacks never set it off.
|
||||
chess_char_2_13_a: (bb, chess, def) => {
|
||||
const t = talentBb(chess, 0);
|
||||
const s2 = onDefaultSkill(chess);
|
||||
@@ -606,7 +610,7 @@ export default {
|
||||
trait: { blockFly: false },
|
||||
skill: {
|
||||
kind: 'duration', trigger: { rule: 'CUSTOM_RANGE', grid: [] }, // never by the engine: the hit handler below
|
||||
mods: { atkPct: num(bb.atk) }, flags: { blockFly: true },
|
||||
mods: { atkPct: num(bb.atk) }, flags: LIFTOFF_FLAGS,
|
||||
targeting: { rangeGrid: def?.skill?.rangeGrid ?? null },
|
||||
attack: { hits: 3 },
|
||||
onStart: tippiTakeOff,
|
||||
@@ -614,7 +618,7 @@ export default {
|
||||
},
|
||||
skills: {
|
||||
skchr_tippi_1: {
|
||||
kind: 'duration', mods: { atkPct: num(bb.atk) }, flags: { blockFly: true },
|
||||
kind: 'duration', mods: { atkPct: num(bb.atk) }, flags: LIFTOFF_FLAGS,
|
||||
targeting: { rangeGrid: def?.skill?.rangeGrid ?? null },
|
||||
onStart: tippiTakeOff,
|
||||
onEnd: tippiLand,
|
||||
|
||||
@@ -893,8 +893,8 @@ const KITS = {
|
||||
for (const a of unit.mem.sickles || []) {
|
||||
if (!a.alive || !a.deployed) continue;
|
||||
// PRTS 备注 "被添加血镰的单位处于起飞时,血镰可对空": a sickle on the ground spares air units (FLY, 近地悬浮, 浮空);
|
||||
// 起飞 = an airborne skywalker (蒂比's skill: off the ground, flag blockFly)
|
||||
const air = !a.ground && !!a.s.flags.blockFly;
|
||||
// 起飞 = an airborne skywalker (蒂比's skills: flag `liftoff`; still a 地面单位, so she can carry one)
|
||||
const air = !!a.s.flags.liftoff;
|
||||
for (const e of battle.enemiesInRadius(a.x, a.y, RING1)) {
|
||||
if (e.isFlying && !air) continue;
|
||||
battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale), type: 'phys', isSkill: true, tags: ['skill', 'bloodSickle'] });
|
||||
|
||||
Reference in new issue
Block a user