feedback3: merge WI — 乌尔比安 S3 move and return are free redeploys, 波登可's spore radius is 0.9, 琳琅诗怀雅 S3 shoots at 10 coins when an enemy is in her attack range
This commit is contained in:
commit
a3ebca0ddf
13 files changed
+547
-31
No files matched your search
@@ -1930,6 +1930,32 @@ export class Battle {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【移动】 (PRTS 术语释义 移动: "不退场,以当前血量在目标位置部署 … 本质上为一次特殊的撤退-再部署行为"): `unit` leaves its
|
||||
* tile (relocate: same checks, false = refused, nothing changed) and is deployed on (r, c) at once. A new deployment —
|
||||
* deploySeq / aggroSeq / deployedAt (the deploy animation; once-per-deployment effects re-arm) — announced by
|
||||
* `deploy` { initial: false, move: true }: deploy effects fire again ("可以通过移动行为多次触发部署时触发的效果"). Not an
|
||||
* exit: no `die` / `death`, so no knock-out, no redeploy timer or cost ("因移动撤退时不会积累再部署惩罚") — 不屈 and
|
||||
* 阿戈尔's revive, which act on exits "因移动之外的原因" (PRTS 阿戈尔 备注), never see it. HP, buffs and a running skill
|
||||
* stay: officially nothing carries over but what the mover's skill names (乌尔比安 S3: 技能进度 and the second talent's
|
||||
* stacks); the remake keeps the rest too (owner's deviation, DESIGN §22.3). `clearSp`: the 技力 is emptied before the
|
||||
* deploy handlers run ("部署时将清空技力"; 乌尔比安's 【返回】 "将清空技力,但仍可以享受后续由其他效果提供的技力").
|
||||
*/
|
||||
moveRedeploy(unit, r, c, { clearSp = false } = {}) {
|
||||
if (!this.relocate(unit, r, c)) return false;
|
||||
unit.deploySeq = ++this._deploySeq;
|
||||
unit.aggroSeq = unit.deploySeq;
|
||||
unit.deployedAt = this.time;
|
||||
const sk = unit.skill;
|
||||
if (clearSp && sk && !sk.noSkill && sk.kind !== 'passive' && !(sk.active && sk.isTimed)) {
|
||||
sk.sp = 0;
|
||||
sk.charges = sk.spCost <= 0 ? sk.maxCharges : 0; // (a free skill is available once per deployment: skills.js reset)
|
||||
}
|
||||
this._ev(['deploy', unit.id]);
|
||||
if (this._hooks.deploy) this.emit('deploy', { unit, initial: false, move: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 受力等级 of enemy `e` under a push / pull of 力度 `force` (微小力 −1, 小力 0, 中力 1, 较大力 2, 大力 3, 大力+1 4, 特大力 5):
|
||||
* force − its current 重量等级 (massLevel incl. 失重) — PRTS 游戏数据基础 §重量公式, 推与拉 (user playtest #6 item 14).
|
||||
|
||||
@@ -374,11 +374,11 @@ function enterTerrain(battle, st, u, code) {
|
||||
* so without it the loss continues. One effect per unit (PRTS 作战机制: "同名buff的默认叠加策略buff只能表现出一个"): a
|
||||
* unit that already carries it gets its full `duration` back and keeps its per-second rhythm — no second effect, no
|
||||
* extra tick [ASSUMED: the time counts from the last contact — so an operator deployed on it, always in contact, drains
|
||||
* past `duration`]. An operator moved off the tile (Battle.relocate: 乌尔比安 S3, 夕's 小自在 …) keeps it for its time;
|
||||
* leaving the field drops it with every buff; a 重生 clears it (enemies.js rebirthCleanse: PRTS 特殊机制 §重生 "清空自身
|
||||
* 身上除白名单外所有Buff") and contact gives it again while the unit is on the tile [ASSUMED]. The tick (infectionDamage)
|
||||
* is true damage no unit deals (无来源), tagged 'terrain' = 环境伤害 (PRTS 自然环境 lists 活性源石), not 'dot' [ASSUMED:
|
||||
* PRTS 伤害分类's list of BUFF damage does not name it].
|
||||
* past `duration`]. An operator moved off the tile (Battle.relocate: 夕's 小自在 …; Battle.moveRedeploy: 乌尔比安 S3)
|
||||
* keeps it for its time; leaving the field drops it with every buff; a 重生 clears it (enemies.js rebirthCleanse: PRTS
|
||||
* 特殊机制 §重生 "清空自身身上除白名单外所有Buff") and contact gives it again while the unit is on the tile [ASSUMED]. The
|
||||
* tick (infectionDamage) is true damage no unit deals (无来源), tagged 'terrain' = 环境伤害 (PRTS 自然环境 lists 活性源石),
|
||||
* not 'dot' [ASSUMED: PRTS 伤害分类's list of BUFF damage does not name it].
|
||||
*/
|
||||
function touchInfection(battle, st, u) {
|
||||
const I = st.infection;
|
||||
@@ -490,11 +490,13 @@ function ensureTick(battle, st) {
|
||||
for (const u of st.turrets.values()) tickTurret(battle, u, dt);
|
||||
});
|
||||
// a unit that (re)deploys / spawns starts fresh (death drops its buffs) and gets its tile's terrain at once: smog
|
||||
// must already hide an operator from the first enemy shot of the battle
|
||||
// must already hide an operator from the first enemy shot of the battle. A 【移动】 (ctx.move: 乌尔比安 S3,
|
||||
// Battle.moveRedeploy) keeps its buffs, so it changes tiles like a walk instead: starting from nothing would strand
|
||||
// the old tile's smog / airflow buff on it
|
||||
battle.on('deploy', (ctx) => {
|
||||
const u = ctx.unit;
|
||||
if (!u || !u.mem || u.kind === 'device') return;
|
||||
u.mem.terrain = 0; u.mem.airAtk = 0; u.mem.airMul = 1; u.mem.airPx = null;
|
||||
if (!ctx.move) { u.mem.terrain = 0; u.mem.airAtk = 0; u.mem.airMul = 1; u.mem.airPx = null; }
|
||||
refreshUnit(battle, st, u);
|
||||
}, { priority: 90 });
|
||||
}
|
||||
|
||||
@@ -241,8 +241,8 @@ export function batMod(v, chess = null, desc = '') {
|
||||
// =================================================================================================================
|
||||
// tier-local constants that exist nowhere in data
|
||||
|
||||
/** Radius (tiles) of 波登可's spore cloud "在周围产生…孢子群" (no blackboard key; ≈ the 3×3 around the vial). */
|
||||
const SPORE_RADIUS = 1.2;
|
||||
/** Radius (tiles) of 波登可's spore cloud. No blackboard key; PRTS 波登可 备注 "孢子群范围半径为0.9,可对空". */
|
||||
const SPORE_RADIUS = 0.9;
|
||||
/** "周围8格" = Chebyshev ring 1 ⇒ Euclidean radius covering the 8 neighbours. */
|
||||
export const RING1 = 1.5;
|
||||
/** 普罗旺斯 杀戮嗅觉 "普通攻击不再以生命值高于80%的敌人作为目标" — the 80 % exists only in the skill text. */
|
||||
@@ -750,8 +750,8 @@ export default {
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 1_13 波登可 孢子扩散: a vial bursts on the current target into a projectile_delay_time s spore cloud: enemies inside
|
||||
// are 停顿 and lose their abilities (silence) and take atk_scale × ATK arts per second.
|
||||
// 1_13 波登可 孢子扩散: a vial bursts on the current target into a projectile_delay_time s spore cloud (radius 0.9,
|
||||
// PRTS 备注, 可对空): enemies inside are 停顿 and lose their abilities (silence) and take atk_scale × ATK arts per second.
|
||||
// 园丁: every 【辅助】 operator on the field ATK +atk. Elite module (DEC-X): +sp_recovery_per_sec SP/s with an enemy in range.
|
||||
// Alternate S1 花香疗法: ATK +atk, normal attacks heal the most injured ally in range instead (ATK per attack). A heal
|
||||
// skill: DEFAULT fires it with an injured ally inside her initial range (research 03 §1.4) — also checked every tick,
|
||||
|
||||
@@ -405,8 +405,10 @@ const KITS = {
|
||||
// closing it spends every coin on random ground enemies of range 2-4 in front and those she blocks (atk_scale phys +
|
||||
// a small push, radial despite the text's 向前 — PRTS 备注 "推开效果为径向推动"; client charpack char_1033_swire2:
|
||||
// the RandomGold ability (Skill_3_End) carries swire2_s_3[knockback] of template knockback[relative]; 地面敌方单位,
|
||||
// 弹道不可对空). Auto-close (the mode casts everything itself; the player's "主动关闭" is not available): once the
|
||||
// purse is full (10) and a coin target stands there. 精锐 module MER-Y: ATK +4 % per trait payment (≤ 5 stacks).
|
||||
// 弹道不可对空). It does not close itself when the purse is full (PRTS 卫戍协议/帮助 「通常不会自动关闭技能」; the skill
|
||||
// text is 「可随时主动关闭」). Owner 2026-10-04: at the cap (金币上限为10) she shoots once an enemy is in the skill's
|
||||
// attack range (the 1-tile range, not the coin-mark 2-4 grid). Below the cap, or with nobody there, it stays open.
|
||||
// 精锐 module MER-Y: ATK +4 % per trait payment (≤ 5 stacks).
|
||||
chess_char_3_04_a: (bb, chess, def) => {
|
||||
const d = defOf(chess, def);
|
||||
const t0 = talentBb(d, 0), t1 = talentBb(d, 1);
|
||||
@@ -493,18 +495,24 @@ const KITS = {
|
||||
skills: altSkills(chess, d, bb, {
|
||||
[S1]: () => ({ kind: 'passive' }), // (coins → heals: installS1)
|
||||
[S3]: (s) => {
|
||||
const cash = num(s.bb.atk_scale, 1), force = num(s.bb.force, 0), full = num(s.bb.sp, 10);
|
||||
const cash = num(s.bb.atk_scale, 1), force = num(s.bb.force, 0);
|
||||
// the 【金币标记】 targets: ground enemies on range 2-4 in front of her (range_table "2-4") and every unit she blocks
|
||||
const coinMarks = (battle, unit) => {
|
||||
const list = enemiesOn(battle, unit, gridKeys(SWIRE2_COIN_GRID, unit), 0, { ...unit.profile, canHitFly: false });
|
||||
for (const e of unit.blocking || []) if (e.alive && !e.isFlying && !list.includes(e)) list.push(e);
|
||||
return list;
|
||||
};
|
||||
const purseCap = Math.max(1, Math.floor(num(s.bb.sp, 10)));
|
||||
return {
|
||||
kind: 'toggle',
|
||||
attack: { hits: 2 },
|
||||
// Owner 2026-10-04: at the cap, shoot once a ground enemy is in the skill attack range (data rangeGrid,
|
||||
// the same 1-tile range as her attack). The coin marks (前方 2-4) are who the burst pays, not the trigger.
|
||||
// Nobody in that range: stay open and keep the coins. Below the cap: stay open.
|
||||
onTick({ battle, unit, skill }) {
|
||||
if ((unit.mem.coins ?? 0) >= full && coinMarks(battle, unit).length) skill.end('manual');
|
||||
if (!skill.active || (unit.mem.coins ?? 0) + 1e-9 < purseCap) return;
|
||||
const keys = gridKeys(skillGrid ?? unit.rangeGrid, unit);
|
||||
if (enemiesOn(battle, unit, keys, 0, { ...unit.profile, canHitFly: false }).length) skill.end('manual');
|
||||
},
|
||||
onEnd({ battle, unit, reason }) {
|
||||
if (reason !== 'manual' || !unit.alive) return;
|
||||
|
||||
@@ -765,7 +765,7 @@ const KITS = {
|
||||
// 乌尔比安 — S3 必须开辟的通路 (25 s, CUSTOM_RANGE row ahead): max HP/ATK +, throws an anchor forward that stops on the
|
||||
// first enemy or at max distance — on his own tile while he blocks: 135 % ATK phys + 6 s stun around it
|
||||
// (projectile_range); moves onto the anchor tile when deployable and not his own (a 从不混淆的方向 marker keeps his tile)
|
||||
// and returns at skill end.
|
||||
// and returns at skill end — both 【移动】, i.e. free redeploys (Battle.moveRedeploy; the return empties his SP).
|
||||
// T1 本性的坚守: heal 100 (160 below 50 %) on every hit taken. T2 血脉的哺养: per kill +120 max HP / +30 ATK (×9),
|
||||
// other Abyssal Hunters +50 %. Module (elite): healing received ×1.2.
|
||||
// S1 必须促成的接触 (instant): the anchor lands on the best enemy of the skill range (beyond his own range, unblocked
|
||||
@@ -846,7 +846,10 @@ const KITS = {
|
||||
const dest = [[sr, sc], frontOf(unit.tileR, unit.tileC, unit.dir, stop + 1)].find(ok);
|
||||
if (dest == null) return;
|
||||
const home = [unit.tileR, unit.tileC];
|
||||
if (!battle.relocate(unit, dest[0], dest[1])) return;
|
||||
// the 【移动】 is a redeploy on the new tile (Battle.moveRedeploy: a new deployment, deploy effects fire again,
|
||||
// no exit) that keeps the running skill — "【移动】后仅继承下列效果:技能进度、第二天赋叠加层数"; the rest of
|
||||
// his buffs are kept too (owner's deviation, DESIGN §22.3). The marker is deployed after the move (备注 ③)
|
||||
if (!battle.moveRedeploy(unit, dest[0], dest[1]) || !unit.alive || !unit.skill?.active) return;
|
||||
const marker = battle.spawnToken(unit, tokenId, home[0], home[1], { untargetable: true, kit: { skill: null, trait: { noAttack: true } } });
|
||||
unit.mem.anchorHome = { r: home[0], c: home[1], marker };
|
||||
battle.fx('teleport', { x: unit.x, y: unit.y, id: unit.id, fromX, fromY });
|
||||
@@ -857,8 +860,12 @@ const KITS = {
|
||||
if (!h) return;
|
||||
if (h.marker && h.marker.alive) battle.retreat(h.marker, { reason: 'expired', permanent: true });
|
||||
if (unit.alive && unit.deployed) {
|
||||
// ④ 【返回】: a 【移动】 back to his tile "【返回】时将清空技力,但仍可以享受后续由其他效果提供的技力" — the SP is
|
||||
// emptied before the deploy effects of the return run (迅捷作战粮, 黄沙罗盘 … still give theirs, so do skillEnd
|
||||
// effects after it: 迅捷). Here in onEnd the skill's mods are still on (skills.js end) [ASSUMED: officially the
|
||||
// return comes "技能结束后"; none of the deploy effects that can reach him reads his stats]
|
||||
const fromX = unit.x, fromY = unit.y;
|
||||
if (battle.relocate(unit, h.r, h.c)) battle.fx('teleport', { x: unit.x, y: unit.y, id: unit.id, fromX, fromY });
|
||||
if (battle.moveRedeploy(unit, h.r, h.c, { clearSp: true })) battle.fx('teleport', { x: unit.x, y: unit.y, id: unit.id, fromX, fromY });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -904,7 +904,11 @@ function champagne(bb) {
|
||||
};
|
||||
}
|
||||
|
||||
/** 从不混淆的方向 (乌尔比安 S3): marker of the owner's original tile; the owner returns there when the skill ends. */
|
||||
/**
|
||||
* 从不混淆的方向 (乌尔比安 S3): marker of the owner's original tile; the owner returns there when the skill ends — a
|
||||
* 【移动】 (Battle.moveRedeploy, SP emptied: PRTS 乌尔比安 S3 备注 "【返回】时将清空技力"). The 乌尔比安 kit brings him back
|
||||
* itself (its onEnd retreats the marker first); this is the fallback for a kit that only places the marker.
|
||||
*/
|
||||
function ulpiaMarker() {
|
||||
return {
|
||||
skill: null,
|
||||
@@ -917,7 +921,7 @@ function ulpiaMarker() {
|
||||
const r = unit.tileR, c = unit.tileC;
|
||||
battle.retreat(unit, { reason: 'expired', permanent: true });
|
||||
const o = ownerOf(unit);
|
||||
if (o && o.alive && o.deployed && (o.tileR !== r || o.tileC !== c) && battle.relocate(o, r, c)) battle.fx('ulpiaReturn', { x: c, y: r, id: o.id });
|
||||
if (o && o.alive && o.deployed && (o.tileR !== r || o.tileC !== c) && battle.moveRedeploy(o, r, c, { clearSp: true })) battle.fx('ulpiaReturn', { x: c, y: r, id: o.id });
|
||||
};
|
||||
// runs before bindToOwnerSkill's plain retreat (higher priority): retreat + bring the owner home
|
||||
battle.on('skillEnd', (ctx) => {
|
||||
|
||||
Reference in new issue
Block a user