feedback5 WE1: 秘术师 stores its energy at the attack check, not one interval later (GitHub #181)

PRTS 分支特性信息 秘术师: "能量储存与攻击占用相同的攻击间隔:在进行攻击判定时…若无有效目标且能量储存数未满,则改为储存一份
攻击能量(属于攻击行为);仅没有有效目标,且能量储存数已满的情况下才进入待机状态", "攻击被打断且弹道未能成功生成的情况下,储存的
能量不会被消耗"; PRTS 异常效果 缴械 "秘术师储存能量同样无法进行".

- ai.js updateAlly: a ready unit (able to act, not disarmed) whose attack check finds no valid target — no target, or
  its profile's canAttack false (a 秘术师 kit's target rule: 深靛's bound enemies, 维伊's marked ones) — calls the
  profile's storeEnergy; a stored energy restarts the attack interval, a full store idles with the attack ready. A
  DEFAULT cast at the check that leaves no target stores in the same check.
- professions.js mystic: storeEnergy (up to bb times), releaseEnergy (performAttack, after beforeAttack: a cancelled
  shot keeps them) and hitsFn(b, u, info) — the energies leave with the attack and land with its main hit; the old
  separate store timer (it started only once the attack was ready and never restarted the interval) is gone.
  [ASSUMED] a redeployment holds no energy.
- 维伊 / 黑键 kits: their own tick stores become the kit's storeEnergy on the shared check; 深靛 / 维伊 no longer write
  hadTarget themselves.

Golden: roster-003, roster-021, roster-022, bond-preciShip-high (深靛 stores at her check); matches solo-FUNNY-1,
solo-NORMAL-2, solo-HARD-2, solo-ABYSS-1, solo-ABYSS-2, coop2-NORMAL-3, coop2-HARD-4, coop4-FUNNY-6, coop4-ABYSS-7,
coop2-NORMAL-8-serverrun, solo-HARD-9-boosted, coop2-ABYSS-10-boosted, coop2-NORMAL-14-standins (each fields 深靛);
diy-032 (维伊) and diy-114 (黑键): the first store after a redeployment comes at the next tick's attack check instead of
the end of the redeploy tick (with the old tick store added back as a fallback the diy family matches the old digests).
fields, standins unchanged.

Tests: test/sim/feedback5-mystic-store.test.js (new, 4); op_veen, op_ebnhlz, kits_t1t2, kits_alt_t1 pass unchanged.
Docs: SIM.md §Professions (mystic row, profile hooks).
This commit is contained in:
sganggs committed 2026-10-06 14:53:56 +08:00
1 parent a009d77d71
commit 8f62f3e360
11 files changed
+354 -214

No files matched your search

+14 -17
View File
@@ -12,8 +12,8 @@
// - Trait (秘术师) "攻击造成法术伤害,在找不到攻击目标时可以将攻击能量储存起来之后一齐发射(最多3个)" (bb times; MSC-X 4):
// ranged arts bolts, hits air units (分支特性信息 "可对空"), blocks 1, ground enemies target him. The store (分支特性信息:
// "能量储存与攻击占用相同的攻击间隔:…若无有效目标且能量储存数未满,则改为储存一份攻击能量(属于攻击行为)"): when his attack is
// ready and he has no valid target he stores one energy and his attack interval starts again — this kit's own store,
// as 维伊's (op-veen.js; the engine's mystic profile stores one interval after the attack is ready). His next attack
// ready and he has no valid target he stores one energy and his attack interval starts again — the shared 秘术师
// profile (professions.js installMystic) with this kit's `storeEnergy` (his elite energy, S3), as 维伊's. His next attack
// releases them at its target ("一齐发射"), each hitting for ATK × the attack's scale × the energy scale (talent 1) as
// an arts 普通伤害 attack hit ("由储存能量形成的弹道造成攻击力100%的法术普通伤害"); they land with the main bolt (one
// volley to one target [ASSUMED], as 维伊's), the later ones only on a target still alive. [ASSUMED] a redeployment holds
@@ -60,9 +60,8 @@
// only elite ones, and at its end at most times_2 stay elite, the rest normal (up to times). "可主动关闭" is a manual
// operation (no automatic stop).
import { num, talentBb, moduleBb, traitBb, skillRec, batMod, up, toggleBuff } from '../shared/tier1.js';
import { num, talentBb, moduleBb, traitBb, skillRec, batMod, toggleBuff } from '../shared/tier1.js';
import { canTargetEnemy, sortEnemyTargets } from '../../../targeting.js';
import { acquireTargets, effectiveProfile } from '../../../ai.js';
import { hasHp } from '../../../damage.js';
import { COLS } from '../../../constants.js';
@@ -204,21 +203,19 @@ export default {
trait: {
hitsFn: () => 1, // the engine's main hit only: the stored energies are this kit's (land)
onEachHit(battle, unit, victim, hc) { if (hc.kind === 'main' && victim && victim.side === 'enemy') land(battle, unit, victim, hc); },
// the store (the shared profile calls it at his attack check with no valid target — professions.js installMystic:
// an attack action, the interval restarts): the trait's energies first, then the elite one; during S3 elite ones
// only (false = a full store: he idles)
storeEnergy(battle, unit) {
const v = stateOf(unit);
if (skillOn(unit, S3)) { if (v.n + v.e >= N + E) return false; v.e++; }
else if (v.n < N) v.n++;
else if (v.e < E) v.e++;
else return false;
return true;
},
install(battle, unit) {
battle.on('deploy', (ctx) => { if (ctx.unit === unit && !ctx.move) unit.trait.ebn = null; }, { owner: unit });
// the store: his attack ready and no valid target ⇒ one energy, an attack action (the interval restarts); the
// attack loop ran first this tick (a valid target was attacked instead)
battle.on('tick', () => {
if (!up(unit) || !unit.canAct || unit.s.flags.disarm || unit.atkCd > 0) return;
const prof = effectiveProfile(unit);
if (prof.noAttack || acquireTargets(battle, unit, prof).length) return;
const v = stateOf(unit);
if (skillOn(unit, S3)) { if (v.n + v.e >= N + E) return; v.e++; }
else if (v.n < N) v.n++;
else if (v.e < E) v.e++;
else return;
unit.atkCd = unit.s.interval;
}, { owner: unit });
battle.on('death', (ctx) => { if (ctx.unit && ctx.unit.side === 'enemy') stateOf(unit).q.delete(ctx.unit.id); }, { owner: unit });
},
},