feedback1 WD: 逐火 embers can be beaten, 转译基底 changes form before it can die
Player reports after 0.1.0: - #8 深池逐火战士 / 精锐战士 / 护卫: every knock-out is a 1 s 重生 (invulnerable, unblockable, immobile), then a walking, disarmed, 隐匿 hit-count ember that a blocker can target and beat; it stands up with full HP after 10 s (PRTS talent text). The old ember stood still, stealthed and unblockable, so nobody could ever target it and every 逐火 enemy leaked. 假想敌:再生's puppet walks on and stays unblockable. - #5 转译基底·α: its original form cancels every damage instance; the 4th physical / arts hit or a block starts a 2 s change into 寻仇者 / 特战术师 / 幽灵 (Mode_*_Passive, 寻仇者 rage below half HP, 特战术师 two targets). The armed forms attack through the engine (profile.dmgType). It used to die in its first form. - Render: units.js FORMS switch the models' clip sets (A_Die_B/C/D then B/C/D, Die then Idle_2/Move_2/Die_2, Revive), driven by the 'phase' kind or the fx `form`; a noMove enemy is drawn standing.
This commit is contained in:
1 parent
7f134737ce
commit
92282878d3
9 files changed
+676
-112
No files matched your search
+7
-5
@@ -17,7 +17,8 @@
|
||||
// user playtest #6: "阻挡了就一定要能打到").
|
||||
// Unblocked ranged enemies attack allies within their radius and pause ATTACK_PAUSE seconds after each attack; the
|
||||
// candidates pass the enemy's own rule (`e.profile.canTarget`) and are ordered blocker → taunt → latest deployed
|
||||
// (targeting.js sortAllyTargets). Reaching the final leg's end = leak. A `fear` (恐惧) status suspends the route: the
|
||||
// (targeting.js sortAllyTargets). An enemy's damage type is its data's unless content arms it (`e.profile.dmgType`:
|
||||
// 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak. A `fear` (恐惧) status suspends the route: the
|
||||
// enemy runs between random checkpoints away from the fear's source (fear.js moveFeared; a self-inflicted fear
|
||||
// flutters inside its own tile); an `attract` (诱导) status walks it to the status point instead (moveAttracted);
|
||||
// both re-plan the route when released (恐惧 outranks 诱导).
|
||||
@@ -456,7 +457,7 @@ export function updateEnemy(b, e, dt) {
|
||||
}
|
||||
if (!e.hidden && b._checkBlock(e)) return;
|
||||
if (b.time < e.pauseUntil) return;
|
||||
if (e.s.flags.noMove) return;
|
||||
if (e.s.flags.noMove) { e.moving = false; return; } // standing (a 重生, a form change): drawn idle, not walking
|
||||
// 恐惧 (ba.fear "无法被阻挡并四散逃跑"; PRTS 诱发移动: 恐惧 outranks 诱导): runs to random tiles of the fan away from
|
||||
// its source — a self-inflicted fear flutters inside its own tile (fear.js); the route re-plans once it ends
|
||||
if (e.s.flags.fear && !e.hidden) { moveFeared(b, e, dt); return; }
|
||||
@@ -570,11 +571,12 @@ function advanceRoute(b, e, dt, R) {
|
||||
function enemyAttack(b, e) {
|
||||
const def = e.def;
|
||||
if (e.profile && e.profile.noAttack) return;
|
||||
if (def.dmgType === 'none' || e.s.atk <= 0) return;
|
||||
const dmgType = (e.profile && e.profile.dmgType) || def.dmgType;
|
||||
if (dmgType === 'none' || e.s.atk <= 0) return;
|
||||
if (e.s.flags.fear || e.s.flags.disarm) return;
|
||||
if (e.s.flags.tremble && e.blockedBy) return; // 战栗: 被阻挡后无法进行普通攻击
|
||||
if (e.atkCd > 0) return;
|
||||
if (def.dmgType === 'heal') { enemyHeal(b, e, e.base.rangeRadius); return; }
|
||||
if (dmgType === 'heal') { enemyHeal(b, e, e.base.rangeRadius); return; }
|
||||
// applyWay MELEE enemies only ever hit their blocker, even when their data carries a rangeRadius (粉碎攻坚手 2.5,
|
||||
// 宿主士兵 2.5, 深池伙友卫队 1.4 … — that radius belongs to their abilities/splash, handled by content).
|
||||
// Content may flip it with `e.profile.melee = false`.
|
||||
@@ -618,7 +620,7 @@ function enemyAttack(b, e) {
|
||||
e.lastAttackAt = b.time;
|
||||
e.stats.attacks++;
|
||||
const attackId = ++b._attackSeq;
|
||||
const type = def.dmgType === 'heal' ? 'arts' : def.dmgType;
|
||||
const type = dmgType === 'heal' ? 'arts' : dmgType;
|
||||
const rangedShot = radius > 0 && !(e.blockedBy && targets[0] === e.blockedBy && radius < 1);
|
||||
// content-resolved attacks (`e.profile.deferHit`: 帝国炮火先兆者's shell landing 3 s later): the attack itself happens
|
||||
// — the 'atk' event (kind `e.profile.shot`, drawn by the content's own fx), cooldown, pause, the 'attack' hook — and
|
||||
|
||||
+129
-76
@@ -24,7 +24,10 @@
|
||||
// rebirth of Reborn.duration s, untargetable and inert, then the new form); artsBarrier() — "吸收法术伤害的屏障"
|
||||
// (absorbs arts after RES); frontGuard() — "来自正面的伤害降低" (facing = walking direction or the bigger crowd);
|
||||
// unbalanced() — 失衡 detection (displacement beyond the enemy's own speed; engine displace() has no hook);
|
||||
// attackLoop() — normal attacks for data-unarmed enemies once a form arms them; float() — 近地悬浮 (an air unit that
|
||||
// husk() — "被击倒时暂时变为…,一段时间后重生" (Revive[Trigger]: every knock-out of the first form ⇒ 1 s 重生 ⇒ a walking
|
||||
// hit-count husk — 隐匿 逐火 embers, the unblockable 再生 puppet — until the real death or its revival); a data-unarmed
|
||||
// enemy armed by a form (转译基底) sets `e.profile` noAttack / melee / dmgType / maxTargets and attacks through the
|
||||
// engine (ai.js enemyAttack); float() — 近地悬浮 (an air unit that
|
||||
// keeps its ground path, Unit.isFlying; kitSyufo / kitParrot lose it when stunned). `e.profile.canTarget(ally)` = the
|
||||
// enemy's own target rule (只攻击地面单位 …), applied by the engine to the candidates before its priority order; a
|
||||
// special priority (优先攻击防御力最高的… / 生命上限最高的…) sorts by its key, ties by taunt then latest deployed
|
||||
@@ -51,11 +54,12 @@
|
||||
//
|
||||
// fx kinds emitted (battle.fx(kind, {x, y, …})): 'explode' {r, kind} · 'zone' {r, dur, kind} · 'telegraph' {r, dur,
|
||||
// kind, tiles?} (delayed strikes, charges, 'reborn') · 'beam' {from, to, kind} · 'summon' {id, key} · 'ember'/'revive'/
|
||||
// 'stone' {id} · 'blink' {id, fx, fy} · 'charge' {id, tx, ty} · 'expose' {id} · 'shieldBreak' {id} · 'liberate' {id}
|
||||
// · 'phase' {id, kind} (form / barrier changes) · 'lpLoss' {value, reason} · 'steal' · 'ignite'.
|
||||
// 'stone' {id, form?} · 'blink' {id, fx, fy} · 'charge' {id, tx, ty} · 'expose' {id} · 'shieldBreak' {id} · 'liberate' {id}
|
||||
// · 'phase' {id, kind} (form / barrier changes) · 'lpLoss' {value, reason} · 'steal' · 'ignite'. A 'phase' kind, or
|
||||
// the `form` of any other fx, is the model's clip set from then on (render/app.js → UnitView.setForm, units.js FORMS).
|
||||
// Custom hook: 'lpLoss' {amount, reason, source} — leader "扣除目标生命" effects; also summed into result.lpLoss.
|
||||
|
||||
import { TICK, MOVE_SCALE, ELEMENT, ATTACK_PAUSE } from '../constants.js';
|
||||
import { TICK, MOVE_SCALE, ELEMENT } from '../constants.js';
|
||||
import { canTargetAlly, sortAllyTargets, aggroCmp } from '../targeting.js';
|
||||
import { mitigate } from '../damage.js';
|
||||
|
||||
@@ -69,6 +73,13 @@ export const EROSION_BURST = Object.freeze({ defDown: ELEMENT.erosion.ally.defDo
|
||||
/** 假想敌:淤困: element damage its host's burst spreads to the allies of the 4 tiles around (PRTS 假想敌:淤困 "附着对象元素爆发时
|
||||
* …对附着对象及周围4格内的所有我方单位…造成1000同类型元素损伤"; the host itself is in its 爆发冷却). */
|
||||
const PARASITE_SPREAD = 1000;
|
||||
/** 重生 of a Revive[Trigger] knock-out before its husk (PRTS 深池逐火战士 "被击倒后重生,持续1s"; the same talent template on
|
||||
* 假想敌:再生, whose PRTS page gives no duration — 1 s there too [ASSUMED]; both models' knock-out clips last 1 s). */
|
||||
export const HUSK_REBIRTH = 1;
|
||||
/** 转译基底·α: "变化过程持续2s" (PRTS 天赋; the model's A_Die_B / _C / _D change clips last 2 s); the original form's
|
||||
* immunities ("免疫晕眩/沉睡/寒冷/冻结/浮空/恐惧", plus 失衡免疫). */
|
||||
export const TRANSLATOR_CHANGE = 2;
|
||||
const TRANSLATOR_IMMUNE = Object.freeze(['stun', 'sleep', 'cold', 'freeze', 'levitate', 'fear']);
|
||||
/** Death-explosion radius when the enemy has no official radius [ASSUMED]. */
|
||||
const BOOM_RADIUS = 1.25;
|
||||
/** 萨卡兹枯朽战车 秽蚀轰击: radius of the 【污染秽蚀】 it leaves at its target (PRTS 萨卡兹枯朽战车 技能 "在目标位置生成半径1.7,
|
||||
@@ -720,33 +731,6 @@ function reborn({ dur = 0, hpRatio = 1, invincible = 0, onKo = null, during = nu
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A normal-attack loop for enemies whose data never attacks (dmgType none) once content arms them (转译基底 forms):
|
||||
* the blocker first, else (ranged) the engine priority inside `radius`; `hits` instances of ATK per attack.
|
||||
* Disabled until `a.on`.
|
||||
*/
|
||||
function attackLoop({ type = 'phys', melee = true, radius = 0, hits = 1 } = {}) {
|
||||
return {
|
||||
spawn(b, e, a) { a.cd = 0; a.on = false; },
|
||||
tick(b, e, a, dt) {
|
||||
if (!a.on || !e.alive || e.hidden) return;
|
||||
const f = e.s.flags;
|
||||
if (f.stun) return;
|
||||
if (a.cd > 0) { a.cd -= dt; if (a.cd > 1e-9) return; }
|
||||
if (f.disarm || f.fear || (f.tremble && e.blockedBy)) return;
|
||||
let t = e.blockedBy && e.blockedBy.alive ? e.blockedBy : null;
|
||||
if (!t && !melee && radius > 0) t = byPriority(e, targetsNear(b, e, radius))[0] ?? null;
|
||||
if (!t) return;
|
||||
a.cd = e.s.interval;
|
||||
e.lastAttackAt = b.time;
|
||||
e.stats.attacks++;
|
||||
if (!e.blockedBy) e.pauseUntil = b.time + ATTACK_PAUSE;
|
||||
if (!melee && t !== e.blockedBy) b.fx('beam', { x: e.x, y: e.y, from: e.id, to: t.id, kind: 'enemyShot' });
|
||||
for (let i = 0; i < hits && t.alive; i++) b.dealDamage(e, t, { amount: e.s.atk, type, isAttack: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 正面减伤: phys/arts damage from allies in front of the enemy (`a.facing` side) × (1 − cut); `face` updates a.facing. */
|
||||
function frontGuard(cut, face) {
|
||||
return {
|
||||
@@ -809,33 +793,53 @@ function artsBarrier(amount, { key = 'ab:artsBarrier', whileUp = null } = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Revive as a stealthed 频次 husk (逐火 embers, 假想敌:再生). */
|
||||
function husk({ hits, delay, stealthy = true, onHusk = null, key = 'ab:ember' }) {
|
||||
/**
|
||||
* "被击倒时暂时变为…,一段时间后重生" (talent Revive[Trigger]: 逐火 embers, 假想敌:再生's puppet). A knock-out of the first form is
|
||||
* no kill (credit, bounty and the kill count wait for the real death): HUSK_REBIRTH s of 重生 first — invulnerable,
|
||||
* untargetable, unblockable, immobile, 失衡免疫 (PRTS 深池逐火战士 天赋 "被击倒后重生,持续1s,随后变为怨恨的余烬,1s内不移动且持有
|
||||
* 无敌+无法阻挡+失衡免疫"; PRTS 特殊机制 §重生) — then the husk until `delay` s after that: `hits` HP of 特殊生命值机制 (every
|
||||
* damage instance removes 1 — PRTS 特殊机制 §特殊生命值机制; engine flag hitCount), no attack (缴械), walking its route on —
|
||||
* 隐匿 (`stealthy`: targetable only while blocked, targeting.js canTargetEnemy — the 余烬 must be blocked to be beaten)
|
||||
* and / or unblockable (`unblock`: 再生's 傀儡 "不可被阻挡"). Killing the husk is the real death; a husk still standing
|
||||
* after `delay` s stands up in its first form with full HP, and every later knock-out starts it again ("一次又一次地站起").
|
||||
* User report after 0.1.0 (#8): the v2.5 ember stood still, stealthed AND unblockable — nobody could ever target it.
|
||||
* fx: 'ember' {id, hits, dur, form: 'husk'} at the knock-out, 'revive' {id, form: 'revived'} when it stands up (the
|
||||
* renderer switches the model's clip set: render/units.js FORMS).
|
||||
*/
|
||||
function husk({ hits, delay, stealthy = true, unblock = false, onHusk = null, key = 'ab:ember' }) {
|
||||
const revive = (b, e, a) => {
|
||||
if (!e.alive || a.state !== 'husk') return;
|
||||
a.state = null;
|
||||
hitCount(b, e, false);
|
||||
e.base.maxHp = a.max;
|
||||
e.markDirty();
|
||||
void e.s;
|
||||
e.hp = e.s.maxHp;
|
||||
e.profile.noAttack = a.noAtk;
|
||||
b.removeBuff(e, key);
|
||||
b.removeBuff(e, `${key}:reborn`);
|
||||
if (e.route) e.route.pts = null;
|
||||
e.atkCd = 0;
|
||||
b.fx('revive', { x: e.x, y: e.y, id: e.id, form: 'revived' });
|
||||
};
|
||||
return {
|
||||
killed(c, b, e, a, ab) {
|
||||
if (a.state === 'husk' || !(hits > 0) || !(delay > 0)) return false;
|
||||
killed(c, b, e, a) {
|
||||
// an HP loss (流失 bypasses 无敌) during the 1 s 重生 cannot cut it short
|
||||
if (a.state === 'husk' && b.time < a.rebornUntil) { e.hp = e.s.maxHp; return true; }
|
||||
if (a.state === 'husk' || !(hits > 0) || !(delay > 0)) return false; // the husk's knock-out is the real death
|
||||
a.state = 'husk';
|
||||
a.rebornUntil = b.time + HUSK_REBIRTH;
|
||||
a.noAtk = e.profile.noAttack;
|
||||
a.max = a.max ?? e.base.maxHp;
|
||||
e.profile.noAttack = true;
|
||||
setHits(e, hits);
|
||||
hitCount(b, e, true);
|
||||
b.addBuff(e, { key, visible: true, flags: { noMove: true, unblockable: true, ...(stealthy ? { stealth: true } : {}) } });
|
||||
b.fx('ember', { x: e.x, y: e.y, id: e.id, hits, dur: delay });
|
||||
b.addBuff(e, { key, visible: true, persist: true, flags: { disarm: true, ...(stealthy ? { stealth: true } : {}), ...(unblock ? { unblockable: true } : {}) } });
|
||||
b.addBuff(e, { key: `${key}:reborn`, duration: HUSK_REBIRTH, flags: { invulnerable: true, untargetable: true, unblockable: true, noMove: true, noDisplace: true } });
|
||||
if (e.route) e.route.pts = null;
|
||||
b.fx('ember', { x: e.x, y: e.y, id: e.id, hits, dur: HUSK_REBIRTH + delay, form: 'husk' });
|
||||
if (onHusk) onHusk(b, e);
|
||||
b.after(delay, () => {
|
||||
if (!e.alive || a.state !== 'husk') return;
|
||||
a.state = null;
|
||||
hitCount(b, e, false);
|
||||
e.base.maxHp = a.max;
|
||||
e.markDirty();
|
||||
void e.s;
|
||||
e.hp = e.s.maxHp;
|
||||
e.profile.noAttack = a.noAtk;
|
||||
b.removeBuff(e, key);
|
||||
if (e.route) e.route.pts = null;
|
||||
b.fx('revive', { x: e.x, y: e.y, id: e.id });
|
||||
}, { owner: e });
|
||||
b.after(HUSK_REBIRTH + delay, () => revive(b, e, a), { owner: e });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
@@ -948,6 +952,9 @@ const kitPrisoner = (freeAll = false) => (ab) => [prisoner(ab, { freeAll })];
|
||||
const kitDeathSpawn = (extra = []) => (ab) => [deathSpawn(ab.tS['DeadSpawn.enemy_key'], T(ab, 'DeadSpawn.cnt') ?? 0), ...extra];
|
||||
const kitStun3 = (ab) => [nthAttackStatus(nthOf(ab.sk.stuncombat), 'stun', (ab.sk.stuncombat && ab.sk.stuncombat.bb.stun) || 0, true)];
|
||||
const kitSelfFear = (ab) => [selfFear(ab)];
|
||||
/** 深池逐火战士 / 精锐战士 / 护卫: knock-out ⇒ 1 s 重生 ⇒ a walking, 隐匿, disarmed 余烬 / 火灰 of prop_max_hp hits for `interval` s
|
||||
* (PRTS 深池逐火战士 天赋: "基础最大生命值临时变为5…具有特殊生命值机制,不进行攻击,获得隐匿、缴械,10s后若未被击倒则变回战士形态并恢复所有
|
||||
* 生命"); blocking it lifts the 隐匿, so its blocker (and every operator in range) can beat it. */
|
||||
const kitEmber = (ab) => [husk({ hits: T(ab, 'Revive[Trigger].prop_max_hp'), delay: T(ab, 'Revive[Trigger].interval') })];
|
||||
const kitPolluted = (ab) => [{
|
||||
death(c, b, e) {
|
||||
@@ -1420,10 +1427,13 @@ function kitBlackCloud(ab, e) {
|
||||
];
|
||||
}
|
||||
|
||||
/** 假想敌:再生 · knock-out ⇒ a 傀儡 of prop_max_hp hits for `interval` s that "不可被阻挡" (data description) and walks on — its
|
||||
* model has a 傀儡 walk cycle (B_Move); an immobile unit would not need to be unblockable [ASSUMED from both] — plus
|
||||
* max_damage_block_cnt-hit shields on the enemies around it. */
|
||||
function kitRegen(ab) {
|
||||
const hits = T(ab, 'Revive[Trigger].prop_max_hp'), delay = T(ab, 'Revive[Trigger].interval'), block = T(ab, 'Aura.max_damage_block_cnt') ?? 0;
|
||||
return [husk({
|
||||
hits, delay, stealthy: false, key: 'ab:regen',
|
||||
hits, delay, stealthy: false, unblock: true, key: 'ab:regen',
|
||||
onHusk(b, e) {
|
||||
// 被击倒后…使周围一定距离内其他敌人获得可以抵挡物理及法术伤害的护盾
|
||||
b.fx('telegraph', { x: e.x, y: e.y, r: ACPUPP_AURA_RADIUS, kind: 'regenShield', id: e.id });
|
||||
@@ -2048,32 +2058,75 @@ function kitWolfLord(ab) {
|
||||
})];
|
||||
}
|
||||
|
||||
/** 转译基底·α · never attacks at first; 4 physical hits ⇒ 复仇 form (melee, heavy), 4 arts hits ⇒ 术士 form (ranged arts),
|
||||
* blocked first ⇒ 幽灵 form (no stat change; unblockable [ASSUMED]). Stats from Mode_*_Passive. */
|
||||
/**
|
||||
* 转译基底·α (PRTS 转译基底·α 天赋; enemy_database talents Passive / Mode_*_Passive / Mode_Fuchou_Anger; skills ChangeToB/C/D):
|
||||
* 原始形态 — no attack, every damage instance is cancelled ("受到伤害时取消此伤害"), 失衡免疫, immune to 晕眩/沉睡/寒冷/冻结/浮空/
|
||||
* 恐惧. The 4th physical damage instance taken ⇒ 寻仇者, the 4th arts one ⇒ 特战术师, being blocked ⇒ 幽灵 — once ("仅可变化一次");
|
||||
* the change takes TRANSLATOR_CHANGE (2) s, standing still and still in the original form [ASSUMED: immobile, damage
|
||||
* still cancelled]; then the form's flat stat changes apply (move speed relative to the data's 1.0, attack interval
|
||||
* +N s, 重量等级 +N):
|
||||
* 寻仇者 (form B): melee only, physical; ATK +100 % while HP < 50 % (Mode_Fuchou_Anger.atk);
|
||||
* 特战术师 (form D): ranged only (the data's 2.4 radius), arts, 2 targets at once;
|
||||
* 幽灵 (form C): no stat change, unblockable, no attack (PRTS lists none; its model has no attack clip).
|
||||
* The armed forms attack through the engine's enemy attack (ai.js enemyAttack, `profile.dmgType`), so attack clips,
|
||||
* projectiles and the attack hooks are the normal ones. Before its change ends it cannot be killed by damage at all —
|
||||
* user report after 0.1.0 (#5): the v2.5 kit let damage through, so it died in its first form, its model still in that
|
||||
* form playing the 寻仇者's B_Die. fx 'phase' {id, kind: translator_fuchou | _shushi | _youling} starts the model's 2 s
|
||||
* change clip (render/units.js FORMS). Form letters: B / C / D follow the talents' order Fuchou / Youling / Shushi — C,
|
||||
* the only clip set without an attack, is the non-attacking 幽灵.
|
||||
*/
|
||||
function kitTranslator(ab, e) {
|
||||
const t = (k) => T(ab, k) ?? 0;
|
||||
const P = { phys: 0, arts: 0, form: null };
|
||||
const mods = (pre) => ({ hpFlat: t(`${pre}.max_hp`), atkFlat: t(`${pre}.atk`), defFlat: t(`${pre}.def`), resFlat: t(`${pre}.magic_resistance`),
|
||||
moveMul: Math.max(0, 1 + t(`${pre}.move_speed`)), batPct: t(`${pre}.base_attack_time`) / (e.base.bat || 1) });
|
||||
const melee = attackLoop({ type: 'phys', melee: true });
|
||||
const caster = attackLoop({ type: 'arts', melee: false, radius: e.base.rangeRadius || (e.def.raw && e.def.raw.stats && e.def.raw.stats.rawRangeRadius) || 2.4 });
|
||||
const change = (b, e2, form) => {
|
||||
if (P.form) return;
|
||||
P.form = form;
|
||||
const pre = form === 'fuchou' ? 'Mode_Fuchou_Passive' : form === 'shushi' ? 'Mode_Shushi_Passive' : 'Mode_Youling_Passive';
|
||||
b.addBuff(e2, { key: 'ab:form', persist: true, visible: true, mods: mods(pre), flags: form === 'youling' ? { unblockable: true } : null });
|
||||
if (form === 'fuchou') melee.on = true;
|
||||
if (form === 'shushi') caster.on = true;
|
||||
b.fx('phase', { x: e2.x, y: e2.y, id: e2.id, kind: `translator_${form}` });
|
||||
const P = { phys: 0, arts: 0, form: null, done: false, anger: false };
|
||||
const PRE = { fuchou: 'Mode_Fuchou_Passive', shushi: 'Mode_Shushi_Passive', youling: 'Mode_Youling_Passive' };
|
||||
const mods = (pre) => ({
|
||||
hpFlat: t(`${pre}.max_hp`), atkFlat: t(`${pre}.atk`), defFlat: t(`${pre}.def`), resFlat: t(`${pre}.magic_resistance`),
|
||||
moveMul: Math.max(0, 1 + t(`${pre}.move_speed`) / (e.def.moveSpeed || 1)), batPct: t(`${pre}.base_attack_time`) / (e.base.bat || 1),
|
||||
massFlat: t(`${pre}.mass_level`),
|
||||
});
|
||||
const finish = (b, e2) => {
|
||||
if (!e2.alive || P.done) return;
|
||||
P.done = true;
|
||||
b.removeBuff(e2, 'ab:origin');
|
||||
ab.immune = null;
|
||||
b.addBuff(e2, { key: 'ab:form', persist: true, visible: true, mods: mods(PRE[P.form]), flags: P.form === 'youling' ? { unblockable: true } : null });
|
||||
if (P.form !== 'youling') {
|
||||
const melee = P.form === 'fuchou';
|
||||
Object.assign(e2.profile, { noAttack: false, melee, dmgType: melee ? 'phys' : 'arts', maxTargets: melee ? 1 : 2 });
|
||||
e2.atkCd = 0;
|
||||
}
|
||||
if (e2.route) e2.route.pts = null;
|
||||
};
|
||||
return [melee, caster, {
|
||||
taken(c, b, e2) {
|
||||
const s = c.source;
|
||||
if (P.form || !s || s.side !== 'ally') return;
|
||||
if (c.dmg.type === 'phys' && ++P.phys >= (t('Passive.phy_max_count') || 4)) change(b, e2, 'fuchou');
|
||||
else if (c.dmg.type === 'arts' && ++P.arts >= (t('Passive.magic_max_count') || 4)) change(b, e2, 'shushi');
|
||||
const change = (b, e2, form) => {
|
||||
if (P.form || !e2.alive) return;
|
||||
P.form = form;
|
||||
b.addBuff(e2, { key: 'ab:change', duration: TRANSLATOR_CHANGE, persist: true, flags: { noMove: true }, onExpire: ({ battle }) => finish(battle, e2) });
|
||||
b.fx('phase', { x: e2.x, y: e2.y, id: e2.id, kind: `translator_${form}`, dur: TRANSLATOR_CHANGE });
|
||||
};
|
||||
return [{
|
||||
spawn(b, e2) {
|
||||
b.addBuff(e2, { key: 'ab:origin', persist: true, flags: { noDisplace: true } });
|
||||
ab.immune = new Set(TRANSLATOR_IMMUNE);
|
||||
},
|
||||
hitIn(c, b, e2) {
|
||||
if (P.done) return;
|
||||
const s = c.source || c.credit, ty = c.dmg.type;
|
||||
if (!P.form && s && s.side === 'ally') {
|
||||
if (ty === 'phys' && ++P.phys >= (t('Passive.phy_max_count') || 4)) change(b, e2, 'fuchou');
|
||||
else if (ty === 'arts' && ++P.arts >= (t('Passive.magic_max_count') || 4)) change(b, e2, 'shushi');
|
||||
}
|
||||
c.dmg.cancel = true;
|
||||
},
|
||||
blocked(c, b, e2) { change(b, e2, 'youling'); },
|
||||
tick(b, e2) {
|
||||
// 寻仇者: "生命值低于50%时,攻击力+100%"
|
||||
if (P.form !== 'fuchou' || !P.done) return;
|
||||
const on = e2.hpRatio < 0.5;
|
||||
if (on === P.anger) return;
|
||||
P.anger = on;
|
||||
if (on) b.addBuff(e2, { key: 'ab:anger', persist: true, visible: true, mods: { atkPct: t('Mode_Fuchou_Anger.atk') } });
|
||||
else b.removeBuff(e2, 'ab:anger');
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -2154,10 +2207,10 @@ export const KITS = Object.freeze({
|
||||
enemy_1209_sfden_2: kitInvisShield, // 堂皇 · same (铁灯盘)
|
||||
enemy_1203_sfhu: kitTeapot, // 烹泉 · death: 4 青瓷茶器 + ASPD-down arts blast zone
|
||||
enemy_1203_sfhu_2: kitTeapot, // 沏虹 · death: 4 彩瓷茶器 + blast zone
|
||||
enemy_1288_duskls: kitEmber, // 深池逐火战士 · on KO: stealthed 5-hit ember, revives after 10 s
|
||||
enemy_1288_duskls: kitEmber, // 深池逐火战士 · every KO: 1 s 重生 ⇒ walking 隐匿 5-hit ember (block it to hit it), back after 10 s
|
||||
enemy_1288_duskls_2: kitEmber, // 深池逐火精锐战士 · same
|
||||
enemy_1292_duskld: kitEmber, // 深池逐火护卫 · 10-hit ember
|
||||
enemy_9010_acpupp: kitRegen, // 假想敌:再生 · 15-hit regen husk (15 s) + 5-hit shields on nearby enemies
|
||||
enemy_1292_duskld: kitEmber, // 深池逐火护卫 · 10-hit 火灰
|
||||
enemy_9010_acpupp: kitRegen, // 假想敌:再生 · every KO: 1 s 重生 ⇒ walking unblockable 15-hit puppet (15 s) + 5-hit shields nearby
|
||||
|
||||
// --- ELEMENT 元素
|
||||
enemy_1148_dssbr: kitEp('neural', 'epdamage.attack@ep_damage_ratio'), // 底海滑动者 · neural on hit
|
||||
@@ -2466,7 +2519,7 @@ export const KITS = Object.freeze({
|
||||
enemy_1517_xi: kitXi, // “自在” · cross arts, 破桎而出 barrier blast ⇒ form 2 (double hits, 2 crosses)
|
||||
enemy_1525_blkswb: kitMace, // 锏 · 抵抗, DEF pen, 速杀 blink, AoE ⇒ form 2 (stealth, double hits, SP clear)
|
||||
enemy_1535_wlfmster: kitWolfLord, // 扎罗 · −30 % damage, 溶血骇惧 ⇒ 远古威慑 rebirth ⇒ ranged double hits
|
||||
enemy_10081_mpplai: kitTranslator, // 转译基底·α · 4 phys / 4 arts hits or being blocked ⇒ one of three forms
|
||||
enemy_10081_mpplai: kitTranslator, // 转译基底·α · damage cancelled; 4th phys / 4th arts hit or blocked ⇒ 2 s change ⇒ 寻仇者 / 特战术师 / 幽灵
|
||||
enemy_10144_xdelk_2: kitElk, // 乌顶巨角卢鲁 · 角力对决 charge on its blocker (push fails ⇒ damage + stun)
|
||||
enemy_2085_skzjxd: (ab) => [frontGuard(T(ab, 'Weakness.damage_resistance') ?? 0, faceCrowd)], // 圆仔 · faces the bigger crowd, front damage −80 %
|
||||
enemy_2085_skzjxd_2: (ab) => [frontGuard(T(ab, 'Weakness.damage_resistance') ?? 0, faceCrowd)], // 圆仔 · same
|
||||
|
||||
Reference in new issue
Block a user