Deploy-timed skills run the normal duration lifecycle: one charge on deployment, a duration bar, a real end, again on the next deployment (#109)
宴 S2, 斯卡蒂 S2, 野鬃 S1, 砾 S1 / S2, 伊内丝 S3, 缄默德克萨斯 S1–S3 and 耀骑士临光 S2 used a separate timed buff while the skill stayed active; equipment and bond effects that read the skill state now see it end. 伊内丝's first sentry placement starts no skill. Contributed by @Sukvii.
This commit is contained in:
1 parent
ede8e7e773
commit
ffdb1293c4
22 files changed
+607
-104
No files matched your search
@@ -901,7 +901,6 @@ export default {
|
||||
// hp_recovery_per_sec_by_max_hp_ratio × max HP per second (the 生命回复速度 attribute: works under her 武者 no-heal).
|
||||
chess_char_1_18_a: (bb, chess) => {
|
||||
const t = talentBb(chess, 0);
|
||||
const buffKey = 'utage:s2';
|
||||
const s1 = skillBbOf(chess, 'skchr_utage_1');
|
||||
return {
|
||||
skills: {
|
||||
@@ -913,16 +912,16 @@ export default {
|
||||
},
|
||||
},
|
||||
skill: {
|
||||
kind: 'passive',
|
||||
kind: 'duration', activateOnDeploy: true, duration: num(bb.duration), spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
mods: { atkPct: num(bb.atk) },
|
||||
onStart({ battle, unit }) {
|
||||
const loss = unit.hp * num(bb.hp_ratio);
|
||||
if (loss > 0 && unit.hp - loss >= 1) battle.loseHp(unit, loss, { source: unit });
|
||||
battle.addBuff(unit, { key: buffKey, duration: num(bb.duration), mods: { atkPct: num(bb.atk) }, tags: ['skill'], visible: true });
|
||||
battle.fx('aoe', { x: unit.x, y: unit.y, radius: 1, id: unit.id, skill: 'breach' });
|
||||
},
|
||||
},
|
||||
talents: [{ install(battle, unit) {
|
||||
onHitBy(battle, unit, ({ dmg }) => { if (dmg.isAttack && dmg.type === 'phys' && unit.findBuff(buffKey)) dmg.type = 'arts'; });
|
||||
onHitBy(battle, unit, ({ dmg }) => { if (dmg.isAttack && dmg.type === 'phys' && unit.skill?.id === 'skchr_utage_2' && unit.skill.active) dmg.type = 'arts'; });
|
||||
const maxAs = num(t.min_attack_speed), minHp = num(t.min_hp_ratio);
|
||||
if (maxAs > 0 && minHp < 1) {
|
||||
battle.on('tick', () => {
|
||||
@@ -965,11 +964,8 @@ export default {
|
||||
return {
|
||||
skills: {
|
||||
skchr_wildmn_1: {
|
||||
kind: 'passive',
|
||||
onStart({ battle, unit }) {
|
||||
const d = num(r1?.duration);
|
||||
if (d > 0) battle.addBuff(unit, { key: 'wildmn:s1', duration: d, mods: { aspd: num(r1?.bb?.attack_speed) }, tags: ['skill'], visible: true });
|
||||
},
|
||||
kind: 'duration', activateOnDeploy: true, duration: num(r1?.duration), spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
mods: { aspd: num(r1?.bb?.attack_speed) },
|
||||
},
|
||||
},
|
||||
skill: {
|
||||
|
||||
@@ -547,7 +547,7 @@ export default {
|
||||
const t = talentBb(chess, 0);
|
||||
return {
|
||||
skill: {
|
||||
kind: 'passive',
|
||||
kind: 'duration', activateOnDeploy: true, duration: num(bb.duration, 10), spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
onStart({ battle, unit }) {
|
||||
const total = unit.s.maxHp * num(bb.hp_ratio);
|
||||
const dur = num(bb.duration, 10);
|
||||
@@ -566,10 +566,11 @@ export default {
|
||||
});
|
||||
battle.fx('shield', { x: unit.x, y: unit.y, id: unit.id });
|
||||
},
|
||||
onEnd({ battle, unit }) { battle.removeBuff(unit, 'gravel:rats'); },
|
||||
},
|
||||
skills: {
|
||||
skchr_gravel_1: {
|
||||
kind: 'passive',
|
||||
kind: 'duration', activateOnDeploy: true, duration: num(bb.duration), spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
onStart({ battle, unit }) {
|
||||
const v = num(bb.def), dur = num(bb.duration);
|
||||
if (!(v > 0) || !(dur > 0)) return;
|
||||
@@ -584,6 +585,7 @@ export default {
|
||||
});
|
||||
battle.fx('buff', { x: unit.x, y: unit.y, id: unit.id, kind: 'def' });
|
||||
},
|
||||
onEnd({ battle, unit }) { battle.removeBuff(unit, 'gravel:shadow'); },
|
||||
},
|
||||
},
|
||||
talents: [{ install(battle, unit) {
|
||||
|
||||
@@ -608,10 +608,8 @@ const KITS = {
|
||||
skills: altSkills(chess, d, bb, {
|
||||
'skcom_quickattack[3]': statSkill,
|
||||
skchr_skadi_2: (s) => ({
|
||||
kind: 'passive',
|
||||
onStart({ battle, unit }) { // "部署后N秒内攻击力+X" (a passive starts at every deployment)
|
||||
battle.addBuff(unit, { key: 'skill:skadi_wave', duration: num(s.bb.duration, 20), mods: { atkPct: num(s.bb.atk) }, visible: true });
|
||||
},
|
||||
kind: 'duration', activateOnDeploy: true, duration: num(s.bb.duration, 20), spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
mods: { atkPct: num(s.bb.atk) },
|
||||
}),
|
||||
}),
|
||||
talents: [
|
||||
|
||||
@@ -495,21 +495,9 @@ const kits = {
|
||||
},
|
||||
}),
|
||||
skchr_ines_3: () => ({
|
||||
kind: 'passive',
|
||||
kind: 'duration', duration: s3Dur, spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
onStart({ battle, unit }) {
|
||||
if (!unit.mem.inesS3Placed) {
|
||||
// 首次部署: place a 影哨 (her talent's sentry, left on retreat) and leave; 立刻刷新再部署时间 — the redeploy
|
||||
// (auto, paying her DP cost like every redeploy) follows as soon as it is affordable
|
||||
unit.mem.inesS3Placed = true;
|
||||
battle.after(0, () => {
|
||||
if (!unit.alive || !unit.deployed) return;
|
||||
battle.retreat(unit, { reason: 'retreat' });
|
||||
unit.respawnAt = battle.time;
|
||||
battle.fx('sentry', { x: unit.x, y: unit.y, id: unit.id });
|
||||
}, { owner: unit });
|
||||
return;
|
||||
}
|
||||
battle.addBuff(unit, { key: 'ines:s3', duration: s3Dur, mods: { atkPct: num(bb.atk) }, visible: true });
|
||||
battle.addBuff(unit, { key: 'ines:s3', mods: { atkPct: num(bb.atk) }, visible: true });
|
||||
// 立刻收回影哨: the sentry flies back to her and hits ≤ max_target enemies on its way (within
|
||||
// projectile_range of the segment, [ASSUMED] nearest to its start first)
|
||||
const from = unit.mem.inesSentryAt;
|
||||
@@ -533,6 +521,7 @@ const kits = {
|
||||
}
|
||||
battle.fx('beam', { x: ax, y: ay, tx: bx, ty: by, id: unit.id });
|
||||
},
|
||||
onEnd({ battle, unit }) { battle.removeBuff(unit, 'ines:s3'); },
|
||||
}),
|
||||
}),
|
||||
skill: {
|
||||
@@ -602,11 +591,27 @@ const kits = {
|
||||
],
|
||||
install(battle, unit) {
|
||||
if (S3) {
|
||||
battle.on('deploy', (c) => {
|
||||
if (c.unit !== unit || c.move) return;
|
||||
if (unit.mem.inesS3Placed) {
|
||||
unit.skill.activate('deploy');
|
||||
return;
|
||||
}
|
||||
// 首次部署 only places a 影哨 and retreats; it is not a skill activation.
|
||||
// 立刻刷新再部署时间: redeploy as soon as its DP cost is affordable.
|
||||
unit.mem.inesS3Placed = true;
|
||||
battle.after(0, () => {
|
||||
if (!unit.alive || !unit.deployed) return;
|
||||
battle.retreat(unit, { reason: 'retreat' });
|
||||
unit.respawnAt = battle.time;
|
||||
battle.fx('sentry', { x: unit.x, y: unit.y, id: unit.id });
|
||||
}, { owner: unit });
|
||||
}, { owner: unit });
|
||||
// where the talent's 影哨 stays (every leave but an expiry), for the recall of the next deployment
|
||||
battle.on('death', (c) => { if (c.unit === unit && c.reason !== 'expired') unit.mem.inesSentryAt = { x: unit.x, y: unit.y }; }, { owner: unit, priority: 5 });
|
||||
// 技能期间每对一个敌人造成伤害就获得1点部署费用
|
||||
battle.on('damaged', (c) => {
|
||||
if (c.source !== unit || c.target.side !== 'enemy' || !(c.amount > 0) || c.type === 'element' || !unit.findBuff('ines:s3')) return;
|
||||
if (c.source !== unit || c.target.side !== 'enemy' || !(c.amount > 0) || c.type === 'element' || !unit.skill?.active || !(unit.skill.timeLeft > 0)) return;
|
||||
battle.addDp(unit.ownerId, num(bb.cost, 1));
|
||||
}, { owner: unit });
|
||||
}
|
||||
@@ -1214,17 +1219,12 @@ const kits = {
|
||||
const g = grid(def.skill?.rangeGrid) || [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]];
|
||||
const S1 = isSel(def, 'skchr_texas2_1'), S2 = isSel(def, 'skchr_texas2_2');
|
||||
const dur = num(def.skill?.duration, S1 ? 11 : S2 ? 8 : 6);
|
||||
const talentAtk = (battle, unit) => battle.addBuff(unit, { key: 'texas2:rainAtk', duration: dur, mods: { atkPct: num(t0.atk, 0.2) } });
|
||||
const castS1 = (battle, unit) => {
|
||||
if (!unit.alive || !unit.deployed) return;
|
||||
talentAtk(battle, unit);
|
||||
battle.addBuff(unit, { key: 'texas2:drizzle', duration: dur, mods: { atkPct: num(bb.atk) }, visible: true });
|
||||
battle.fx('swordRain', { x: unit.x, y: unit.y, id: unit.id });
|
||||
};
|
||||
const castS2 = (battle, unit) => {
|
||||
if (!unit.alive || !unit.deployed) return;
|
||||
talentAtk(battle, unit);
|
||||
battle.addBuff(unit, { key: 'texas2:shower', duration: dur, mods: { atkPct: num(bb.atk) }, visible: true });
|
||||
const mr = num(bb.magic_resistance, 0);
|
||||
for (const e of targetsInGrid(battle, unit, g)) {
|
||||
battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale, 1.5), type: 'arts', isSkill: true, tags: ['skill', 'burst'] });
|
||||
@@ -1235,16 +1235,14 @@ const kits = {
|
||||
};
|
||||
const castS3 = (battle, unit) => {
|
||||
if (!unit.alive || !unit.deployed) return;
|
||||
talentAtk(battle, unit);
|
||||
for (const e of targetsInGrid(battle, unit, g)) {
|
||||
for (let i = 0; i < 2 && e.alive; i++) battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb['appear.atk_scale'], 1.15), type: 'arts', isSkill: true, tags: ['skill', 'burst'] });
|
||||
if (e.alive) battle.applyStatus(e, 'stun', { duration: num(bb['appear.stun'], 1.5), source: unit });
|
||||
}
|
||||
battle.fx('swordStorm', { x: unit.x, y: unit.y, id: unit.id });
|
||||
unit.mem.rainUntil = battle.time + dur;
|
||||
unit.mem.rainTimer?.cancel();
|
||||
unit.mem.rainTimer = battle.every(Math.max(0.1, num(bb['texas2_s_3[sword].interval'], 1)), (b, sched) => {
|
||||
if (!unit.alive || !unit.deployed || b.time > unit.mem.rainUntil + 1e-9) { sched.cancel(); return; }
|
||||
if (!unit.alive || !unit.deployed || !unit.skill?.active) { sched.cancel(); return; }
|
||||
const list = targetsInGrid(b, unit, g);
|
||||
sortEnemyTargets(b, unit, list, null);
|
||||
for (const e of list.slice(0, Math.max(1, Math.floor(num(bb.max_target, 2))))) {
|
||||
@@ -1253,28 +1251,62 @@ const kits = {
|
||||
b.fx('swordRain', { x: e.x, y: e.y, id: e.id });
|
||||
}
|
||||
}, { owner: unit });
|
||||
// Duration advances by dt in the ally phase; align the rain with that clock so the final wave lands before end.
|
||||
unit.mem.rainTimer.due -= battle.dt;
|
||||
};
|
||||
const cast = S1 ? castS1 : S2 ? castS2 : castS3;
|
||||
const skill = {
|
||||
kind: 'duration', activateOnDeploy: true, duration: dur, spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
mods: { atkPct: num(t0.atk, 0.2) + (S1 || S2 ? num(bb.atk) : 0) },
|
||||
onStart({ battle, unit, reason }) {
|
||||
if (reason === 'kill' && unit.mem.texasRecastPending) {
|
||||
unit.mem.texasRecastPending = false; // the synchronous burst already performed this recast
|
||||
return;
|
||||
}
|
||||
unit.mem.texasCasting = true;
|
||||
cast(battle, unit);
|
||||
unit.mem.texasCasting = false;
|
||||
},
|
||||
onEnd({ unit, reason }) {
|
||||
if (reason === 'recast' && unit.mem.texasRecastPending) return;
|
||||
unit.mem.rainTimer?.cancel();
|
||||
},
|
||||
};
|
||||
return {
|
||||
skill: { kind: 'passive', onStart({ battle, unit }) { cast(battle, unit); } },
|
||||
skill,
|
||||
skills: alt(def, {
|
||||
skchr_texas2_1: () => ({ kind: 'passive', onStart({ battle, unit }) { cast(battle, unit); } }),
|
||||
skchr_texas2_2: () => ({ kind: 'passive', onStart({ battle, unit }) { cast(battle, unit); } }),
|
||||
skchr_texas2_1: () => skill,
|
||||
skchr_texas2_2: () => skill,
|
||||
}),
|
||||
// S2: 攻击变为二连击 while the passive lasts
|
||||
trait: S2 ? { hitsFn: (b, u) => (u.findBuff('texas2:shower') ? 2 : 1) } : undefined,
|
||||
trait: S2 ? { hitsFn: (b, u) => (u.skill?.active ? 2 : 1) } : undefined,
|
||||
talents: [
|
||||
{ install(battle, unit) { // 德克萨斯传统 (2nd half): first kill of each deployment ⇒ full heal + recast
|
||||
// (the passive's deploy burst runs inside skill.reset, BEFORE the `deploy` hook: the per-deployment state is
|
||||
// therefore reset when she leaves the field, so a kill by the deploy burst itself counts)
|
||||
battle.on('death', (c) => { if (c.unit === unit) unit.mem.texasKilled = false; }, { owner: unit });
|
||||
battle.on('death', (c) => {
|
||||
if (c.unit !== unit) return;
|
||||
unit.mem.texasKilled = unit.mem.texasCasting = unit.mem.texasRecastPending = false;
|
||||
}, { owner: unit });
|
||||
battle.on('kill', (c) => {
|
||||
if (c.killer !== unit || !unit.alive || c.victim.side !== 'enemy' || unit.mem.texasKilled) return;
|
||||
unit.mem.texasKilled = true;
|
||||
battle.removeBuff(unit, 'texas2:swordplay');
|
||||
battle.heal(unit, unit, unit.s.maxHp * num(t0.hp_ratio, 1), { self: true });
|
||||
cast(battle, unit);
|
||||
if (unit.mem.texasCasting) {
|
||||
unit.mem.texasRecastPending = true;
|
||||
cast(battle, unit); // preserve the burst's synchronous reentry and damage order
|
||||
} else {
|
||||
unit.skill.end('recast');
|
||||
unit.skill.activate('kill', { free: true });
|
||||
}
|
||||
}, { owner: unit });
|
||||
battle.on('skillStart', (c) => {
|
||||
if (c.unit !== unit || !unit.mem.texasRecastPending) return;
|
||||
// Complete the lifecycle after observers received the outer start; the burst already recast above.
|
||||
unit.skill.end('recast');
|
||||
unit.skill.activate('kill', { free: true });
|
||||
}, { owner: unit, priority: -2000 });
|
||||
} },
|
||||
{ install(battle, unit) { // 德克萨斯剑术: until her first kill after each deployment: ASPD +8, −25 % damage taken
|
||||
battle.on('deploy', (c) => {
|
||||
@@ -1290,7 +1322,7 @@ const kits = {
|
||||
const dot = num(bb['attack@texas2_s_1[dot].dot_damage'], 260), dotIv = Math.max(0.1, num(bb['attack@texas2_s_1[dot].interval'], 1));
|
||||
battle.on('damaged', (c) => {
|
||||
const e = c.target;
|
||||
if (c.source !== unit || !c.dmg?.isAttack || e.side !== 'enemy' || !e.alive || !unit.findBuff('texas2:drizzle')) return;
|
||||
if (c.source !== unit || !c.dmg?.isAttack || e.side !== 'enemy' || !e.alive || !unit.skill?.active) return;
|
||||
battle.applyStatus(e, 'silence', { duration: sil, source: unit });
|
||||
battle.addBuff(e, { key: `texas2:drizzleDot:${unit.id}`, duration: dotDur + 1e-6, interval: dotIv, source: unit, refresh: 'extend', // a re-hit refreshes it, the per-second ticks keep their rhythm
|
||||
onTick: ({ unit: t }) => battle.dealDamage(unit, t, { amount: dot, type: 'arts', isSkill: true, tags: ['skill', 'dot'] }) });
|
||||
@@ -1299,7 +1331,7 @@ const kits = {
|
||||
if (S2) {
|
||||
// 攻击…造成法术伤害 while the passive lasts (the double hit is the kit trait's hitsFn)
|
||||
battle.on('hit', (c) => {
|
||||
if (c.source === unit && c.dmg.isAttack && c.dmg.type === 'phys' && unit.findBuff('texas2:shower')) c.dmg.type = 'arts';
|
||||
if (c.source === unit && c.dmg.isAttack && c.dmg.type === 'phys' && unit.skill?.active) c.dmg.type = 'arts';
|
||||
}, { owner: unit, priority: 50 });
|
||||
}
|
||||
const a = num(tb.atk, 0); // module (elite): ATK +10 % with no ally on the 4 adjacent tiles
|
||||
|
||||
@@ -2679,23 +2679,24 @@ function nearl2(bb, chess, def) {
|
||||
// (whole hits negated); then she withdraws and this redeploy time is ×respawn_time (×1 when the operator deployed
|
||||
// right before her is 【卡西米尔】)
|
||||
skchr_nearl2_2: {
|
||||
kind: 'passive',
|
||||
kind: 'duration', activateOnDeploy: true, duration: num(def?.skill?.duration, 22), spCost: 0, spType: 'none', trigger: 'NEVER',
|
||||
mods: { atkPct: num(bb.atk) },
|
||||
onStart({ battle, unit, skill }) {
|
||||
const dur = num(skill.duration, num(def?.skill?.duration, 22));
|
||||
if (!(dur > 0)) return;
|
||||
const prev = lastOps.map?.get(unit.ownerId);
|
||||
const combo = !!prev && prev !== unit && hasBond(prev, 'kazimierzShip');
|
||||
battle.addBuff(unit, { key: 'nearl2:night', mods: { atkPct: num(bb.atk) }, duration: dur, visible: true });
|
||||
unit.mem.nearl2Combo = !!prev && prev !== unit && hasBond(prev, 'kazimierzShip');
|
||||
const hits = Math.floor(num(bb.times));
|
||||
if (hits > 0) battle.addBuff(unit, { key: 'nearl2:shield', shieldHits: hits, duration: dur, visible: true });
|
||||
const seq = unit.deploySeq;
|
||||
battle.after(dur, () => {
|
||||
if (!live(unit) || unit.deploySeq !== seq) return;
|
||||
battle.retreat(unit, { reason: 'retreat' });
|
||||
const mul = combo ? num(bb['nearl2_s_2[withdraw][combo].respawn_time'], 1) : num(bb.respawn_time, 1);
|
||||
if (Number.isFinite(unit.respawnAt) && mul !== 1) unit.respawnAt = battle.time + (unit.respawnAt - battle.time) * mul;
|
||||
battle.fx('disappear', { x: unit.x, y: unit.y, id: unit.id, combo });
|
||||
}, { owner: unit });
|
||||
},
|
||||
onEnd({ battle, unit, reason }) {
|
||||
battle.removeBuff(unit, 'nearl2:shield');
|
||||
if (reason !== 'duration' || !live(unit)) return;
|
||||
const combo = unit.mem.nearl2Combo;
|
||||
battle.retreat(unit, { reason: 'retreat' });
|
||||
const mul = combo ? num(bb['nearl2_s_2[withdraw][combo].respawn_time'], 1) : num(bb.respawn_time, 1);
|
||||
if (Number.isFinite(unit.respawnAt) && mul !== 1) unit.respawnAt = battle.time + (unit.respawnAt - battle.time) * mul;
|
||||
battle.fx('disappear', { x: unit.x, y: unit.y, id: unit.id, combo });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user