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
@@ -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 });
|
||||
|
||||
Reference in new issue
Block a user