feedback2 WC: 阿戈尔's devour cancels the pending marks of a unit it knocked out, also when that unit is revived at once, so the revived members stay standing (GitHub #33 item 1)
This commit is contained in:
1 parent
2a03467897
commit
215d52efe3
5 files changed
+84
-7
No files matched your search
@@ -373,12 +373,12 @@ Members (11 chess, 10 in current shop pool; by tier in shop: {'1': 1, '2': 1, '3
|
||||
|
||||
- **[3 distinct]** 阿戈尔 members maxHP x(1 + 0.35 + 0.01*L).
|
||||
- **[3 distinct (battle start)]** Devour (吞噬): see algorithm.
|
||||
- **[5 distinct]** The first 3 阿戈尔 members to be knocked out (first time each) revive immediately (max_free_respawn_cnt 3).
|
||||
- **[5 distinct]** The first 3 阿戈尔 members to be knocked out (first time each) revive immediately (max_free_respawn_cnt 3). Devour knock-outs count: a chain that knocks out 3 members spends the 3 revives at battle start (and those members stay standing, step 4).
|
||||
- Algorithm:
|
||||
1. Order: 阿戈尔 members sorted leftmost first, then topmost ("更靠左和靠上").
|
||||
2. Each 阿戈尔 in order marks the unit on the tile directly in front of it (its facing direction) and also the front-tile unit of every 阿戈尔 it has marked (chain). It never marks itself, a unit it already marked, or a unit that marked it.
|
||||
3. The marker immediately gains the base ATK of every unit it marked (added to base ATK at the final stage) and their block counts.
|
||||
4. After all marks are placed, each mark makes its target suffer one 5000-point physical 流失 (HP loss, source = the target itself; if it dies, the kill is credited to the marker), resolved in marking order. A target that dies cancels its remaining pending marks.
|
||||
4. After all marks are placed, each mark makes its target suffer one 5000-point physical 流失 (HP loss, source = the target itself; if it dies, the kill is credited to the marker), resolved in marking order. A target knocked out for the first time has its pending marks cancelled ("目标首次被击倒后解除自身被付与但还未触发的【吞噬】效果") — also when it is revived at once (the 5-tier revive, 埃芒加德, M3茧甲), so a revived member is not devoured again. [ASSUMED] a marker knocked out during the pass gives no further mark either, revived or not (PRTS names only the target). DESIGN §22.3.
|
||||
5. Each devoured unit adds layers to 阿戈尔 equal to its tier (1-6), once per unit per battle.
|
||||
6. (5-member tier) Revive is implemented as: when the unit leaves the field for any reason other than being moved, its next deployment has 0 redeploy time and 0 cost (PRTS). Devour layers: each devoured unit adds its tier once per battle (refreshes next round).
|
||||
- Formulas: `hpMultiplier = 1.35 + 0.01*L`; `devourDamage = 5000`; `layersPerDevoured = tier of devoured unit`
|
||||
|
||||
@@ -339,7 +339,9 @@ 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 unit knocked out during the pass cancels its remaining marks, also
|
||||
* when it is revived at once (the 5-tier 立刻复活, 埃芒加德 / M3茧甲): as target (PRTS 盟约记录 "目标首次被击倒后解除自身被付与
|
||||
* 但还未触发的【吞噬】效果") and as marker (the remake's rule since 0.1.0, [ASSUMED]: PRTS names only the target).
|
||||
* Each devoured operator adds its tier to 阿戈尔 once (IN_BATTLE gain, disabled in 联防 / boss fields).
|
||||
* Tokens / devices / empty tiles are never devoured.
|
||||
*/
|
||||
@@ -382,8 +384,14 @@ function devour(battle, pid, bb, members) {
|
||||
}
|
||||
const amount = num(bb.damage_value, 0);
|
||||
const layered = new Set();
|
||||
// knocked out during the pass = off the field, or in another deployment than when the marks were placed (items
|
||||
// deploymentOf: the 5-tier revive redeploys, 埃芒加德 / M3茧甲 revive 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 pair of marks) for (const u of pair) if (!dep.has(u)) dep.set(u, items.deploymentOf(u));
|
||||
const knocked = (u) => !u.alive || items.deploymentOf(u) !== dep.get(u);
|
||||
for (const [m, t] of marks) {
|
||||
if (!t.alive || !m.alive) continue;
|
||||
if (knocked(t) || knocked(m)) 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 +416,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; }
|
||||
|
||||
@@ -418,6 +418,71 @@ test('阿戈尔 5: the first 3 members knocked out for the first time redeploy a
|
||||
assert.ok(!h3.unit('g0_a').alive);
|
||||
});
|
||||
|
||||
test('阿戈尔 5 + the devour chain (GitHub #33): a member the devour knocks out revives, its pending marks are cancelled and it stays standing (normal and 联防)', () => {
|
||||
// one row, all facing right: 浊心斯卡蒂 → 乌尔比安 → 幽灵鲨 → 海霓 → 深巡 → 隐现. PRTS 盟约记录 阿戈尔 备注 "标记按付与顺序触发
|
||||
// 【吞噬】效果,目标首次被击倒后解除自身被付与但还未触发的【吞噬】效果"; until 0.1.1 the marks still pending on a revived member
|
||||
// knocked it out again, so all 3 revives went at t = 0 and the members stayed down
|
||||
const ids = ['chess_char_6_04_a', 'chess_char_5_05_a', 'chess_char_2_07_a', 'chess_char_3_09_a', 'chess_char_1_04_a', 'chess_char_1_01_a'];
|
||||
for (const kind of ['normal', 'unite']) {
|
||||
const h = makeBattle({
|
||||
kind, units: ids.map((chessId, i) => ({ chessId, row: 10, col: 2 + i })), bonds: { egirShip: bondOn(5, 0, null, [3, 5]) },
|
||||
hooks: ['damaged', 'death', 'deploy'], captureNoisy: true, autoFinish: false, timeLimit: 60,
|
||||
});
|
||||
h.step(1);
|
||||
const [skadi, ulpia, ghost, hn, deep, fodder] = ids.map((id) => h.unit(id));
|
||||
// 浊心斯卡蒂 marks the whole row and resolves first; every later mark is on a unit already knocked out (乌尔比安's), or
|
||||
// comes from one (幽灵鲨's, 海霓's, 深巡's)
|
||||
assert.deepEqual(tagged(h, 'bond:egir:devour').map((c) => [c.source.defId, c.target.defId]),
|
||||
[ulpia, ghost, hn, deep, fodder].map((t) => [skadi.defId, t.defId]), kind);
|
||||
for (const u of [ghost, hn, deep]) {
|
||||
assert.equal(h.hooksOf('death').filter((c) => c.unit === u && c.reason === 'killed').length, 1, `${kind}: ${u.defId} knocked out once`);
|
||||
assert.equal(h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial).length, 1, `${kind}: ${u.defId} revived at once`);
|
||||
}
|
||||
h.run(3);
|
||||
for (const u of [skadi, ulpia, ghost, hn, deep]) assert.ok(u.alive && u.deployed && u.hp > 0, `${kind}: ${u.defId} standing`);
|
||||
assert.ok(!fodder.alive, `${kind}: the fodder (no member) stays down`);
|
||||
// the 3 revives went to the first knock-outs at t = 0, as officially: the next knock-out of a member is final
|
||||
h.b.dealDamage(null, ghost, { amount: 1e9, type: 'true' });
|
||||
assert.ok(!ghost.alive, `${kind}: no revive left`);
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test('阿戈尔 devour: a unit revived during the pass is out of it — a marker back from the 5-tier revive gives no mark, a target 埃芒加德 revived in place takes none', () => {
|
||||
// g1 (10,3) → g2 (10,4, 3000 × 1.35 HP: knocked out by g1's mark, revived) → g3 (10,5) → fodder (10,6); g4 / g5 face empty tiles
|
||||
const list = [
|
||||
['g1_a', ['egirShip']], ['g2_a', ['egirShip'], { stats: { maxHp: 3000 } }], ['g3_a', ['egirShip'], { stats: { maxHp: 20000 } }],
|
||||
['g4_a', ['egirShip']], ['g5_a', ['egirShip']], ['fod_a', ['preciShip'], { tier: 2, stats: { maxHp: 20000 } }],
|
||||
];
|
||||
const units = [
|
||||
{ chessId: 'g1_a', row: 10, col: 3 }, { chessId: 'g2_a', row: 10, col: 4 }, { chessId: 'g3_a', row: 10, col: 5 }, { chessId: 'fod_a', row: 10, col: 6 },
|
||||
{ chessId: 'g4_a', row: 12, col: 3 }, { chessId: 'g5_a', row: 12, col: 5 },
|
||||
];
|
||||
const h = makeBattle({ defs: defsOf(list), units, bonds: { egirShip: bondOn(5, 0, null, [3, 5]) }, hooks: ['damaged', 'deploy'], captureNoisy: true });
|
||||
h.step(1);
|
||||
const [g2, g3, f] = ['g2_a', 'g3_a', 'fod_a'].map((id) => h.unit(id));
|
||||
assert.ok(g2.alive && h.hooksOf('deploy').some((c) => c.unit === g2 && !c.initial), 'g2 knocked out and revived');
|
||||
// [ASSUMED] (PRTS names only the target): g2's own marks on g3 and the fodder are cancelled with its knock-out
|
||||
assert.deepEqual(tagged(h, 'bond:egir:devour').map((c) => [c.source.defId, c.target.defId]),
|
||||
[['g1_a', 'g2_a'], ['g1_a', 'g3_a'], ['g1_a', 'fod_a'], ['g3_a', 'fod_a']]);
|
||||
close(f.hp, 20000 - 2 * 5000, 1e-6, 'the fodder: g1 and g3 only');
|
||||
close(g2.s.atk, 1000 + 1000 + 1000, 1e-6, 'g2 keeps the base ATK it gained at marking (a persistent buff) [ASSUMED]');
|
||||
assert.equal(h.b.getPlayer('p1').bonds.egirShip.layers, 1 + 1 + 2, 'each devoured unit adds its tier once');
|
||||
checkInvariants(h.b);
|
||||
// 3 members + 埃芒加德 (命结之秘, the first 3 knocked-out operators revive in place — a new deployment, items revivedInPlace):
|
||||
// g1 → g2 → fodder (3000 HP): g1's mark knocks the fodder out, 埃芒加德 revives it, g2's mark on it is cancelled
|
||||
const list2 = [['g1_a', ['egirShip']], ['g2_a', ['egirShip'], { stats: { maxHp: 20000 } }], ['g3_a', ['egirShip']], ['fod_a', ['preciShip'], { tier: 2, stats: { maxHp: 3000 } }]];
|
||||
const units2 = [{ chessId: 'g1_a', row: 10, col: 3 }, { chessId: 'g2_a', row: 10, col: 4 }, { chessId: 'fod_a', row: 10, col: 5 }, { chessId: 'g3_a', row: 12, col: 3 }];
|
||||
const e = makeBattle({ defs: defsOf(list2), units: units2, bandId: 'band_ermengard', bonds: { egirShip: bondOn(3, 0, null, [3, 5]) }, hooks: ['damaged'], captureNoisy: true });
|
||||
e.step(1);
|
||||
const fe = e.unit('fod_a');
|
||||
assert.deepEqual(tagged(e, 'bond:egir:devour').map((c) => [c.source.defId, c.target.defId]), [['g1_a', 'g2_a'], ['g1_a', 'fod_a']]);
|
||||
assert.ok(fe.alive, 'revived in place');
|
||||
close(fe.hp, fe.s.maxHp, 1e-6, 'at full HP: no second mark');
|
||||
assert.deepEqual(e.eventsOf('fx').filter((x) => x[1] === 'revive').map((x) => x[4].left), [2], 'one 埃芒加德 revive spent, not two');
|
||||
checkInvariants(e.b);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// 叙拉古
|
||||
|
||||
|
||||
Reference in new issue
Block a user