feedback2: merge WC — 阿戈尔's devour cancels a knocked-out target's pending marks (revived members stay standing); 乌尔比安 S3 stays on his tile while he blocks (GitHub #33 items 1-2)
This commit is contained in:
commit
f3cb6f414e
8 files changed
+177
-21
No files matched your search
@@ -339,7 +339,10 @@ function installLaterano(battle, pid, bb, members) {
|
||||
* 物理流失 (PRTS 盟约记录: "造成5000点物理流失", 修正 "【吞噬】的物理流失来源为被付与目标自身;单位被【吞噬】击杀时,击杀来源始终为
|
||||
* 对应标记的付与来源"; PRTS 作战机制: a 物理流失 "会受到目标当前防御力…影响而相应衰减") — less the target's DEF as a physical hit
|
||||
* (its own source: no DEF ignore), then battle.loseHp: no shields, dodge or damage multipliers (DEF-free until 0.1.1); the
|
||||
* kill is credited to the marker — in marking order. A unit that died cancels its remaining marks (as target and as marker).
|
||||
* kill is credited to the marker — in marking order. A target knocked out during the pass has its remaining marks
|
||||
* cancelled, also when it is back at once (the 5-tier 立刻复活, 不屈's 立刻重新部署, 埃芒加德 / M3茧甲): PRTS 盟约记录 "目标首次被
|
||||
* 击倒后解除自身被付与但还未触发的【吞噬】效果". A marker off the field gives no further mark (the rule since 0.1.0; one knocked
|
||||
* out and back in the same pass still gives its marks).
|
||||
* Each devoured operator adds its tier to 阿戈尔 once (IN_BATTLE gain, disabled in 联防 / boss fields).
|
||||
* Tokens / devices / empty tiles are never devoured.
|
||||
*/
|
||||
@@ -382,8 +385,14 @@ function devour(battle, pid, bb, members) {
|
||||
}
|
||||
const amount = num(bb.damage_value, 0);
|
||||
const layered = new Set();
|
||||
// a target knocked out during the pass = off the field, or in another deployment than when the marks were placed (items
|
||||
// deploymentOf: the 5-tier revive and 不屈 redeploy it, 埃芒加德 / M3茧甲 revive it in place) — a revived member was
|
||||
// standing again when its pending marks used to knock it out a second time and spend every revive at t = 0 (GitHub #33)
|
||||
const dep = new Map();
|
||||
for (const [, t] of marks) if (!dep.has(t)) dep.set(t, items.deploymentOf(t));
|
||||
const knocked = (t) => !t.alive || items.deploymentOf(t) !== dep.get(t);
|
||||
for (const [m, t] of marks) {
|
||||
if (!t.alive || !m.alive) continue;
|
||||
if (knocked(t) || !m.alive) continue;
|
||||
S.fxOn(battle, 'devour', t, 'bond:egirShip', 'devour', { from: m.id });
|
||||
if (amount > 0) battle.loseHp(t, mitigate(amount, 'phys', t.s), { source: m, tags: ['bond:egir:devour'] });
|
||||
if (!layered.has(t)) {
|
||||
@@ -408,6 +417,8 @@ function installEgir(battle, pid, bb, members) {
|
||||
// fell —, or its own home when it fell on another board piece's home; PRTS 卫戍协议/帮助 §作战阶段 单位部署) with full
|
||||
// HP, SP reset and `deploy` effects (卡西米尔 / 叙拉古). Death priority 11: before 不屈 (10),
|
||||
// whose redeploy "also consumes a 复活 charge" — with this order the charge is always the one used, same outcome.
|
||||
// A member the battle-start devour knocks out spends a charge like any other first knock-out; the marks still pending
|
||||
// on it are cancelled (devour), so it stays standing.
|
||||
const memberSet = new Set(members);
|
||||
const max = Math.max(0, Math.floor(num(bb.max_free_respawn_cnt, 0)));
|
||||
const st = { knocked: new Set(), revives: 0 };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// server/sim/content/items.js — equipment & Arts content (research 04, docs/DATA.md §5).
|
||||
// Battle side: install(battle) is called once per Battle by content/index.js (inside try/catch).
|
||||
// Prep side: registerMeta(registry) is called once at server boot by match/effectsMeta.js.
|
||||
// lendItemEffects(battle, fromUnit, toUnit, { maxTier, duration }) is used by 萨尔贡 × 娜仁图亚 (bands).
|
||||
export { install, lendItemEffects } from './items/battle.js';
|
||||
// lendItemEffects(battle, fromUnit, toUnit, { maxTier, duration }) is used by 萨尔贡 × 娜仁图亚 (bands); deploymentOf(u)
|
||||
// (the deployment a unit is in — a redeploy or an in-place 复活 opens a new one) by 阿戈尔's devour (bonds/core.js).
|
||||
export { install, lendItemEffects, deploymentOf } from './items/battle.js';
|
||||
export { registerMeta } from './items/meta.js';
|
||||
@@ -238,8 +238,9 @@ function hammerState(battle, rt, u) {
|
||||
* [ASSUMED a deployment], 阿戈尔's 立刻复活), and an in-place 复活 (M3茧甲, 埃芒加德: revivedInPlace) opens a new one too
|
||||
* [ASSUMED] — PRTS (M3茧甲 / 埃芒加德 / 阿戈尔 备注) "“复活”的实现方式为:受益者因移动之外的原因退场时下次部署的再部署时间和
|
||||
* 费用归零": officially a revive is a 0-time / 0-cost redeploy; the remake keeps the unit standing instead.
|
||||
* Also read by 阿戈尔's devour (bonds/core.js): a unit whose deployment changed during the pass was knocked out.
|
||||
*/
|
||||
function deploymentOf(u) { return `${u.deploySeq}:${u.mem.revives | 0}`; }
|
||||
export function deploymentOf(u) { return `${u.deploySeq}:${u.mem.revives | 0}`; }
|
||||
|
||||
/** An in-place 复活 (M3茧甲, 埃芒加德) happened: a new deployment for the once-per-deployment lock (deploymentOf). */
|
||||
export function revivedInPlace(u) { if (u && u.mem) u.mem.revives = (u.mem.revives | 0) + 1; }
|
||||
|
||||
@@ -763,8 +763,9 @@ 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: 135 % ATK phys + 6 s stun around it (projectile_range); moves onto the anchor tile when
|
||||
// deployable (a 从不混淆的方向 marker keeps his tile) and returns at skill end.
|
||||
// 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.
|
||||
// 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
|
||||
@@ -815,23 +816,31 @@ const KITS = {
|
||||
kind: 'duration',
|
||||
mods: mods({ hpPct: num(bb.max_hp), atkPct: num(bb.atk) }),
|
||||
onStart({ battle, unit }) {
|
||||
// the anchor flies straight ahead along his direction
|
||||
let stop = null;
|
||||
for (let d = 1; d <= reach; d++) {
|
||||
const [r, c] = frontOf(unit.tileR, unit.tileC, unit.dir, d);
|
||||
// the anchor stops at the field edge and in front of a ground obstacle (crates, roadblocks)
|
||||
if (!battle.grid.inRect(r, c) || battle.grid.isObstacle(r, c)) break;
|
||||
stop = d;
|
||||
if (battle.enemiesInKeys([r * COLS + c], unit, { canHitFly: true }).length) break;
|
||||
// PRTS 备注 ② — the anchor's target: his own tile while he blocks an enemy (e.g. just after a 突袭 landing),
|
||||
// else the nearest tile ahead in the skill range (straight along his direction) with an enemy on it, else the
|
||||
// farthest one. His own tile is a candidate only while he blocks ("自身所在地块(仅阻挡敌人时)"); not taken: the
|
||||
// reading of the range's own tile (6-1 starts at [0,0]) as distance 0 for the second rule (a flyer over him)
|
||||
let stop = 0;
|
||||
if (!unit.blocking.some((e) => e.alive && e.blockedBy === unit)) {
|
||||
for (let d = 1; d <= reach; d++) {
|
||||
const [r, c] = frontOf(unit.tileR, unit.tileC, unit.dir, d);
|
||||
// the anchor stops at the field edge and in front of a ground obstacle (crates, roadblocks)
|
||||
if (!battle.grid.inRect(r, c) || battle.grid.isObstacle(r, c)) break;
|
||||
stop = d;
|
||||
if (battle.enemiesInKeys([r * COLS + c], unit, { canHitFly: true }).length) break;
|
||||
}
|
||||
}
|
||||
const [sr, sc] = frontOf(unit.tileR, unit.tileC, unit.dir, stop ?? 0);
|
||||
const [sr, sc] = frontOf(unit.tileR, unit.tileC, unit.dir, stop);
|
||||
const fromX = unit.x, fromY = unit.y;
|
||||
battle.fx('anchor', { x: sc, y: sr, id: unit.id, fromX, fromY, r: radius });
|
||||
for (const e of battle.foesInRadius(sc, sr, radius)) {
|
||||
battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale), type: 'phys', isSkill: true, tags: ['skill', 'anchor'] });
|
||||
if (e.alive) battle.applyStatus(e, 'stun', { duration: num(bb.stun), source: unit });
|
||||
}
|
||||
if (stop == null || !unit.alive) return;
|
||||
// ③ 【移动】 — only a change of tile moves him and leaves the 从不混淆的方向 ("若目标地块不为当前地块,会在原地部署"): an
|
||||
// anchor on his own tile leaves him where he stands, no marker, nothing to return from [ASSUMED: the "tile one
|
||||
// beyond the landing" is not tried when the landing is his own tile]
|
||||
if (stop === 0 || !unit.alive) return;
|
||||
// PRTS 备注: landing tile > the tile one beyond it > his own tile (a deployable, free, unreserved melee tile)
|
||||
const ok = ([r, c]) => (r !== unit.tileR || c !== unit.tileC) && battle.grid.inRect(r, c) && battle.grid.canStand(r, c) && !battle.grid.isObstacle(r, c) && !battle.isReservedTile(r, c);
|
||||
const dest = [[sr, sc], frontOf(unit.tileR, unit.tileC, unit.dir, stop + 1)].find(ok);
|
||||
|
||||
Reference in new issue
Block a user