Files
ark-stronghold-protocol/server/sim/battle/combat.js
T

60 lines
3.4 KiB
JavaScript

// server/sim/battle/combat.js — Battle methods: damage, heal and HP loss (the guarded entry points of the damage.js
// pipeline), projectiles and forced attacks (ai.js performAttack).
// Installed on Battle.prototype by server/sim/Battle.js (a method container: never instantiated; `this` is the battle).
import { dealDamage as pipeDamage, heal as pipeHeal, applyHpLoss, makeDamageInfo, reduceElement, leaderHitCancelled } from '../damage.js';
import { effectiveProfile, performAttack, acquireTargets } from '../ai.js';
export class BattleCombat {
dealDamage(source, target, dmg) {
try { return pipeDamage(this, source, target, dmg); } catch (e) { this._internalError('dealDamage', e); return 0; }
}
heal(source, target, amount, opts = {}) {
try { return pipeHeal(this, source, target, amount, opts); } catch (e) { this._internalError('heal', e); return 0; }
}
/**
* HP loss that ignores DEF/RES, dodge and shields (流失). May kill. opts: { source, silent, tags, from } — `from` = the
* DamageInfo this loss derives from (damage passed on to a leader, split, shared…): its tags are inherited and it is
* kept as `dmg.origin`, so `damaged` handlers that skip their own tagged damage also skip what it turned into; a loss
* derived from 无来源 damage (`from.sourceless`, element bursts) is 无来源 too (hooks see no source, `source` is credited).
* `sourceless: true` makes the loss itself 无来源 ("受到等量的无来源生命流失": hooks see no source; `source` keeps the
* credit — the stats and the per-player shared-pool tally).
* On a leader in a boss / hidden battle a loss of ≥ BOSS_HIT_LIMIT is cancelled like a hit (damage.js leaderHitCancelled).
*/
loseHp(target, amount, { source = null, silent = false, tags = null, from = null, sourceless = false } = {}) {
if (!target || !target.alive || !(amount > 0)) return 0;
// 限伤 (shared/constants.js BOSS_HIT_LIMIT): a loss passed on to a leader (parts' 传递, 无人机) is one hit too
if (leaderHitCancelled(this, target, amount)) return 0;
const t = ['hpLoss'];
for (const list of [from && from.tags, tags]) if (Array.isArray(list)) for (const x of list) if (!t.includes(x)) t.push(x);
return applyHpLoss(this, source, target, amount, { type: 'true', tags: t, noSp: true, silent, origin: from ?? null, sourceless: !!sourceless || !!(from && from.sourceless) });
}
reduceElement(target, amount, element = null) { return reduceElement(target, amount, element); }
makeDamage(d) { return makeDamageInfo(d); }
addProjectile(p) { return this.projectiles.add(p); }
// exposed for ai/content convenience
effectiveProfile(u) { return effectiveProfile(u); }
/**
* Perform an immediate attack with a unit's current profile (content: "立即攻击", extra attacks, counters). It is an
* attack in every respect (hooks, attack SP, a running ammo skill's bullet) — except with `noAmmo: true`: an extra
* attack that spends no ammo (no `ammoUsed`, the skill never ends on it; 圣约送葬人 "不额外消耗弹药").
* Returns true when an attack was made.
*/
forceAttack(u, targets = null, { noAmmo = false } = {}) {
if (!u || !u.alive || !u.profile) return false;
const prof = effectiveProfile(u);
const t = targets ?? acquireTargets(this, u, prof);
if (!t || !t.length) return false;
const n0 = u.stats.attacks;
performAttack(this, u, prof, t, noAmmo ? { noAmmo: true } : null);
return u.stats.attacks > n0;
}
}