From 50245590281fc92048ebd1c0a1b26707e0ab1c5f Mon Sep 17 00:00:00 2001 From: sganggs Date: Mon, 5 Oct 2026 16:20:06 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20R3:=20docs=20and=20comments=20follo?= =?UTF-8?q?w=20the=20split=20=E2=80=94=20DESIGN=20=C2=A72=20and=20SIM.md?= =?UTF-8?q?=20list=20server/sim/battle/=20and=20content/enemies/,=20SIM.md?= =?UTF-8?q?=20and=20research=2008=20name=20the=20new=20homes=20(archetypes?= =?UTF-8?q?.js=20frontGuard=20/=20ep=20/=20dmgZone=20/=20pollution,=20help?= =?UTF-8?q?ers.js=20areaAllies=20/=20zone=20/=20setForm,=20fly.js=20kitShe?= =?UTF-8?q?ll),=20code=20comments=20that=20pointed=20at=20moved=20code=20s?= =?UTF-8?q?ay=20where=20it=20lives=20now=20(enemies.js=20header,=20battle/?= =?UTF-8?q?blocking.js,=20battle/queries.js,=20sim=20constants=20/=20snaps?= =?UTF-8?q?hot=20/=20units=20/=20ai=20/=20targeting=20/=20devices=20/=20bo?= =?UTF-8?q?sses,=20shared=20constants=20/=20protocol,=20render=20style=20/?= =?UTF-8?q?=20interp);=20.git-blame-ignore-revs=20lists=20the=20two=20pure?= =?UTF-8?q?-move=20commits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .git-blame-ignore-revs | 8 ++++++++ docs/DESIGN.md | 8 ++++++-- docs/SIM.md | 22 +++++++++++++--------- docs/research/08-waves-official.md | 2 +- public/js/render/interp.js | 2 +- public/js/render/style.js | 4 ++-- server/sim/ai.js | 2 +- server/sim/battle/blocking.js | 2 +- server/sim/battle/queries.js | 2 +- server/sim/constants.js | 4 ++-- server/sim/content/bosses.js | 2 +- server/sim/content/devices.js | 2 +- server/sim/content/enemies.js | 8 ++++---- server/sim/snapshot.js | 4 ++-- server/sim/targeting.js | 6 +++--- server/sim/units.js | 2 +- shared/constants.js | 2 +- shared/protocol.js | 2 +- 18 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..14a5a471 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +# Commits that only move code between files (0.2.0 refactor). Blame skips them when asked to: +# git config blame.ignoreRevsFile .git-blame-ignore-revs (GitHub's blame view reads this file by itself) +# For lines that moved into a new file, add -C -C (git blame -C -C ) to follow them to their old file. + +# feedback5 R3: server/sim/Battle.js → server/sim/battle/*.js (the Battle methods, verbatim) +13224f9483d94f65089e289ff2be63f50a69b1cb +# feedback5 R3: server/sim/content/enemies.js → server/sim/content/enemies/*.js (helpers, archetypes, kit families) +560328cacc863c26d07780096527e255ebf62bc4 diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 54821f00..5315c7df 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -59,7 +59,9 @@ server/ results.js settlement stats, titles (评语) bot.js AI player (plays prep phases; also auto-play for disconnected humans) sim/ - Battle.js one field simulation (normal / unite / boss); public API §5 + Battle.js one field simulation (normal / unite / boss); public API §5 — the class: constructor + method install + battle/ Battle's methods by concern (players, lifecycle, hooks, spawns, deploy, blocking, status, combat, + queries, summons, tiles, displacement, economy, events), installed on Battle.prototype constants.js TICK, conversions, tuning knobs rng.js seeded PRNG (mulberry32) + helpers grid.js field grid, tile queries, passability, official 4-direction SPFA flow field + line-of-sight smoothing, road-over-floor preference only where it crosses fewer floor tiles (§21.15); DEPLOY_REFUSED_TILES (深水区, §21.3) @@ -81,7 +83,9 @@ server/ garrisons.js battle side (IN_BATTLE) garrison effect keys items.js battle side of equipment + Arts bands.js battle side of band (strategy) effects - enemies.js enemy ability specs + generic special-type behaviours + enemies.js enemy ability specs + generic special-type behaviours: dispatch hooks, KITS, STATS_ONLY + enemies/ helpers.js, archetypes.js, one kit file per special type (invisible, times, element, dot, + reflection, fly, special) and leaders.js — each with its part of KITS bosses.js boss scripts (boss_1 … boss_10) devices.js stage devices & special terrain shared/ diff --git a/docs/SIM.md b/docs/SIM.md index 65c4c99a..70e5ca60 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -7,7 +7,10 @@ the test harness. Everything here is deterministic: the only randomness is `batt ``` server/sim/ - Battle.js one field (normal / unite / boss / hidden) — public API, hook bus, helpers + Battle.js one field (normal / unite / boss / hidden) — the class: constructor, method install, public API + battle/ Battle's methods by concern: players, lifecycle (start / step / end), hooks (bus, timers, callback + isolation), spawns, deploy, blocking, status (buffs, statuses), combat, queries (+ ranges), summons, + tiles (relocation, bodies, tactical points), displacement, economy (DP, layers, coins), events constants.js TICK, MOVE_SCALE, ATTACK_PAUSE, element numbers, tuning knobs rng.js mulberry32 PRNG (+ int/range/chance/pick/shuffle/weighted) grid.js stage grid, tile semantics, 8-dir A* (no corner cutting), obstacles @@ -25,6 +28,7 @@ server/sim/ snapshot.js wire format (UnitInfo, snapshot tuples, flags, anim codes) simdata.js data access + normalisation (data/*.json, research fallback) content/index.js installContent / setupUnitKit / registerAllMeta + content/enemies/ helpers.js, archetypes.js, one kit file per special type + leaders.js (content/enemies.js builds KITS) content/generic.js generic kit from skill blackboards content/kits/tier1..6.js, content/{tokens,bonds,garrisons,items,bands,enemies,bosses,devices,choices}.js (content phase) ``` @@ -137,7 +141,7 @@ passage must follow; test/ui/playtest6_summons.test.js checks). "左右两格" = `offsetTile(r, c, ±1, 0, dir)` (`support.sideTiles`); kit pushes / pulls use `unit.fwd`; blowers compare directions (same ⇒ `equal`, reverse ⇒ `opposite`, perpendicular ⇒ `vertical`); board-position rules ("更靠左", "最右边", "同一行最右边") stay board positions in the player's own frame (mirrored FA side: counted from the field's other end), -independent of the units' directions. Enemies keep their own horizontal facing logic (content/enemies.js frontGuard). +independent of the units' directions. Enemies keep their own horizontal facing logic (content/enemies/archetypes.js frontGuard). "First tile" tie-breaks relative to a unit (tactical points `findTacticalPoint`, summon tiles `findSummonTile`, the 突袭 landing tile) compare offsets in the unit's facing-RIGHT frame (`localOrder` / `localBefore`; for a RIGHT unit exactly the old tile-key order), so a rotated layout plays the same (test/sim/facing-invariance.test.js: every chess × 4 @@ -267,7 +271,7 @@ a stealthed ally (隐匿, 排气格栅) only for the enemy it blocks — our ope (attacks, skill picks, cast conditions, a normal attack on every operator in range — 斩胄之剑 / 破胄之锤's hover attack, “灵幛”). **Enemy area effects** — splash, death and self blasts, area skills and statuses, pulses, the zones an enemy leaves, chain / bounce jumps, 周围四格 additions, whole-column / whole-field skills — select with `targeting.js -areaSelectable` (`content/enemies.js areaAllies` / `areaAlliesInTiles` / `fieldAllies`; PRTS 作战机制 §AOE伤害判定 +areaSelectable` (`content/enemies/helpers.js areaAllies` / `areaAlliesInTiles` / `fieldAllies`; PRTS 作战机制 §AOE伤害判定 "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定"; PRTS 异常效果 §无法选择: 隐匿, 不可选中, 无敌 and 对地规避 make "常见的、来自不同阵营的“选择”行为" skip a unit unless the ability "无视可选性"): no 隐匿 ally, the one blocking that enemy included (GitHub #97, owner 2026-10-04; the 0.1.2 [ASSUMED] that the blocker's area also hit is withdrawn — the blocked enemy's attack still lands), no untargetable or sleeping one, no @@ -291,7 +295,7 @@ DISAPPEAR / APPEAR legs still happen, hiding ends the stand; GitHub #58 — then never attack — unless content arms them through `enemy.profile` (`noAttack: false`, `melee`, `dmgType`, `maxTargets`: 转译基底·α's 寻仇者 / 特战术师 forms, which then attack like any enemy); `dmgType 'heal'` enemies heal the lowest-HP% enemy in their radius instead. A `noMove` enemy stands (not `moving`, drawn idle). Content can take over an enemy's attack: `enemy.profile.deferHit` = the engine makes the attack (target, timing, the `'atk'` event) but deals no -damage — the content's `attack` handler resolves it (帝国炮火先兆者's shells landing 3 s later, `content/enemies.js +damage — the content's `attack` handler resolves it (帝国炮火先兆者's shells landing 3 s later, `content/enemies/fly.js kitShell`); `enemy.profile.shot` = the `'atk'` event's projectile kind (`'mortar'`: no projectile drawn). 暴鸰 (`kitBombd`, no normal attack) drops its one bomb as a projectile: the cast (trigger: an ally within its range 2; the drone hovers through it) releases it `BOMBD_RELEASE` (8 ticks, the Attack clip's OnAttack on frame 8) later — `'atk'` kind @@ -306,7 +310,7 @@ present when data/stages.json says `active: true` (this wins over the level file research stages without `active` use `!hidden`. Active platforms/mounds (射击台, act1 m03) [ASSUMED, DATA §15.11] are ground obstacles, and an operator standing on one is elevated (`unit.ground = false`: never blocks). -**Enemy damage zones** (`content/enemies.js zone` / `dmgZone` / `pollution`): a zone ticks on the allies inside it +**Enemy damage zones** (`content/enemies/helpers.js zone`, `archetypes.js dmgZone` / `pollution`): a zone ticks on the allies inside it that it selects — an enemy's zone through its area selection (`areaAllies`, above; the sourceless 毒雾 with no selecting enemy: 隐匿 kept out, 起飞 not), 【污染秽蚀】 on every ally inside (flyers, stealthed and untargetable ones included; `alliesInRadius`) — through `dealDamage`, so shields absorb a damage @@ -323,7 +327,7 @@ one; the sourceless 毒雾 of 假想敌:蚀裂 skips a 隐匿 one but still re kind of damage — 无来源 true (like the terrain it stands for [ASSUMED]), the chimera credited — not a 流失 (player report D1 audit): radius 1.2, a tick every 0.5 s, one tick per unit per 0.5 s however many chimeras reach it (`mem.chimeraAt`). -**Knock-outs that are not deaths** (`content/enemies.js`; player reports after 0.1.0): a `killed` ability that keeps the +**Knock-outs that are not deaths** (`content/enemies/archetypes.js`; player reports after 0.1.0): a `killed` ability that keeps the enemy alive hides the knock-out from every later `kill` handler, the kill count, kill credit and the bounty — they all wait for the real death, the only one with a `die` event. Every 重生 (`reborn()`, `husk()`, `statue()`) clears what operators put on the enemy — the buffs with an ally source and source-less catalogue statuses (PRTS 特殊机制 §重生 @@ -598,7 +602,7 @@ gain is amount × `dmg.mul` × `elemTakenMul` (元素损伤倍率: "受到的元 改为目标的损伤抵抗即可"); `elementIntake(unit)` in damage.js returns that factor, applied after `elementHit`). 元素脆弱 never scales it, no gauge decays (EP_RECOVERY_PER_SEC 0), and every enemy in data/enemies.json has 损伤抵抗 0 except 转译基底·α (10); operators have none. -Enemies deal ATK × their talent's `ep_damage_ratio` per hit (content/enemies.js `ep`). A full gauge bursts with the +Enemies deal ATK × their talent's `ep_damage_ratio` per hit (content/enemies/archetypes.js `ep`). A full gauge bursts with the official effects, which depend on the side hit (constants.js `ELEMENT`); burst damage is **无来源** (DamageInfo `sourceless`, PRTS 伤害分类 "无法被追溯伤害来源": no damage-dealt multiplier or penetration of the unit that filled the gauge, and the `hit` / `damaged` / `fatal` hooks see `source: null` — no attacker-keyed content applies — while the ctx's @@ -1175,9 +1179,9 @@ Unknown subprofessions fall back to the profession default (test `professions.te e.g. `mortar` for 帝国炮火先兆者, which the renderer does not draw — its fx `bombardShell` is the shell), `['dmg', tgt, amount, type]` (`phys|arts|true|burn|neural|necrosis|apoptosis`), `['heal', tgt, amount]`, `['skill', id, 1|0]`, `['die', id, reason]`, `['leak', id]`, `['status', id, key, 1|0]`, `['fx', kind, x, y, extra]` (`hitCap` `{ id, n }`: a leader's hit cancelled by 限伤 — the renderer draws nothing; - `extra.form` = the unit's model form from then on — an enemy's `content/enemies.js setForm`, a 傀儡师's 替身 — `shared/protocol.js fxForm`), + `extra.form` = the unit's model form from then on — an enemy's `content/enemies/helpers.js setForm`, a 傀儡师's 替身 — `shared/protocol.js fxForm`), `['layer', playerId, bondId, n]` (n = the layers actually added, capped at 999), `['bounty', playerId, coins]`. -- `UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, form?, skillIndex?, moduleId?, items? }` (`skillIndex`: an ally's equipped skill, DESIGN §16; `form`: the unit's current model form — `content/enemies.js setForm`: 掠海漂移体 `'crawl'`, 暴鸰 `'bombed'` after its drop, 转译基底·α's forms …; a 傀儡师 fighting as its 替身 `'doll'` — so a view built mid-battle from `fieldMeta()` starts on that clip set; `items`: an ally operator's equipped item ids — a 变形同构体 wearer is a member of the bond it grants on the client too, the bond popup and the detail card's chips) +- `UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, form?, skillIndex?, moduleId?, items? }` (`skillIndex`: an ally's equipped skill, DESIGN §16; `form`: the unit's current model form — `content/enemies/helpers.js setForm`: 掠海漂移体 `'crawl'`, 暴鸰 `'bombed'` after its drop, 转译基底·α's forms …; a 傀儡师 fighting as its 替身 `'doll'` — so a view built mid-battle from `fieldMeta()` starts on that clip set; `items`: an ally operator's equipped item ids — a 变形同构体 wearer is a member of the bond it grants on the client too, the bond popup and the detail card's chips) (`dir` = the unit direction, allies meaningful, enemies 'RIGHT'; `facing` = its horizontal sign for sprite flipping) (`spine`/`avatar` are asset ids from data). - flags: UF bits (blocked 1, stunned 2, frozen 4, stealth 8 — 隐匿 (an enemy's only while not blocked / revealed and not within 3 s of a block's end) or an ally's 迷彩 — skill 16, shield 32, invuln 64, cold 128, sleep 256, flying 512); diff --git a/docs/research/08-waves-official.md b/docs/research/08-waves-official.md index fc4fc0b4..0ed9f843 100644 --- a/docs/research/08-waves-official.md +++ b/docs/research/08-waves-official.md @@ -554,7 +554,7 @@ Everything in `docs/BALANCE.md` §4 (the tuned tables) and the `tools/balance.mj - `config.bossHpScale`: co-op pool = `bloodPoint[difficulty]` with no alive-player factor; solo stays a flagged config value. (2026-10-01: × alive / 4 available behind `aliveScaling`, off, see §6 #7.) - Keep `enemyScale` (PRTS table) and the 终极 speed ×1.15 from R3. - Re-run `tools/balance.mjs --tuning off` only to report, not to tune. -3. **Pathing** (`server/sim/grid.js`, `server/sim/ai.js`, `server/sim/Battle.js`): +3. **Pathing** (`server/sim/grid.js`, `server/sim/ai.js`, `server/sim/Battle.js` — since 0.2.0 `server/sim/battle/spawns.js` / `tiles.js`): - Add `Grid.flowField(dest)` as in §3.4, cached per `(dest, version)`. - `planLeg` for WALK: follow the smoothed parents from the enemy's tile to the leg target and re-plan on version change. That is the same trigger as today. - Crates: `setObstacle` marks a cost-1000 obstacle-like tile, not an impassable one. The existing "blocked enemy attacks the crate" logic stays. diff --git a/public/js/render/interp.js b/public/js/render/interp.js index 1ab8cc28..a3d99c8a 100644 --- a/public/js/render/interp.js +++ b/public/js/render/interp.js @@ -21,7 +21,7 @@ // included — are always delivered, and a full queue sheds only cosmetic ones. // // Snapshot tuple layout (DESIGN §8.2): [id, x, y, hp, maxHp, sp, spMax, flags, anim]. Two optional lists ride along -// (server/sim/Battle.js snapshot, user playtest #4 items 8 / 9): +// (server/sim/battle/events.js snapshot, user playtest #4 items 8 / 9): // * `elem` [[id, element, fill, cooldownEnd, cooldown]] — the element gauge a unit shows: appended to that unit's // normalised tuple (EL…EL_DUR) and handed out by sample() as `el`, `elFill`, `elUntil`, `elDur` (from the older // snapshot, like flags); diff --git a/public/js/render/style.js b/public/js/render/style.js index 5bfbdaa4..4f53e9ad 100644 --- a/public/js/render/style.js +++ b/public/js/render/style.js @@ -113,7 +113,7 @@ export const PROJ = Object.freeze({ drone: { look: 'dart', speed: 16, tint: 0xe4fbff, glow: 0x57c9ff, len: 0.7, width: 0.16, head: 0.3, trail: 0x57c9ff, hit: 'zap' }, enemy: { look: 'orb', speed: 10, tint: 0xffe2da, glow: 0xff3b30, len: 0.55, width: 0.28, head: 0.44, trail: 0xff4a3a, muzzle: 0xff6a5a, hit: 'enemy' }, boomerang: { look: 'boomerang', speed: 15, back: 3.75, tint: 0xfff4d6, glow: 0x9ff0dc, len: 0.4, width: 0.3, head: 0.5, trail: 0x9ff0dc, hit: 'spark' }, - // 暴鸰's bomb (sim content/enemies.js kitBombd; official projectile_bombd, speed 5): dropped from the drone, it falls + // 暴鸰's bomb (sim content/enemies/fly.js kitBombd; official projectile_bombd, speed 5): dropped from the drone, it falls // onto its target with a low arc and bursts where the sim's 'explode' blast goes off droneBomb: { look: 'shell', speed: 5, once: true, tint: 0xffe2c8, glow: 0xff5a3a, len: 0.6, width: 0.3, head: 0.5, trail: 0xff7a4a, arc: 0.35, smoke: 0x2e2824, hit: 'boom' }, }); @@ -143,7 +143,7 @@ const STATUS_GUESS = [ ]; /** - * The 折射 icon is not drawn while the unit is silenced: the RES bonus is already off (enemies.js refraction) + * The 折射 icon is not drawn while the unit is silenced: the RES bonus is already off (sim content/enemies/archetypes.js refraction) * and the status must not keep looking active. Other icons stay. * @param {string} key a b.ev status key * @param {Set|string[]|null} statuses diff --git a/server/sim/ai.js b/server/sim/ai.js index fe9a1ab1..f40b4882 100644 --- a/server/sim/ai.js +++ b/server/sim/ai.js @@ -635,7 +635,7 @@ function enemyAttack(b, e) { const radius = melee ? 0 : e.base.rangeRadius; // the range circle takes an ally whose 0.25 collider touches it (PRTS 作战机制 §碰撞体积: 索敌 uses the colliders) const reach = radius > 0 ? radius + ALLY_COLLIDER_RADIUS : 0; - // `e.profile.canTarget(ally)`: the enemy's own target rule (只攻击地面单位, 不会攻击飞行单位 …; content/enemies.js), + // `e.profile.canTarget(ally)`: the enemy's own target rule (只攻击地面单位, 不会攻击飞行单位 …; content/enemies/*.js), // applied to the candidates before the priority sort and the target count const own = e.profile && typeof e.profile.canTarget === 'function' ? e.profile.canTarget : null; if (e.atkCd > 0) { diff --git a/server/sim/battle/blocking.js b/server/sim/battle/blocking.js index 369e41f3..53b81c59 100644 --- a/server/sim/battle/blocking.js +++ b/server/sim/battle/blocking.js @@ -104,7 +104,7 @@ export class BattleBlocking { * A block on enemy `e` just ended (every release goes through here: `_unblock`, `releaseBlocked`, ai.js * enforceBlockCapacity). Each 隐匿 source it holds stays switched off for its restore time — PRTS 作战机制 §隐匿 "对于 * 绝大部分可隐匿的敌人而言,在被我方单位阻挡后会解除隐匿,不被阻挡的3秒后重新进入隐匿" (STEALTH_RESTORE), or the source's - * own "(解除阻挡N秒后恢复)" (buff `data.stealthRestore`, content/enemies.js: 0 s / 1 s on some enemy pages) — as a + * own "(解除阻挡N秒后恢复)" (buff `data.stealthRestore`, content/enemies/helpers.js STEALTH_RESTORE_BY_KEY: 0 s / 1 s on some enemy pages) — as a * `stealthOff` buff per source; meanwhile it is targetable, operator splash reaches it and it is drawn solid * (targeting.js enemyStealthed). A new block inside the window lifts it again and its end restarts the window. Our * operators' 隐匿 / 迷彩 are never lifted by blocking (only enemies get here). diff --git a/server/sim/battle/queries.js b/server/sim/battle/queries.js index f5b36283..cf0ba4ba 100644 --- a/server/sim/battle/queries.js +++ b/server/sim/battle/queries.js @@ -151,7 +151,7 @@ export class BattleQueries { /** * Every deployed ally within `r` of (x, y) — no selection rule: an enemy's area effect selects among them with - * targeting.js areaSelectable (content/enemies.js areaAllies: no 隐匿 ally, the blocker included, GitHub #97; DESIGN §22.12). + * targeting.js areaSelectable (content/enemies/helpers.js areaAllies: no 隐匿 ally, the blocker included, GitHub #97; DESIGN §22.12). */ alliesInRadius(x, y, r, ownerId = null, { includeDevices = false } = {}) { const out = []; diff --git a/server/sim/constants.js b/server/sim/constants.js index e6229ace..3308a55d 100644 --- a/server/sim/constants.js +++ b/server/sim/constants.js @@ -38,7 +38,7 @@ export const BLOCK_RADIUS_SQ = Object.freeze({ ground: 0.49999037, fly: 0.799951 /** * An enemy's 隐匿 after a block ends (s): PRTS 作战机制 §隐匿 "对于绝大部分可隐匿的敌人而言,在被我方单位阻挡后会解除隐匿,不被 * 阻挡的3秒后重新进入隐匿" / §隐匿与Buff的关系 "阻挡状态解除后3s开关重新被开启而恢复隐匿". An enemy page's "(解除阻挡N秒后 - * 恢复)" overrides it per 隐匿 source (buff `data.stealthRestore`: content/enemies.js). Battle._stealthSwitch; our + * 恢复)" overrides it per 隐匿 source (buff `data.stealthRestore`: content/enemies/helpers.js STEALTH_RESTORE_BY_KEY). Battle._stealthSwitch; our * operators' 隐匿 / 迷彩 are never lifted by blocking ("我方干员并不会因为阻挡而解除隐匿"). */ export const STEALTH_RESTORE = 3; @@ -48,7 +48,7 @@ export const PROJECTILE_SPEED = 12; * Projectile speeds per visual kind (tiles/s). `none`/`beam` are instant. `boomerang` (回环射手 跃跃) is the OUTBOUND * flight to the target — PRTS 跃跃 特性 note "投射物飞行速度15,返回时飞行速度3.75"; the way back is * BOOMERANG_RETURN_SPEED (ai.js throwBoomerang). `droneBomb` = 暴鸰's bomb (the official projectile_bombd `_speed` 5; - * content/enemies.js kitBombd). + * content/enemies/fly.js kitBombd). */ export const PROJECTILE_SPEEDS = Object.freeze({ arrow: 14, bolt: 11, bomb: 8, lob: 8, orb: 10, drone: 16, enemy: 10, boomerang: 15, droneBomb: 5 }); /** 回环射手: speed (tiles/s) of a boomerang flying back from its hit point to its thrower (PRTS "返回时飞行速度3.75"). */ diff --git a/server/sim/content/bosses.js b/server/sim/content/bosses.js index 66c22709..f6810972 100644 --- a/server/sim/content/bosses.js +++ b/server/sim/content/bosses.js @@ -58,7 +58,7 @@ // 掷锤 pick their operator "(无视无法选择)"), the 盲信之誓 chains ("无视无法选择"), the 法术护盾 counter on its attacker // (a direct pick) and the ticks of a debuff already on it (【自然涌动】: a tick selects nobody). // Every area effect of a leader or part — pulses, strikes around an echo, blasts, charges and tramples, crosses, columns, -// whole-field skills — selects with enemies.js areaAllies / areaAlliesInTiles / fieldAllies (targeting.js +// whole-field skills — selects with enemies/helpers.js areaAllies / areaAlliesInTiles / fieldAllies (targeting.js // areaSelectable): no 隐匿 operator, the one blocking the unit included (GitHub #97), no untargetable or sleeping one, no 起飞 one for a // ground unit; 迷彩 is not checked (splash-type, 中点判定 / 格子判定 or "无视迷彩" on PRTS; the rest [ASSUMED], DESIGN // §22.12). Only 【盲信之誓】 ("无视无法选择、迷彩") takes everyone on its lines. diff --git a/server/sim/content/devices.js b/server/sim/content/devices.js index 221f5269..62f5d68b 100644 --- a/server/sim/content/devices.js +++ b/server/sim/content/devices.js @@ -375,7 +375,7 @@ function enterTerrain(battle, st, u, code) { * 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: 夕's 小自在 …; Battle.moveRedeploy: 乌尔比安 S3) - * keeps it for its time; leaving the field drops it with every buff; a 重生 clears it (enemies.js rebirthCleanse: PRTS + * keeps it for its time; leaving the field drops it with every buff; a 重生 clears it (enemies/archetypes.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]. diff --git a/server/sim/content/enemies.js b/server/sim/content/enemies.js index de48e466..37ac3fa9 100644 --- a/server/sim/content/enemies.js +++ b/server/sim/content/enemies.js @@ -4,8 +4,8 @@ // bosses.js calls it too) and attaches an ability list to every spawned enemy whose key has a kit in KITS. // Every number comes from the enemy's talent / skill blackboard (data/enemies.json, merged with the wave template's // `overrides[key].talents/skills` — the engine only applies `overrides.stats`). Numbers that exist nowhere in the -// data are named constants below, marked [ASSUMED] or sourced from the official term table (gamedata_const -// termDescriptionDict) / PRTS. +// data are named constants (enemies/helpers.js when several kit families use them, else in the family's file), marked +// [ASSUMED] or sourced from the official term table (gamedata_const termDescriptionDict) / PRTS. // // Runtime model: `enemy.mem.ab = { key, t (talent bb), tS (talent bbStr), sk {prefabKey → {cd, icd, sp, bb, bs}}, // list [abilities], times, hitShield, atkType, immune, … }`. An ability is a plain object with optional methods @@ -54,7 +54,7 @@ // 【国度】, the chimera's 源石污染区 [ASSUMED]) reach every ally. // // Special types (factions.json): -// FLY — engine (FLY motion, ranged-only targeting). Flyer kits below (御4, 护障, 寒霜, 萨科塔之翼/眼, 黑云 …). +// FLY — engine (FLY motion, ranged-only targeting). Flyer kits in enemies/fly.js (御4, 护障, 寒霜, 萨科塔之翼/眼, 黑云 …). // TIMES 频次 — "需要N次伤害击倒": maxHp := N hits; engine buff flag hitCount — every damage instance (any type, element // bursts included) removes exactly 1 on its own DamageInfo (茶器 hitCountArts: phys removes 0); HP loss // (loseHp) bypasses the rule. The faction's enemies @@ -75,7 +75,7 @@ // The mirror rule for an ally's 隐匿 — enemy area effects skip it even when it blocks the enemy (0.1.3, GitHub #97) — is areaAllies. // REFLECTION — 折射 (ba.refraction "生效时,法术抗性+70"): RES +refracting.magic_resistance while NOT silenced // (the ability line is SILENCE-flagged: silencing turns it off); 镜膜 also gets max HP +100 % while on. -// SPECIAL — mostly stats; prisoners, 穿刺手, 暴虐兵长, 镜卫, 动力装甲 … below. +// SPECIAL — mostly stats; prisoners, 穿刺手, 暴虐兵长, 镜卫, 动力装甲 … in enemies/special.js. // Every enemy key of data/enemies.json is either in KITS (this file), BOSS_KEYS (bosses.js) or STATS_ONLY (with the // reason) — pinned by test/content/enemies_bosses.test.js. Bounty (悬赏) leaders are included: multi-form ones // (巨大的丑东西, 杰斯顿, 自在, 锏, 扎罗) get their second form; event-only mechanics (芦苇, 摄影区, 狂欢时刻, 悬索桥, 唤血祭坛, diff --git a/server/sim/snapshot.js b/server/sim/snapshot.js index 2b0c86a5..b53f6019 100644 --- a/server/sim/snapshot.js +++ b/server/sim/snapshot.js @@ -2,7 +2,7 @@ // // b.snap = { fieldId, t, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]], dp, killed, total } // UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, -// form?, skillIndex?, moduleId?, items? } (form = the unit's current model form — an enemy's, content/enemies.js setForm: +// form?, skillIndex?, moduleId?, items? } (form = the unit's current model form — an enemy's, content/enemies/helpers.js setForm: // 掠海漂移体 'crawl', 暴鸰 'bombed', 转译基底·α's forms …; a 傀儡师 fighting as its 替身 'doll', professions.js — a view built // after the change, a field opened mid-battle, draws it: render/units.js FORMS) // dir = 'UP'|'RIGHT'|'DOWN'|'LEFT' (allies: the deploy direction, sim/dir.js); facing = its horizontal sign (±1). @@ -39,7 +39,7 @@ export function unitInfo(u) { maxHp: Math.max(1, Math.round(u.s.maxHp)), motion: u.motion === 'FLY' ? 'FLY' : undefined, boss: u.isBoss ? true : undefined, - // the unit's current model form (an enemy's content/enemies.js setForm, a 傀儡师's 替身 — render/units.js FORMS): a + // the unit's current model form (an enemy's content/enemies/helpers.js setForm, a 傀儡师's 替身 — render/units.js FORMS): a // view built mid-battle (fieldMeta — a watched teammate's field, 联防 observers, a reconnect) starts on that clip set form: typeof u.form === 'string' ? u.form : undefined, uid: u.uid ?? undefined, diff --git a/server/sim/targeting.js b/server/sim/targeting.js index 286cb695..5d2ebcf9 100644 --- a/server/sim/targeting.js +++ b/server/sim/targeting.js @@ -107,7 +107,7 @@ export function canTargetAlly(e, a, ranged) { /** * May an AREA effect of enemy-side `src` select ally `a` — a splash, a blast, an area skill or status, a pulse, a zone it - * leaves, a chain / bounce jump, a 周围四格 addition (content/enemies.js areaAllies / areaAlliesInTiles / fieldAllies)? + * leaves, a chain / bounce jump, a 周围四格 addition (content/enemies/helpers.js areaAllies / areaAlliesInTiles / fieldAllies)? * PRTS 作战机制 §AOE伤害判定 "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定"; §隐匿 "隐匿效果使得获得该效果的单位无法被 * 任何敌方的能力索敌选中"; PRTS 异常效果 §无法选择: with 隐匿, 不可选中, 无敌, 塔不可选中 or 对地规避 "常见的、来自不同阵营的 * “选择”行为将无视这些单位进行(如同范围内不存在这个单位)", and the abilities PRTS marks "无视可选性" are those that skip @@ -133,9 +133,9 @@ export function areaSelectable(src, a) { /** * May a BUFF AURA of enemy-side `src` (a 光环 refreshed on whoever stands in it — 深池伙友卫队's force field, 扎罗's - * 远古威慑; content/enemies.js auraAllies) take ally `a`? PRTS 作战机制 §隐匿与Buff的关系 "隐匿状态下的单位一般无法被敌方的 + * 远古威慑; content/enemies/helpers.js auraAllies) take ally `a`? PRTS 作战机制 §隐匿与Buff的关系 "隐匿状态下的单位一般无法被敌方的 * 索敌机制和Buff选择器选中为目标", "目前明日方舟中使用能选中隐匿状态单位的Buff效果一定是无视隐匿状态起作用的" (its example: - * 寒霜's 攻速下降 Debuff — content/enemies.js allyAura keeps that one on every ally): no 隐匿 ally, the one blocking `src` + * 寒霜's 攻速下降 Debuff — content/enemies/archetypes.js allyAura keeps that one on every ally): no 隐匿 ally, the one blocking `src` * included (GitHub #97), no untargetable or sleeping one; 迷彩 does not protect ("所有光环类能力…均不受迷彩制约"). Unlike areaSelectable it does not * apply 对地规避: a ground enemy's aura still reaches an airborne 起飞 ally [ASSUMED, DESIGN §21.20 / §21.22]. */ diff --git a/server/sim/units.js b/server/sim/units.js index 7be21141..25e07a06 100644 --- a/server/sim/units.js +++ b/server/sim/units.js @@ -85,7 +85,7 @@ export class Unit { this.stats = { dmg: 0, kills: 0, heal: 0, taken: 0, attacks: 0 }; this.hidden = false; // enemies inside a DISAPPEAR segment this.moving = false; // enemies: walked this tick (drawn on the move clip; ai.js updateEnemy) - this.form = null; // the model's current form (content/enemies.js setForm, a 傀儡师's 替身 'doll' → snapshot.js unitInfo) + this.form = null; // the model's current form (content/enemies/helpers.js setForm, a 傀儡师's 替身 'doll' → snapshot.js unitInfo) this.anim = 0; this.persist = { redeployMul: 1, freeRedeploys: 0 }; this.isBoss = false; diff --git a/shared/constants.js b/shared/constants.js index d4e566b1..8ae63403 100644 --- a/shared/constants.js +++ b/shared/constants.js @@ -81,7 +81,7 @@ export const SKILL_SUMMON_START_DEPLOY = true; * 12534 "每把都能999层", 12316 "999謝"; research 02 §layers). The only implementation of the cap (DESIGN §20.12). Every * writer of a bond's layers goes through `layerGainRoom`: the prep-side gains (server/match/PlayerState.js addLayers — * 特质, items, bands, 机变 cards, bonds), the settle of the in-battle gains (server/match/Match.js) and the live in-battle - * copy (server/sim/Battle.js addLayers, as the client's AddBondCount) — and the dev tools' direct writes (tools/matchrun.mjs + * copy (server/sim/battle/economy.js addLayers, as the client's AddBondCount) — and the dev tools' direct writes (tools/matchrun.mjs * --layers, tools/balance.mjs applyBoard); a gain at the cap adds 0 (no onLayers, no 'layer' * event), and the client-result check (server/match/fields.js) bounds a reported gain by the room left. Milestones paid * per N layers (远见, 奇迹, 维多利亚 …) stop with the count. 0 / Infinity = no cap. diff --git a/shared/protocol.js b/shared/protocol.js index 3dd2ec4c..73bbb0ab 100644 --- a/shared/protocol.js +++ b/shared/protocol.js @@ -346,7 +346,7 @@ export const EV = Object.freeze({ }); /** - * The model form a `b.ev` 'fx' tuple ['fx', kind, x, y, extra] puts its unit in: `extra.form` (sim content/enemies.js + * The model form a `b.ev` 'fx' tuple ['fx', kind, x, y, extra] puts its unit in: `extra.form` (sim content/enemies/helpers.js * setForm — 转译基底·α's forms, a 逐火 余烬 and its revival, a leader's 重生, 守墓石像's modes, 掠海漂移体's crawl; a 傀儡师's 替身, * sim professions.js; a string is that clip set, null the base one), undefined for any other tuple. A form is state, not decoration: a view that misses * the fx keeps drawing the old model (player report #5 after 0.1.0), so the client's catch-up frames, its hidden-tab