feedback1 integrate: WL 源石溶剂 drain is 无来源 true damage; 信仰搅拌机 S3 counters any damage
# Conflicts: # data/items.json
This commit is contained in:
commit
e228801bee
16 files changed
+637
-47
No files matched your search
@@ -19,7 +19,8 @@
|
||||
// instances multiplied (×5.4 × ×5.6 on a leader at ~250 layers, DESIGN §20.10)
|
||||
// 坚守 steadShip all operators max HP +(base + per·L); tier 2: 40 % of a non-member operator's damage is borne by
|
||||
// the members on the field (split evenly, sourceless true damage — already mitigated), members’ thorns
|
||||
// (base + per·L arts, sourceless but credited to the member hit, ≤ 1 per cd_duration per member)
|
||||
// (base + per·L arts, sourceless but credited to the member hit, ≤ 1 per cd_duration per member;
|
||||
// 无来源 damage uses the chance and hits nobody, a 流失 never does — PRTS 备注 / 作战机制)
|
||||
// + 脆弱 ×damage_scale for weak[limit] s
|
||||
// 助力 deputShip all operators DEF +(base + per·L), redeploy time ×(1 + respawn_time)
|
||||
// 突袭 raidShip member idle ≥ no_attack_duration s (or skill ready) with no enemy in range → "保留技力立即再部署"
|
||||
@@ -43,6 +44,7 @@
|
||||
|
||||
import { absoluteRangeKeys, canTargetEnemy } from '../../../targeting.js';
|
||||
import { localOrder, localBefore } from '../../../dir.js';
|
||||
import { isHpLoss } from '../../../damage.js';
|
||||
import {
|
||||
num, bondRecord, buffParams, bondTier, bondLayers, isMember, isElite, isGroundOp, onField, playerOps, passiveBuff,
|
||||
fxOn, N4, N8, bodyInKeys, directMods,
|
||||
@@ -442,7 +444,9 @@ export function install(battle) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (dmg.steadShare || dmg.steadThorn || c.type === 'element' || !st.members[ID.stead].has(t)) return;
|
||||
// a 流失 never uses a thorn chance (PRTS 作战机制 "生命流失不会触发反伤"); 无来源 damage does, hitting nobody (备注
|
||||
// "可被无来源伤害消耗反伤机会")
|
||||
if (dmg.steadShare || dmg.steadThorn || c.type === 'element' || isHpLoss(dmg) || !st.members[ID.stead].has(t)) return;
|
||||
const bb = st.bb[ID.stead];
|
||||
const last = t.mem[STEAD_CD] ?? -Infinity;
|
||||
if (battle.time - last < num(bb.cd_duration) - 1e-9) return;
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
|
||||
import { gameData, num, buffsOf, passiveBuff, effectRecord, directMods } from './support/index.js';
|
||||
import { isShopItem } from '../simdata.js';
|
||||
import { isHpLoss } from '../damage.js';
|
||||
|
||||
// =====================================================================================================================
|
||||
// data helpers
|
||||
@@ -472,7 +473,7 @@ export function install(battle) {
|
||||
if (!t || t.side !== 'ally' || (t.kind !== 'op' && t.kind !== 'token')) return;
|
||||
// 自愈: every damage instance an own unit takes (not HP loss 流失, not element gauge fills, not under 禁疗)
|
||||
const amt = heal.get(t.ownerId);
|
||||
if (amt > 0 && ctx.amount > 0 && ctx.type !== 'element' && !(ctx.dmg && Array.isArray(ctx.dmg.tags) && ctx.dmg.tags.includes('hpLoss'))
|
||||
if (amt > 0 && ctx.amount > 0 && ctx.type !== 'element' && !isHpLoss(ctx.dmg)
|
||||
&& t.alive && t.deployed && t.hp > 0 && !t.s.flags.noHeal) {
|
||||
battle.heal(null, t, amt, { self: true });
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
import { TICK, MOVE_SCALE, ELEMENT, ATTACK_PAUSE, PROJECTILE_SPEEDS } from '../constants.js';
|
||||
import { canTargetAlly, sortAllyTargets, aggroCmp } from '../targeting.js';
|
||||
import { mitigate } from '../damage.js';
|
||||
import { mitigate, periodicDamage } from '../damage.js';
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// constants (numbers that exist nowhere in the data)
|
||||
@@ -120,8 +120,9 @@ const SHELL_FLIGHT = 3, SHELL_RADIUS = 1.2;
|
||||
/** 假想敌:黑云 抓取: the blackboard radius counts ×2.5 (PRTS "2.5倍可变半径": range_radius 1.5 → 3.75), at most 3 prey,
|
||||
* "短暂延迟后" the 延迟吞噬 lands (delay [ASSUMED] 0.5 s); its SP (= 全弹发射 hits) caps at the data's spData.maxSp. */
|
||||
const GRAB_RADIUS_SCALE = 2.5, GRAB_MAX_PREY = 3, GRAB_DELAY = 0.5;
|
||||
/** 源石污染区 aura radius of an activated 奇美拉 [ASSUMED]. */
|
||||
const CHIMERA_AURA_RADIUS = 1;
|
||||
/** 孽罪奇美拉 污染模式 aura (PRTS 天赋 "自身半径1.2范围内的所有单位…每0.5秒受到50真实持续伤害(同类效果取最高)"): radius,
|
||||
* damage period (s). */
|
||||
const CHIMERA_AURA_RADIUS = 1.2, CHIMERA_AURA_EVERY = 0.5;
|
||||
/** Bombardments per 自行炮 Cannon cast ("数次") [ASSUMED]. */
|
||||
const CANNON_SHOTS = 3;
|
||||
/** 枯朽之种 summoned per BornBugs cast ("数个") [ASSUMED]. */
|
||||
@@ -1074,7 +1075,9 @@ function kitDeepsea(ab, { swim = false, drown = false }) {
|
||||
if (!wet && a.wet) b.removeBuff(e, 'ab:swim');
|
||||
if (wet) b.removeBuff(e, 'terrain:deepsea'); // 免疫水蚀: drop devices.js' deep-water debuff
|
||||
}
|
||||
if (drown && wet) { const v = T(ab, 'Drown.damage') ?? 0; if (v > 0) b.loseHp(e, v * dt, { source: null }); }
|
||||
// PRTS 码头水手 "水蚀状态下或处于清澈水域时,每秒受到1000点无来源真实伤害" (伤害分类: 深水区/涨潮水蚀 is BUFF damage):
|
||||
// damage, not a 流失 — 脆弱 scales it (player report D1 audit)
|
||||
if (drown && wet) { const v = T(ab, 'Drown.damage') ?? 0; if (v > 0) b.dealDamage(null, e, { ...periodicDamage(v * dt), tags: ['dot', 'periodic', 'drown'] }); }
|
||||
a.wet = wet;
|
||||
},
|
||||
hitIn(c, b, e, a) { if (swim && a.wet && c.dmg.tags && c.dmg.tags.some((t) => t === 'terrain' || t === 'deepsea' || t === 'drown')) c.dmg.cancel = true; },
|
||||
@@ -1101,11 +1104,16 @@ function kitChimera(ab) {
|
||||
activate(b, e, a);
|
||||
if (!a.on) return;
|
||||
a.acc = (a.acc ?? 0) + 0.25;
|
||||
const pulse = a.acc >= 1 - 1e-9;
|
||||
if (pulse) a.acc -= 1;
|
||||
const pulse = a.acc >= CHIMERA_AURA_EVERY - 1e-9;
|
||||
if (pulse) a.acc -= CHIMERA_AURA_EVERY;
|
||||
for (const u of b.alliesInRadius(e.x, e.y, CHIMERA_AURA_RADIUS)) {
|
||||
auraBuff(b, u, 'ab:originium', 0.25, { spRecoveryMul: Math.max(0, 1 + spr) }, null, true);
|
||||
if (pulse && dmg > 0) b.loseHp(u, dmg, { source: e });
|
||||
// PRTS "持续视为受到源石污染区影响…每0.5秒受到50真实持续伤害": a damage instance (受击回复, the 重装 trigger), not a
|
||||
// 流失 — 无来源 like the 源石污染区 terrain it stands for [ASSUMED], the chimera keeps the credit; "同类效果取最高": one
|
||||
// tick per unit per period however many chimeras reach it (`mem.chimeraAt`; every chimera's is 50)
|
||||
if (!pulse || !(dmg > 0) || b.time - (u.mem.chimeraAt ?? -Infinity) < CHIMERA_AURA_EVERY - 1e-6) continue;
|
||||
u.mem.chimeraAt = b.time;
|
||||
b.dealDamage(e, u, { ...periodicDamage(dmg), tags: ['dot', 'pollution'] });
|
||||
}
|
||||
},
|
||||
}];
|
||||
@@ -2493,7 +2501,7 @@ export const KITS = Object.freeze({
|
||||
enemy_1067_snslime: (ab, e) => [deathBoom({ scale: T(ab, 'boom.atk_scale') ?? 0, r: (e.def.raw.stats && e.def.raw.stats.rawRangeRadius) || BOOM_RADIUS, status: { key: 'cold', dur: T(ab, 'boom.freeze') ?? 0 } })], // 冰爆源石虫 · death: phys blast + cold
|
||||
enemy_1069_icebrk_2: (ab) => [{ hitOut(c, b, e) { if (c.dmg.isAttack && c.target.s.flags.freeze) c.dmg.amount *= T(ab, 'atkup.atk_scale') ?? 1; } }], // 雪怪小队破冰者 · ×3 vs frozen
|
||||
enemy_1026_aghost: () => [unblockable()], // 幽灵组长 · unblockable
|
||||
enemy_1062_rager_2: (ab) => [{ iv: 1, tick(b, e) { b.loseHp(e, T(ab, 'periodic_damage.damage') ?? 0, { source: null }); } }], // 狂暴宿主组长 · loses HP over time
|
||||
enemy_1062_rager_2: (ab) => [{ iv: 1, tick(b, e) { const v = T(ab, 'periodic_damage.damage') ?? 0; if (v > 0) b.dealDamage(null, e, periodicDamage(v)); } }], // 狂暴宿主组长 · "自身每秒受到500无来源真实伤害" (damage, not 流失)
|
||||
enemy_1183_mlasrt: (ab) => [ep('erosion', T(ab, 'EpDamage.attack@ep_damage_ratio') ?? 0), nthAttackPower(nthOf(ab.sk.PowerAttack), (ab.sk.PowerAttack && ab.sk.PowerAttack.bb.atk_scale) || 1)], // 无胄盟清扫小队 · erosion; every 4th attack ×1.5
|
||||
enemy_1273_stmgun_2: (ab) => [skill(ab.sk.Cannon, (b, e) => { // 高准度伦蒂尼姆城防自行炮 · locks the highest max-HP unit, bombards the highest HP% around it
|
||||
const lock = allTargets(b, e).sort((p, q) => q.s.maxHp - p.s.maxHp)[0];
|
||||
@@ -2672,9 +2680,11 @@ export const KITS = Object.freeze({
|
||||
enemy_2085_skzjxd: (ab) => [unblockable(), frontGuard(T(ab, 'Weakness.damage_resistance') ?? 0, faceCrowd)],
|
||||
enemy_2085_skzjxd_2: (ab) => [unblockable(), frontGuard(T(ab, 'Weakness.damage_resistance') ?? 0, faceCrowd)], // (鸭爵 strategy) same
|
||||
// 失衡 (pushed / pulled by operators)
|
||||
enemy_1328_cbjedi: (ab) => [unbalanced((b, e, a, d) => { // 弧光锋卫 · loses HP in proportion to the distance moved while unbalanced
|
||||
enemy_1328_cbjedi: (ab) => [unbalanced((b, e, a, d) => { // 弧光锋卫 · takes damage in proportion to the distance moved while unbalanced
|
||||
// PRTS 修正 "失衡移动时持续受到真实伤害", "处于失衡状态时,每0.066s受到400点无来源真实持续伤害" (伤害分类: 弧光锋卫失衡状态下的自残
|
||||
// 伤害 is BUFF damage): damage, not a 流失 (player report D1 audit)
|
||||
const v = T(ab, 'unbalanced_bleed.damage') ?? 0, iv = T(ab, 'unbalanced_bleed.interval') ?? 1;
|
||||
if (v > 0 && iv > 0) b.loseHp(e, (v * d) / (iv * UNBALANCE_SPEED), { source: null, tags: ['unbalanced'] });
|
||||
if (v > 0 && iv > 0) b.dealDamage(null, e, { ...periodicDamage((v * d) / (iv * UNBALANCE_SPEED)), tags: ['dot', 'periodic', 'unbalanced'] });
|
||||
})],
|
||||
enemy_10112_ymgds: (ab) => [unbalanced((b, e) => { // 冒失的小弟 · stunned after being unbalanced
|
||||
const st = T(ab, 'StunAfterUnbalance.stun') ?? 0;
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
num, itemRecord, itemKeyOf, buffsOf, isOp, onField, unitBonds, isMember, bondActive, isGroundOp, frontTile,
|
||||
alliesAround, passiveBuff, fxOn, battleStore, contentInfo, itemsOf, directMods,
|
||||
} from '../support/index.js';
|
||||
import { mitigate, hasHp } from '../../damage.js';
|
||||
import { mitigate, hasHp, periodicDamage, isHpLoss } from '../../damage.js';
|
||||
|
||||
// =====================================================================================================================
|
||||
// data helpers
|
||||
@@ -56,6 +56,8 @@ function partnerOf(p) {
|
||||
const isProc = (dmg) => !!(dmg && dmg.tags && dmg.tags.indexOf('item') >= 0);
|
||||
const procTags = (k) => ['item', `item:${k}`];
|
||||
const chance = (battle, p) => p > 0 && (p >= 1 || battle.rng() < p);
|
||||
/** An ally built from an enemy record — PRTS's 敌人类我方单位 (炎佑 enemy_9012_acloon). */
|
||||
const ENEMY_RECORD = /^enemy_/;
|
||||
/** 'fatal' priorities (see header). */
|
||||
export const PRIO_REVIVE = -100;
|
||||
const PRIO_FREE_UNDYING = 20;
|
||||
@@ -315,11 +317,27 @@ function onAttackEnemies(S, u, fn) {
|
||||
|
||||
/** Generic buff-key behaviours (shared by several items). */
|
||||
const BY_BUFF = {
|
||||
// 源石溶剂: 每秒流失 damage 点生命值 (HP loss, may kill)
|
||||
// 源石溶剂: the text's "每秒流失 damage 点生命值" is officially 每秒受到 damage 点真实伤害 (PRTS 盟约记录 修正 "并非流失", 备注
|
||||
// "造成无来源真实持续环境伤害"; the `periodic_damage` template — also 狂暴宿主 "自身每秒受到N无来源真实伤害"): a damage instance,
|
||||
// not a 流失 — shields, damage-taken modifiers and the target-side `hit` effects apply, and it is a "受到伤害" for 受击回复
|
||||
// SP, the 重装 TAKE_DAMAGE trigger and 信仰搅拌机 S3's counters (player report D1). 无来源: hooks see no source; the carrier
|
||||
// keeps the credit (a carrier the drain finishes off is its own kill, as before). May kill.
|
||||
// The same 备注: "携带后,全场范围内的所有敌人类我方单位也会获得此装备的“每秒受到60真实伤害”效果(该效果的付与为我方阵营索敌,
|
||||
// 可对空,无视目标可选性)" — every enemy-record unit on our side of the whole field (炎佑 enemy_9012_acloon, a partner's
|
||||
// too; flying, 孤立) takes the same tick, credited to nobody, while a carrier is on the field [ASSUMED: it ends with its
|
||||
// carriers]; one effect per unit however many carriers (PRTS 作战机制 "同名buff的默认叠加策略buff只能表现出一个").
|
||||
periodic_damage(battle, u, p, S) {
|
||||
const d = num(p.damage);
|
||||
if (!(d > 0)) return;
|
||||
S.stat('drain', null, { interval: 1, onTick: ({ unit }) => { if (unit.deployed) battle.loseHp(unit, d, { source: unit }); } });
|
||||
S.stat('drain', null, { interval: 1, onTick: ({ unit }) => {
|
||||
if (!unit.deployed) return;
|
||||
battle.dealDamage(unit, unit, periodicDamage(d));
|
||||
for (const a of battle.allies()) {
|
||||
if (a === unit || !ENEMY_RECORD.test(a.defId ?? '') || battle.time - (a.mem.solventAt ?? -Infinity) < 1 - 1e-6) continue;
|
||||
a.mem.solventAt = battle.time;
|
||||
battle.dealDamage(null, a, periodicDamage(d));
|
||||
}
|
||||
} });
|
||||
},
|
||||
// 奥术法阵: 攻击使目标失去特殊能力 silence s
|
||||
silence_attachment(battle, u, p, S) {
|
||||
@@ -439,14 +457,14 @@ const BY_ITEM = {
|
||||
S.buff(u, { key, mods, refresh: 'stack', stacks: 1, maxStacks: cap, persist: true, allowDead: true });
|
||||
});
|
||||
},
|
||||
// 伪装服: first damage taken in the battle ⇒ 隐匿 `duration` s
|
||||
// 伪装服: first damage taken in the battle ⇒ 隐匿 `duration` s (a 流失 is not 受到伤害: damage.js isHpLoss)
|
||||
chess_item_4_04_e(battle, u, rec, S) {
|
||||
const p = bp(rec, 'act2autochess_equip_acarm055_global_buff');
|
||||
const d = p ? num(p.duration) : 0;
|
||||
if (!(d > 0)) return;
|
||||
let used = false;
|
||||
S.on('damaged', (c) => {
|
||||
if (used || c.target !== u || !(c.amount > 0) || !u.alive) return;
|
||||
if (used || c.target !== u || !(c.amount > 0) || !u.alive || isHpLoss(c.dmg)) return;
|
||||
used = true;
|
||||
battle.applyStatus(u, 'stealth', { duration: d, source: u });
|
||||
fxOn(battle, 'camouflage', u, 'item:chess_item_4_04_e', rec.id, { duration: d });
|
||||
|
||||
@@ -61,6 +61,11 @@ export const cheb = (a, b) => (b.hitArea ? bodyTileReach(b, Math.round(a.y), Mat
|
||||
export const isMainHit = (dmg) => !!dmg && dmg.isAttack && !dmg.isSplash && !(dmg.tags && dmg.tags.includes('chain'));
|
||||
/** Damage ctx caused by an enemy's attack. */
|
||||
export const byEnemyAttack = (ctx) => !!ctx.source && ctx.source.side === 'enemy' && !!ctx.dmg && ctx.dmg.isAttack;
|
||||
/**
|
||||
* `damaged` ctx of a damage that removed HP and can give 受击回复 SP — the engine's rule (damage.js applyHpLoss: not a 流失
|
||||
* (`noSp`, Battle.loseHp), not an element 损伤), whatever its source: an attack, a zone, the 无来源 源石溶剂 tick.
|
||||
*/
|
||||
export const hurtSpDamage = (ctx) => !!ctx.dmg && !ctx.dmg.noSp && ctx.type !== 'element' && ctx.amount > 0;
|
||||
/** A timed skill is running (no SP may be gained). */
|
||||
export const skillBusy = (u) => !!(u.skill && u.skill.active && u.skill.isTimed);
|
||||
/** Give SP unless a timed skill is running (AK: no SP gain during a skill). */
|
||||
@@ -1039,8 +1044,10 @@ export default {
|
||||
talents: [{ install(battle, unit) {
|
||||
const sp = num(t.sp);
|
||||
if (sp > 0) {
|
||||
// PRTS 备注 "仅伤害量不为0且能够触发受击回复的伤害才能触发此天赋": any such damage, not only an enemy attack
|
||||
// (a zone tick, the 源石溶剂 drain — player report D1 audit)
|
||||
onDamagedOn(battle, unit, (ctx) => {
|
||||
if (!byEnemyAttack(ctx) || !up(unit)) return;
|
||||
if (!hurtSpDamage(ctx) || !up(unit)) return;
|
||||
giveSp(unit, sp);
|
||||
const mates = alliesInGridOf(battle, unit, grid ?? [[1, 0], [0, -1], [0, 1], [-1, 0]]).filter((a) => a !== unit && a.skill && !a.skill.noSkill);
|
||||
const m = battle.rng.pick(mates);
|
||||
|
||||
@@ -592,11 +592,13 @@ 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.
|
||||
// 2_13 蒂比 紧急赶场通知 (AUTO): "受到攻击后触发" is officially "受到伤害前触发" (PRTS 修正 原因 6; 备注 "在受到伤害前自动
|
||||
// 触发技能…若本次伤害为物理或法术,再将本次伤害闪避") — any incoming damage instance (an attack, a zone tick, the 源石溶剂
|
||||
// drain; never a 流失, which has no `hit`) sets it off (the kit is the only trigger: the engine rule is disabled) 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. 片场工作指南
|
||||
// ("若最近N秒内未受伤害" — 修正 原文 攻击): if no damage for stack_time s, the next physical/arts damage is dodged (prob);
|
||||
// every damage instance restarts that timer (dodged or not; a 流失 does not — PRTS 备注).
|
||||
// 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.
|
||||
chess_char_2_13_a: (bb, chess, def) => {
|
||||
@@ -627,7 +629,7 @@ export default {
|
||||
battle.on('deploy', ({ unit: u }) => { if (u === unit) last = battle.time - st; }, { owner: unit });
|
||||
onHitOn(battle, unit, (ctx) => {
|
||||
const { dmg } = ctx;
|
||||
if (!byEnemyAttack(ctx) || dmg.cancel || !up(unit)) return;
|
||||
if (!dmg || dmg.cancel || !up(unit)) return;
|
||||
const dodgeable = dmg.type === 'phys' || dmg.type === 'arts';
|
||||
const sk = unit.skill;
|
||||
let dodged = false;
|
||||
|
||||
@@ -31,6 +31,7 @@ import { absoluteRangeKeys, canTargetEnemy, sortEnemyTargets } from '../../targe
|
||||
import { normalizeChess } from '../../simdata.js';
|
||||
import { aggregateMods } from '../../buffs.js';
|
||||
import { CAT_SHIELD_KEY } from '../tokens.js';
|
||||
import { isHpLoss } from '../../damage.js';
|
||||
|
||||
const TICK_EPS = 0.01; // minimal status duration (s)
|
||||
const AURA = 0.2; // aura refresh period (s)
|
||||
@@ -286,18 +287,27 @@ const kits = {
|
||||
// module SPT-X (elite default): 攻击范围内敌人的隐匿效果失效
|
||||
if (moduleIs(def, 'uniequip_002_rmixer')) whileDeployed(battle, unit, AURA, () => reveal(battle, enemiesOnRange(battle, unit)));
|
||||
if (!S3) return;
|
||||
// S3 counter: when hit, fire one volley at ≤ 3 enemies in range (min interval = interval × ratio)
|
||||
// S3 counter (PRTS 备注 "反击受到任何伤害后均可触发,无需目标,视为普通攻击,受各类无法触发普通攻击效果的影响"): ANY damage
|
||||
// she takes — an enemy's attack, a zone, the 无来源 源石溶剂 tick (items/battle.js periodic_damage) — fires one volley at
|
||||
// ≤ 3 enemies in range (the attacker first), at most once per actual interval × ratio. With no enemy in range the
|
||||
// counter still happens and spends its bullet (无需目标: the drain alone empties the skill — 莫斯提马's 特质 turns those
|
||||
// bullets into 拉特兰 layers, player report D1). A 流失 (Battle.loseHp, tag 'hpLoss': it skips every damage event —
|
||||
// PRTS 作战机制) and an element 损伤 never counter; stunned / disarmed: no counter
|
||||
battle.on('damaged', (c) => {
|
||||
if (c.target !== unit || !unit.canAct || !skillActive(unit) || !c.source || c.source.side !== 'enemy' || !c.dmg?.isAttack) return;
|
||||
const d = c.dmg;
|
||||
if (c.target !== unit || !unit.canAct || unit.s.flags.disarm || !skillActive(unit) || !d || c.type === 'element') return;
|
||||
if (isHpLoss(d)) return;
|
||||
if (battle.time < (unit.mem.counterReady ?? -Infinity) - 1e-9) return;
|
||||
const list = targetsInRange(battle, unit);
|
||||
const i = list.indexOf(c.source);
|
||||
const i = c.source ? list.indexOf(c.source) : -1;
|
||||
if (i > 0) { list.splice(i, 1); list.unshift(c.source); }
|
||||
const targets = list.slice(0, counterMax);
|
||||
if (!targets.length) return;
|
||||
unit.mem.counterReady = battle.time + unit.s.interval * counterRatio;
|
||||
unit.mem.inCounter = true;
|
||||
try { battle.forceAttack(unit, targets); } finally { unit.mem.inCounter = false; }
|
||||
try {
|
||||
if (targets.length) battle.forceAttack(unit, targets);
|
||||
else { unit.stats.attacks++; unit.skill.onAttackPerformed([], true); } // a counter into nothing: its bullet goes
|
||||
} finally { unit.mem.inCounter = false; }
|
||||
battle.fx('counter', { x: unit.x, y: unit.y, id: unit.id, n: targets.length });
|
||||
}, { owner: unit });
|
||||
},
|
||||
@@ -1151,7 +1161,7 @@ const kits = {
|
||||
install(battle, unit) {
|
||||
battle.on('damaged', (c) => {
|
||||
const t = c.target;
|
||||
if (t.side !== 'ally' || !t.alive || !(c.amount > 0) || !t.findBuff(`reckpr:guard:${unit.id}`)) return;
|
||||
if (t.side !== 'ally' || !t.alive || !(c.amount > 0) || isHpLoss(c.dmg) || !t.findBuff(`reckpr:guard:${unit.id}`)) return;
|
||||
battle.heal(unit, t, num(bb['attack@fixed_heal_value'], 80));
|
||||
}, { owner: unit });
|
||||
installLowHpHealBonus(battle, unit, tb);
|
||||
@@ -1544,7 +1554,7 @@ const kits = {
|
||||
onStart({ battle, unit }) { battle.fx('featherArrow', { x: unit.x, y: unit.y, id: unit.id }); } },
|
||||
talents: [
|
||||
{ install(battle, unit) { // 凝神: ATK +15 % when not hurt for 10 s
|
||||
battle.on('damaged', (c) => { if (c.target === unit && c.amount > 0) unit.mem.lastHurt = battle.time; }, { owner: unit });
|
||||
battle.on('damaged', (c) => { if (c.target === unit && c.amount > 0 && !isHpLoss(c.dmg)) unit.mem.lastHurt = battle.time; }, { owner: unit }); // (a 流失 is not 受伤害)
|
||||
battle.on('deploy', (c) => { if (c.unit === unit) unit.mem.lastHurt = -Infinity; }, { owner: unit });
|
||||
whileDeployed(battle, unit, 0.1, () => toggleBuff(battle, unit, 'fartth:focus', battle.time - (unit.mem.lastHurt ?? -Infinity) >= num(t0.delay, 10) - 1e-9, { atkPct: num(t0.atk, 0.15) }));
|
||||
} },
|
||||
|
||||
@@ -35,7 +35,7 @@ import { COLS } from '../../constants.js';
|
||||
import { bodyInKeys, bodyInRadius, bodyKeys } from '../../body.js';
|
||||
import { absoluteRangeKeys, sortEnemyTargets } from '../../targeting.js';
|
||||
import { frontOf, rotateOffset, toLocal } from '../../dir.js';
|
||||
import { mitigate, hasHp } from '../../damage.js';
|
||||
import { mitigate, hasHp, isHpLoss } from '../../damage.js';
|
||||
|
||||
// ---- text-only constants (the official blackboards carry no key for these) --------------------------------------
|
||||
/** 华法琳 S1 "只当目标生命值不满一半时才会触发"; 塞雷娅 S1 "血量小于等于一半"; 山 module "生命值高于50%时". */
|
||||
@@ -834,7 +834,7 @@ const KITS = {
|
||||
talents: [
|
||||
{ install(battle, unit) { // 本性的坚守
|
||||
battle.on('damaged', (c) => {
|
||||
if (c.target !== unit || !on(unit) || unit.hp <= 0 || !(c.amount > 0) || c.type === 'element') return;
|
||||
if (c.target !== unit || !on(unit) || unit.hp <= 0 || !(c.amount > 0) || c.type === 'element' || isHpLoss(c.dmg)) return; // (not a 流失)
|
||||
const v = (unit.hpRatio < num(t0.hp_ratio, 0.5) ? num(t0.value2) : num(t0.value1)) * (unit.skill?.active ? t0Scale : 1);
|
||||
if (v > 0) battle.heal(unit, unit, v, { self: true });
|
||||
}, { owner: unit });
|
||||
@@ -2496,7 +2496,7 @@ const KITS = {
|
||||
install(battle, unit) {
|
||||
battle.on('damaged', (c) => {
|
||||
const a = c.target;
|
||||
if (a.side !== 'ally' || !(c.amount > 0) || a.hp <= 0 || c.type === 'element') return;
|
||||
if (a.side !== 'ally' || !(c.amount > 0) || a.hp <= 0 || c.type === 'element' || isHpLoss(c.dmg)) return; // (not a 流失)
|
||||
const b = a.findBuff('reckpr:guard');
|
||||
if (b && b.data.src === unit) battle.heal(unit, a, num(b.data.value));
|
||||
}, { owner: unit });
|
||||
|
||||
+26
-1
@@ -6,6 +6,8 @@
|
||||
// → 限伤 (leaders in boss / hidden battles: a hit of ceil(final) ≥ BOSS_HIT_LIMIT is cancelled, see leaderHitCancelled)
|
||||
// → shields (hit-negating barriers first, then HP shields) → HP loss (boss pool routing) → 'damaged' hook
|
||||
// → SP-on-hurt / TAKE_DAMAGE trigger → fatal/kill.
|
||||
// Damage-dealt stats (the source's `stats.dmg`, the player's `damageDealt`) count only HP removed from the other side:
|
||||
// self and friendly damage (a 源石溶剂 drain, an operator's own 流失) is the target's `taken` and keeps the kill credit.
|
||||
// Phys: max(A − max(0, D×(1−defIgnorePct) − defIgnoreFlat), 5 %·A); Arts: max(A×(1 − R′/100), 5 %·A) with
|
||||
// R′ = max(0, R×(1−resIgnorePct) − resIgnoreFlat); True: A; Elemental (元素伤害): max(A×(1 − 元素抗性/100), 5 %·A)
|
||||
// (PRTS 游戏数据基础 DMG_e; 元素抗性 = the target's data `epDamageResistance`: 0 on every enemy in data/enemies.json).
|
||||
@@ -87,6 +89,27 @@ export function makeDamageInfo(d = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* DamageInfo of the official `periodic_damage` template — 源石溶剂 (PRTS 盟约记录: "受到60真实伤害", 修正 "并非流失", 备注
|
||||
* "造成无来源真实持续环境伤害"), 狂暴宿主组长 ("自身每秒受到500无来源真实伤害"): 无来源 true 持续 damage (PRTS 伤害分类: attack
|
||||
* type BUFF, "自残类型"). A damage instance, not a 流失 (Battle.loseHp skips every damage event — PRTS 作战机制 "生命流失不会
|
||||
* 触发反伤、受击回复"): shields, damage-taken multipliers and target-side `hit` effects apply, and it is a "受到伤害" for
|
||||
* 受击回复 SP and the 重装 TAKE_DAMAGE trigger. Tagged 'dot' (a fixed-value DoT: ATK multipliers skip it; 锡人's 持续伤害
|
||||
* boost takes it) and 'periodic'. The caller passes the credit (the carrier / null) as the dealDamage source.
|
||||
*/
|
||||
export function periodicDamage(amount) {
|
||||
return makeDamageInfo({ amount, type: 'true', canDodge: false, sourceless: true, tags: ['dot', 'periodic'] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this the DamageInfo of a 流失 (Battle.loseHp, tag 'hpLoss')? Its `damaged` hook still runs (stats, leader parts,
|
||||
* HP thresholds), but a 流失 is not "受到伤害": "受到伤害时" content (heals, counters, 未受伤害 timers, 反伤 chances)
|
||||
* skips it — PRTS 作战机制 "生命流失…跳过所有结算与伤判效果…不会触发反伤、受击回复等受到攻击触发的时点".
|
||||
*/
|
||||
export function isHpLoss(dmg) {
|
||||
return !!dmg && Array.isArray(dmg.tags) && dmg.tags.includes('hpLoss');
|
||||
}
|
||||
|
||||
/** 沉睡 (ba.sleep "无敌且无法行动"): only attackers whose profile has `hitSleep` (or `ignoreSleep` damage) reach a sleeper. */
|
||||
function sleepBlocks(target, source, dmg) {
|
||||
return !!target.s.flags.sleep && !dmg.ignoreSleep && !(source && source.profile && source.profile.hitSleep);
|
||||
@@ -256,7 +279,9 @@ export function applyHpLoss(battle, source, target, amount, dmg) {
|
||||
}
|
||||
dealt = Math.max(0, before - Math.max(0, target.hp));
|
||||
}
|
||||
if (source) {
|
||||
// damage dealt (unit stats, the results screen's 造成伤害) never counts a unit's own side — its own drain or 流失 (源石溶剂,
|
||||
// 史尔特尔 S3 …), friendly damage; `taken` and the kill credit do
|
||||
if (source && source.side !== target.side) {
|
||||
source.stats.dmg += dealt;
|
||||
if (source.side === 'ally' && source.ownerId != null) { const pp = battle._pp(source.ownerId); if (pp) pp.damageDealt += dealt; }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user