feedback6 R21H: the round's enemy effects reach the 频次 器物 and the leaders' mid-fight summons (PR #272, 感谢 @CXUtk); golden: 74 moved

器物 (the 14 hit-count keys): hits = data × the spawn's hpMul ÷ supplyHpMul, rounded as before (setHits) — 攻坚装备 / II / III
leave out only 炎佑, 补给线 / 补给线II also exactly these 14 keys (activity_table aceffect_enemy_2 / 2_2 enemy_exclude,
data/effects.json); until now setHits(def.maxHp) overwrote the round's HP (青铜镜 30 hits at every difficulty). build-data
writes enemyScale[r].supplyHp = 1.2^(kHp − kAtk) · extra (the HP-only 补给线II steps and 补给线's 1.08; co-op 终极 R5–R15
only — the rebuild changed config.json alone), gamedata returns it as supplyHpMul and the wave / 悬赏 mods carry it
(waves.js roundMods; a 频次 enemy's death spawn inherits it). Not taken from the PR: its 1.08 / 1.2 back-calculation (it
counted the 补给线II steps as 攻坚装备) and its fractional-HP ceiling. Leader summons (死亡集群's 妖怪, 刺胄之弹,
“裂管之音”/“断弦之音”, 不祥幻影): the boss / hidden field carries the round's scale as flags.enemyScale (bossRounds.js,
golden fields, tools/balance.mjs) and bosses.js summonMods gives them HP / ATK / speed (死亡集群's summon.hp_ratio on
top; 刺胄之弹 / 余音 hit counts follow their HP); the leader's pool stays exempt; 王权号令's invulnerable equipment
unchanged; 炎佑 is an ally token, never scaled (no change). Unchanged and open: the 逐火 / 再生 husk counts (talent
prop_max_hp).

Golden — 器物 counts: roster 37 (002 003 004 005 007–020 022 023 025 026 029–034 036–041 043 045 049: each holds a 器物
extra spawn / 频次 wave / death spawn), bond-soloShip-high, diy 9 (036 040 056 062 068 073 091 114 115), boss-boss_6-solo
(俗心's 小说卷轴 escorts); matches 16 (all but solo-ABYSS-2 and coop2-NORMAL-70-diy: 频次 rounds / 悬赏 and the Final
Assault summons). Summons: boss-boss_1-pair/solo, hidden-boss_8-pair/solo (妖怪, 刺胄之弹), boss-boss_3-pair/solo,
hidden-boss_10-pair/solo (余音), boss-boss_5-pair/solo (不祥幻影). standins unchanged.
This commit is contained in:
sganggs committed 2026-10-07 18:05:42 +08:00
1 parent 783bd30afd
commit e33ee3db8f
20 files changed
+1491 -1324

No files matched your search

+7 -1
View File
@@ -397,14 +397,20 @@ export class GameData {
return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : null;
}
/** Official enemy multipliers of round r (config enemyScale: the PRTS table + 终极 speed ×1.15 from R3). */
/**
* Official enemy multipliers of round r (config enemyScale: the PRTS table + 终极 speed ×1.15 from R3). `supplyHpMul`
* (only when ≠ 1: co-op 终极 R5–R15) = the share of hpMul from 补给线 / 补给线II (config `supplyHp`), effects whose
* `enemy_exclude` leaves out the 14 器物 hit-count keys — they take hpMul / supplyHpMul (archetypes.js `times`).
*/
baseEnemyScale(r) {
const e = this.mode.enemyScale && this.mode.enemyScale[String(r)];
if (!e || typeof e !== 'object') return { hpMul: 1, atkMul: 1, speedMul: 1 };
const supply = numOr(e.supplyHp, 1);
return {
hpMul: Math.max(0.01, numOr(e.hp, 1)),
atkMul: Math.max(0, numOr(e.atk, 1)),
speedMul: Math.max(0.01, numOr(e.speed, 1)),
...(supply > 0 && supply !== 1 ? { supplyHpMul: supply } : {}),
};
}
+2 -1
View File
@@ -99,7 +99,8 @@ export class MatchBoss {
spawns: this._sanitizeSpawns(spawns),
routes: wave.routes,
sharedBoss: this.bossPool,
flags: { layerGainsEnabled: false, ...this.gd.dp },
// the round's enemy effects for the leaders' mid-fight summons (server/sim/content/bosses.js summonMods)
flags: { layerGainsEnabled: false, ...this.gd.dp, enemyScale: this.gd.enemyScale(this.round) },
fieldId,
enemyOverrides: wave.overrides,
waveId: wave.templateId,
+11 -3
View File
@@ -234,6 +234,13 @@ export function scaleFor(gd, r) {
return gd.enemyScale(r);
}
/** The spawn mods of a round's scale for a non-leader enemy: HP / ATK / speed, plus `supplyHpMul` when the round has one. */
export function roundMods(scale) {
const m = { hpMul: scale.hpMul, atkMul: scale.atkMul, speedMul: scale.speedMul };
if (scale.supplyHpMul != null) m.supplyHpMul = scale.supplyHpMul;
return m;
}
/** Slot class of an enemy that has no placeholder slot (literal template keys, bounty adds). */
export function classOf(gd, enemyKey) {
const e = gd.enemy(enemyKey);
@@ -327,8 +334,9 @@ function templateSpawns(gd, tpl, round, pick) {
interval: count > 1 ? step : 0,
// the round multipliers are ENEMY effects on every enemy but 炎佑 (aceffect_enemy_1–5 `enemy_attribute_mul`,
// enemy_exclude = enemy_9012_acloon): leader parts take them all; the leader takes ATK / speed but not HP — its HP
// is the server pool, "领袖单位于服务器的生命值加成不受上述加成影响" (PRTS 下半)
mods: isBoss ? { atkMul: scale.atkMul, speedMul: scale.speedMul, slot } : { hpMul: scale.hpMul, atkMul: scale.atkMul, speedMul: scale.speedMul, slot },
// is the server pool, "领袖单位于服务器的生命值加成不受上述加成影响" (PRTS 下半). `supplyHpMul` (roundMods) rides along for
// the 器物 hit-count units, which 补给线 / 补给线II leave out (archetypes.js `times`; also a 频次 enemy's death spawn)
mods: isBoss ? { atkMul: scale.atkMul, speedMul: scale.speedMul, slot } : { ...roundMods(scale), slot },
actionIndex: i,
preview: previewInfo(gd, key, routes[routeIndex], isBoss, leader),
};
@@ -528,7 +536,7 @@ function bountyPlan(gd, round, wave, bounties, playerId, side) {
routeIndex,
count: run.len,
interval: run.len > 1 ? step : 0,
mods: { hpMul: scale.hpMul, atkMul: scale.atkMul, speedMul: scale.speedMul, slot: classOf(gd, c.enemyKey), bountyId: b.id },
mods: { ...roundMods(scale), slot: classOf(gd, c.enemyKey), bountyId: b.id },
tag: 'bounty',
ownerPlayerId: playerId,
preview: previewInfo(gd, c.enemyKey, routes[routeIndex], false, leader),
+24 -7
View File
@@ -240,6 +240,23 @@ function branchSpawn(b, tpl, name, phaseIdx, { fallback = null, mods = null, at
return out;
}
/**
* Spawn mods of a leader's mid-fight summon (死亡集群's 妖怪, 刺胄之弹, “裂管之音”/“断弦之音”, 不祥幻影): the round's enemy
* effects (攻坚装备 / II / III, 补给线 / II, 急行军 — `enemy_attribute_mul` / `enemy_move_speed_mul` on 所有敌人, `enemy_exclude`
* only 炎佑 and, for 补给线, the 器物) reach a summon like any enemy; only the leader's own HP, the server pool, is exempt
* ("领袖单位于服务器的生命值加成不受上述加成影响", PRTS 下半). They come as the battle's `flags.enemyScale` (gd.enemyScale of the
* boss round, set by the match — a BattleSpec flag, so browsers run the same); `hpRatio` = the skill's own summon HP ratio
* (死亡集群 `summon.hp_ratio`), multiplied in. No flags (tests, tools): the ratio alone. Until 0.2.1 the summons took no
* round effect at all (PR #272). 王权号令's equipment is left out: invulnerable, untargetable, it never fights.
*/
function summonMods(b, hpRatio = null) {
const s = b.flags && b.flags.enemyScale;
if (!s || typeof s !== 'object') return hpRatio ? { hpMul: hpRatio } : null;
const m = { hpMul: (Number.isFinite(s.hpMul) && s.hpMul > 0 ? s.hpMul : 1) * (hpRatio || 1), atkMul: s.atkMul, speedMul: s.speedMul };
if (s.supplyHpMul != null) m.supplyHpMul = s.supplyHpMul;
return m;
}
// ---------------------------------------------------------------------------------------------------------------
// install
@@ -398,8 +415,8 @@ function kitHelm(ab, e, b, tpl) {
const hpMul = s2.bb['summon.hp_ratio'] > 0 ? s2.bb['summon.hp_ratio'] : null;
const key = s2.bs.enemy_key ?? 'enemy_1005_yokai';
const drones = branchSpawn(b2, tpl, s2.bs.branch_id ?? 'boss_summon_enemy', 0, {
mods: hpMul ? { hpMul } : null, at: { x: e2.x, y: e2.y },
fallback: () => [b2.spawnEnemy(key, { pos: [e2.y, e2.x], route: toGoal(b2, e2.x, e2.y, 'FLY'), mods: hpMul ? { hpMul } : null })].filter(Boolean),
mods: summonMods(b2, hpMul), at: { x: e2.x, y: e2.y },
fallback: () => [b2.spawnEnemy(key, { pos: [e2.y, e2.x], route: toGoal(b2, e2.x, e2.y, 'FLY'), mods: summonMods(b2, hpMul) })].filter(Boolean),
});
b2.fx('summon', { x: e2.x, y: e2.y, id: e2.id, key, n: drones.length });
const ratio = s2.bb.hp_ratio ?? 0;
@@ -429,7 +446,7 @@ export function droneLinkBase(boss, base = DRONE_LINK_BASE) {
/** Launch a 刺胄之弹 from `boss` at `target`'s tile. */
function fireShell(b, boss, target) {
const tr = target.tileR, tc = target.tileC;
const sh = b.spawnEnemy(SHELL_KEY, { pos: [boss.y, boss.x], route: { motion: 'FLY', start: [boss.y, boss.x], end: [tr, tc], steps: [{ t: 'wait', s: 99999 }] }, tag: 'part', countInTotal: false, ownerPlayerId: boss.ownerId });
const sh = b.spawnEnemy(SHELL_KEY, { pos: [boss.y, boss.x], route: { motion: 'FLY', start: [boss.y, boss.x], end: [tr, tc], steps: [{ t: 'wait', s: 99999 }] }, tag: 'part', countInTotal: false, ownerPlayerId: boss.ownerId, mods: summonMods(b) });
if (!sh) return null;
sh.mem.ab.shell = { tr, tc, boss };
b.fx('shell', { x: boss.x, y: boss.y, id: sh.id, tx: tc, ty: tr, r: 1.5, kind: 'helmShell' });
@@ -440,7 +457,7 @@ function kitShell(ab, e) {
const stun = T(ab, 'killed.duration') ?? 0, dot = T(ab, 'killed.value') ?? 0;
return [{
spawn(b, e2) {
setHits(e2, e2.def.maxHp); hitCount(b, e2, true); // 需要数次攻击击倒
setHits(e2, e2.base.maxHp); hitCount(b, e2, true); // 需要数次攻击击倒 (× the round's HP: summonMods)
b.addBuff(e2, { key: 'boss:shellGuard', persist: true, flags: { noDisplace: true } }); // 失衡免疫 (PRTS 天赋; also 静态刚体)
},
tick(b, e2, a, dt) {
@@ -762,7 +779,7 @@ function kitEcho(ab, e) {
const s = ab.sk.Skill;
return [
{
spawn(b, e2) { setHits(e2, e2.def.maxHp); hitCount(b, e2, true); e2.blockWeight = ECHO_BLOCK_WEIGHT; setEchoForm(b, e2, 'dark'); },
spawn(b, e2) { setHits(e2, e2.base.maxHp); hitCount(b, e2, true); e2.blockWeight = ECHO_BLOCK_WEIGHT; setEchoForm(b, e2, 'dark'); }, // hits × the round's HP (summonMods)
taken(c, b, e2) { const s = c.source || c.credit; if (s && s.side === 'ally') echoHit(b, e2); }, // 受到伤害时以自身为中心造成一次范围伤害 (a 无来源 burst too)
},
s && {
@@ -784,7 +801,7 @@ function pipeCore(ab, e, b, tpl, { form, prefix }) {
const key = ab.tS['1.enemy_key'] ?? ECHO_KEY;
const P = { acc: 0, atk: Infinity }; // first strike as soon as an echo of its form exists (engine attack rule)
const summon = (b2, e2) => {
const got = branchSpawn(b2, tpl, 'summon_enemy', 0, { at: { x: e2.x, y: e2.y }, fallback: () => [b2.spawnEnemy(key, { pos: [e2.y, e2.x], route: toGoal(b2, e2.x, e2.y) })].filter(Boolean) });
const got = branchSpawn(b2, tpl, 'summon_enemy', 0, { mods: summonMods(b2), at: { x: e2.x, y: e2.y }, fallback: () => [b2.spawnEnemy(key, { pos: [e2.y, e2.x], route: toGoal(b2, e2.x, e2.y), mods: summonMods(b2) })].filter(Boolean) });
for (const u of got) if (u.defId === ECHO_KEY && form === 'gold') setEchoForm(b2, u, 'gold');
b2.fx('summon', { x: e2.x, y: e2.y, id: e2.id, key, n: got.length });
};
@@ -962,7 +979,7 @@ function kitLucien(ab, e) {
const route = remainingRoute(e2);
const from = blinkForward(b, e2, s.bb.dist ?? 1.5);
if (!from) return;
const ph = b.spawnEnemy(PHANTOM_KEY, { pos: [from.y, from.x], route, ownerPlayerId: e2.ownerId, sourcePlayerId: e2.sourcePlayerId });
const ph = b.spawnEnemy(PHANTOM_KEY, { pos: [from.y, from.x], route, ownerPlayerId: e2.ownerId, sourcePlayerId: e2.sourcePlayerId, mods: summonMods(b) });
if (ph) b.fx('summon', { x: from.x, y: from.y, id: e2.id, key: PHANTOM_KEY, n: 1 });
},
}];
+15 -2
View File
@@ -34,8 +34,21 @@ const unblockable = () => ({ spawn(b, e) { b.addBuff(e, { key: 'ab:unblockable',
*/
const runWhenHit = (v) => ({ taken(c, b, e, a) { if (a.on || !(v > 0)) return; a.on = true; b.addBuff(e, { key: 'ab:duckRun', persist: true, mods: { moveMul: 1 + v } }); } });
/** 频次: maxHp = hits. */
const times = (artsOnly = false) => ({ spawn(b, e) { setHits(e, e.def.maxHp); hitCount(b, e, true, artsOnly); } });
/**
* 频次 (the 14 器物 keys, kitTimes): maxHp = hits = the data's max HP × the spawn's HP multiplier. The round effects reach
* these units like any enemy (攻坚装备 / II / III leave out only 炎佑), but not 补给线 / 补给线II, whose `enemy_exclude` lists
* exactly these 14 keys (activity_table aceffect_enemy_2 / 2_2, data/effects.json): that share (`mods.supplyHpMul`, co-op
* 终极 R5–R15) comes out again — a 频次 enemy's death spawn inherits the parent's mods, so it travels with them. Whole hits:
* setHits rounds [ASSUMED: no source says how a fractional count rounds]. Until 0.2.1 the count was the data's whatever
* the difficulty (PR #272).
*/
const times = (artsOnly = false) => ({
spawn(b, e) {
const s = Number(e.mods && e.mods.supplyHpMul);
setHits(e, e.base.maxHp / (Number.isFinite(s) && s > 0 ? s : 1));
hitCount(b, e, true, artsOnly);
},
});
/** "只能被阻挡数大于等于N的单位阻挡". */
const blockWeight = (n) => ({ spawn(b, e) { e.blockWeight = n; } });
+1 -1
View File
@@ -302,7 +302,7 @@ export function stepToward(u, tx, ty, dist) {
return false;
}
/** Set a 频次 unit's hits (maxHp := hits, full). */
/** Set a 频次 unit's hits (maxHp := hits, full): `n` rounded to a whole count, at least 1. */
export function setHits(e, n) {
const h = Math.max(1, Math.round(n));
e.base.maxHp = h;