fix: 补回驭械术师普通攻击的本体伤害

This commit is contained in:
星闌 committed 2026-10-11 16:42:50 +08:00
1 parent bc50cac01f
commit 823e3d0af4
21 files changed
+1011 -710

No files matched your search

+15 -1
View File
@@ -307,13 +307,27 @@ export function resolveHit(b, u, prof, target, info, x, y) {
if (target && target.alive) {
let mulT = skillMul;
if (prof.dmgMul) { const m = typeof prof.dmgMul === 'function' ? prof.dmgMul(b, u, target) : prof.dmgMul; if (Number.isFinite(m)) mulT *= m; }
const funnelMul = prof.funnelMul ? prof.funnelMul(b, u, target) : 1;
const attachedFunnel = !!prof.funnelMul && (!info.isSkill || prof.funnelBodyHits > 0);
// Unreleased drones are additional damage (PRTS 驭械术师), not a replacement for the caster's attack. A volley
// shares one talent roll and attackId; extra drones never duplicate the body. Released skill paths stay separate.
// [ASSUMED] Body instances precede additions at the existing projectile impact; a multi-hit volley keeps one ramp.
if (!attachedFunnel) mulT *= funnelMul;
const hits = prof.hitsFn ? prof.hitsFn(b, u, info) : Math.max(1, prof.hits || 1);
// `hitDmgMul` (professions.js header): each instance's 伤害倍率 — the DamageInfo `mul`, after DEF / RES — and only the
// first instance may give 受击回复 (砾's two 50 % hits, PRTS 砾 特性备注)
const split = Number.isFinite(prof.hitDmgMul) && prof.hitDmgMul > 0 ? prof.hitDmgMul : null;
let dealtMain = 0;
if (attachedFunnel && info.index === 0) {
const bodyHits = Math.max(1, prof.funnelBodyHits || 1);
for (let h = 0; h < bodyHits && target.alive; h++) {
dealtMain += b.dealDamage(u, target, { amount: atk * scale * mulT, type: baseType,
isAttack: true, isSkill: info.isSkill, tags: prof.tags || [], attackId });
}
}
for (let h = 0; h < hits && target.alive; h++) {
const d = { amount: atk * scale * mulT, type: baseType, isAttack: true, isSkill: info.isSkill, tags: prof.tags || [], attackId };
const d = { amount: atk * scale * mulT * (attachedFunnel ? funnelMul : 1), type: baseType,
isAttack: !attachedFunnel, isSkill: info.isSkill, tags: attachedFunnel ? [...(prof.tags || []), 'addition', 'funnel'] : (prof.tags || []), attackId };
if (split != null) { d.mul = split; if (h > 0) d.noSp = true; }
dealtMain += b.dealDamage(u, target, d);
}
@@ -12,10 +12,10 @@ export default {
const hits = textNum(d.skill?.description, /连续攻击(\d+|[一二两三四五])次/, 2);
const insight = (battle) => (battle.rng.chance(num(t0.prob, 0)) ? num(t0.atk_scale, 1) : 1);
return {
trait: { dmgMul: (battle, unit, target) => funnelMap(battle, unit, target) * insight(battle) },
trait: { funnelMul: funnelMap, dmgMul: insight },
skill: {
kind: charges > 1 ? 'charges' : 'instant',
attack: { atkScale: num(bb.atk_scale, 1), hits, dmgType: 'arts', dmgMul: (battle) => insight(battle) },
attack: { atkScale: num(bb.atk_scale, 1), hits, funnelBodyHits: hits, dmgType: 'arts', dmgMul: insight },
},
// S1 迅捷打击·γ型: ATK / ASPD + (the drones' ramp and 忽有所悟 stay on every attack through the trait)
skills: altSkills(chess, d, bb, { 'skcom_quickattack[3]': statSkill }),
@@ -13,7 +13,7 @@ export default {
const extra = Math.floor(num(bb['attack@cnt'], 0));
const prob = num(bb['attack@prob'], 0), cold = num(bb['attack@cold'], 0);
const kit = {
trait: { dmgMul: funnelMap },
trait: { funnelMul: funnelMap },
skill: {
kind: 'duration',
mods: { atkPct: num(bb.atk) },
@@ -20,7 +20,7 @@ export default withDefaults({
attack: {
// every drone ramps on its own locked target (trait init/delta/max): the multipliers are queued per target
// by the beforeAttack hook below (two drones on the same enemy ramp independently)
dmgMul: (battle, unit, target) => {
funnelMul: (battle, unit, target) => {
const q = unit.mem.kjeraQueue && unit.mem.kjeraQueue.get(target.id);
return q && q.length ? q.shift() : funnel(unit).init;
},
@@ -143,7 +143,7 @@ function whitw2(bb, chess, def) {
...(skillGridW ? { targeting: { rangeGrid: skillGridW } } : {}),
attack: {
hits: 1,
dmgMul(battle, unit, target) {
funnelMul(battle, unit, target) {
const f = unit.profile?.funnel || { init: 0.2, delta: 0.15, max: 1.1 };
const m = unit.mem.hunt;
if (!m) return f.init;
+1 -1
View File
@@ -17,7 +17,7 @@
// projectile_chr_gdglow_funnel_s1_1 … s3_3 (`_lifeTimeType` 2: until stopped, `_stopWhenSourceInvalid`, its crash
// AdvancedApplyDamage MAGICAL at atk_scale_2) and their s12 / s3 blast projectiles.
// - Trait (驭械术师) "操作浮游单元造成法术伤害;单元攻击同一敌人伤害提升(最高造成干员110%攻击力的伤害)" (trait bb init / delta /
// max_atk_scale): the engine's funnel profile (professions.js: her normal attack = one drone hit at init → +delta per hit on
// max_atk_scale): the engine's funnel profile (professions.js: her normal attack = a body hit plus one drone addition at init → +delta per hit on
// the same target → max). FUN-X: init 0.35 ("单元初始伤害提高"); FUN-Y: max 1.2. Ranged arts, 3-1, hits air (PRTS "可对空"),
// blocks 1, ground enemies target her.
// - Skill drones (the three skills "浮游单元+N,释放浮游单元锁定敌人攻击 … 锁敌后直至敌人被击杀、自爆或技能结束后返回干员身边"): while a
+3 -1
View File
@@ -21,6 +21,8 @@
// noAttack bool (never attacks) noAttackUnlessSkill bool (attacks only while its skill is active)
// noHeal bool (cannot be healed by others) blockFly bool onHitStatus {key, duration, value}
// dmgMul(battle, unit, target) → number afterHit(battle, unit, target, {dealt,x,y})
// funnelMul(battle, unit, target) → number (drone ramp, separate from the caster's attack multiplier)
// funnelBodyHits n (an unreleased skill attack's body hit count; default normal attack: one, drones: `hits`)
// canAttack(battle, unit) → bool afterAttack(battle, unit, targets)
// skipEnemy(enemy) → bool (an enemy the unit never selects — targeting.js canTargetEnemy; 嵯峨 "不攻击重伤单位")
// healThrough(healer, ally) → bool (a healer that selects and heals that ally through its 禁疗 — Battle
@@ -508,7 +510,7 @@ export const SUB = Object.freeze({
blastcaster: P({ rangeAoe: true }),
chain: P({ chain: { count: 3, falloff: 0.15, radius: CHAIN_RADIUS, sluggish: 0.5 } }),
funnel: P({ projectile: 'drone', install: installFunnel,
dmgMul: (battle, unit, target) => {
funnelMul: (battle, unit, target) => {
const f = unit.profile.funnel || { init: 0.2, delta: 0.15, max: 1.1 };
if (unit.trait.funnelTarget === target.id) unit.trait.funnelScale = Math.min(f.max, (unit.trait.funnelScale ?? f.init) + f.delta);
else { unit.trait.funnelTarget = target.id; unit.trait.funnelScale = f.init; }