feedback1 integrate: WL 源石溶剂 drain is 无来源 true damage; 信仰搅拌机 S3 counters any damage

# Conflicts:
#	data/items.json
This commit is contained in:
sganggs committed 2026-10-03 06:52:36 +08:00
commit e228801bee
16 files changed
+637 -47

No files matched your search

+8 -1
View File
@@ -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);
+8 -6
View File
@@ -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;
+17 -7
View File
@@ -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) }));
} },
+3 -3
View File
@@ -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 });