feedback2: merge WJ — enemy splash, blasts, area skills, zones and auras skip a 隐匿 operator that does not block them; 迷彩 is still hit by splash (GitHub #32 item 6)

# Conflicts:
#	docs/DESIGN.md
#	docs/SIM.md
#	server/sim/content/enemies.js
This commit is contained in:
sganggs committed 2026-10-04 02:21:15 +08:00
commit a168c99e6a
9 files changed
+674 -130

No files matched your search

+2 -2
View File
@@ -229,7 +229,7 @@ Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `ba
`Buff = { key, source, duration /*s, Infinity*/, stacks=1, maxStacks=1, refresh: 'replace'|'extend'|'stack', mods, flags, onTick?, onExpire?, tags[] }`
- `mods` keys (all optional, additive within a key): `atkFlat, atkPct, atkMul, defFlat, defPct, defMul, hpFlat, hpPct, hpMul, resFlat, resMul, aspd, batPct, blockCnt, rangeExtend (tiles), moveMul, dmgDealtMul (multiplicative, e.g. 1.2), dmgTakenMul, physTakenMul, artsTakenMul, trueTakenMul, defIgnoreFlat, defIgnorePct, resIgnoreFlat, resIgnorePct, dodgePhys, dodgeArts, healingDealtMul, healingTakenMul, spRecoveryFlat, spRecoveryMul, maxTargets (+n), shield (absorbs damage; consumed)`.
- `flags`: `stun, freeze, sleep, silence (no skills), disarm (no attacks), stealth (untargetable by enemies/ops unless blocked or revealed — an operator's radius area damage skips it too, `Battle.foesInRadius`, PRTS 作战机制 §AOE伤害判定, §21.19; an enemy's 隐匿 returns only 3 s after a block ends, or after its PRTS page's "(解除阻挡N秒后恢复)" — `stealthOff` buffs, `targeting.js enemyStealthed`, §22.8; an enemy's b.snap stealth bit is set only while its 隐匿 is on, §21.4), camou (an ally's 迷彩: like stealth for enemy targeting and on screen, but not 隐匿 for 隐匿 conditions — 叙拉古, 家族徽章; §20.2), liftoff (an ally's 起飞, 蒂比's skills: it blocks flyers only and has 对地规避 — no ground enemy selects it, so no selected damage or status of one lands, while what selects nobody still does: `ignoreSelect` / 无来源 — 无视无法选择 abilities, direct picks, flying units' blasts, a debuff's ticks; it stays a ground unit on its tile; `targeting.js evadesGround`, §21.22), invulnerable, unblockable, levitate, fear (enemy: cannot attack, unblockable, runs to random tiles of the ±45° fan away from the source; no source or itself as source = inside its own tile — `fear.js`, §20.4), taunt (+aggro), cold, noHeal (no heals from others: 禁疗 summons and 孤立 units; an HP-regen attribute still applies — PRTS 安洁莉娜 备注), healFree (禁疗 that stops the unit's own heals too, shown as the status 'healFree': 史尔特尔's 余烬 sets it with noHeal — only an HP-regen attribute and her S3's "无视禁疗" start heal pass, §22.7), isolated (孤立: no friendly selector picks it — no heal, buff, aura or talent of an ally, `Battle.allySelectable`; §20.3)`.
- `flags`: `stun, freeze, sleep, silence (no skills), disarm (no attacks), stealth (untargetable by enemies/ops unless blocked or revealed — an operator's radius area damage skips it too, `Battle.foesInRadius`, PRTS 作战机制 §AOE伤害判定, §21.19, and so do an enemy's area effects and buff auras unless the 隐匿 ally blocks that enemy, `targeting.js areaSelectable` / `auraSelectable`, §22.12; an enemy's 隐匿 returns only 3 s after a block ends, or after its PRTS page's "(解除阻挡N秒后恢复)" — `stealthOff` buffs, `targeting.js enemyStealthed`, §22.8; an enemy's b.snap stealth bit is set only while its 隐匿 is on, §21.4), camou (an ally's 迷彩: like stealth for enemy targeting and on screen, but not 隐匿 for 隐匿 conditions — 叙拉古, 家族徽章; §20.2 — and an enemy's splash and other area effects still hit it, ba.camou "无法躲避溅射类攻击", §22.12), liftoff (an ally's 起飞, 蒂比's skills: it blocks flyers only and has 对地规避 — no ground enemy selects it, so no selected damage or status of one lands, while what selects nobody still does: `ignoreSelect` / 无来源 — 无视无法选择 abilities, direct picks, flying units' blasts, a debuff's ticks; it stays a ground unit on its tile; `targeting.js evadesGround`, §21.22), invulnerable, unblockable, levitate, fear (enemy: cannot attack, unblockable, runs to random tiles of the ±45° fan away from the source; no source or itself as source = inside its own tile — `fear.js`, §20.4), taunt (+aggro), cold, noHeal (no heals from others: 禁疗 summons and 孤立 units; an HP-regen attribute still applies — PRTS 安洁莉娜 备注), healFree (禁疗 that stops the unit's own heals too, shown as the status 'healFree': 史尔特尔's 余烬 sets it with noHeal — only an HP-regen attribute and her S3's "无视禁疗" start heal pass, §22.7), isolated (孤立: no friendly selector picks it — no heal, buff, aura or talent of an ally, `Battle.allySelectable`; §20.3)`.
- **Status catalogue** (`buffs.js`, keys used by content): `stun`, `freeze` (stun + res −15), `cold` (aspd −30; a second cold while cold ⇒ freeze 3 s unless immune), `sleep` (stun; ends on damage? no: in AK sleep = untargetable & unable to act; keep AK: untargetable & inactive), `slow` (moveMul), `bind` (moveMul 0), `fragile` (dmgTakenMul), `artsFragile` (artsTakenMul), `silence`, `fear` (the 恐惧 walk, `fear.js`), `burnGauge`/`necrosis`/`neural` element gauges (see damage), `stealth`, `camou` (迷彩, its own keys: 忍冬 S3 `vulpis:camou` until her next cast, 寒芒克洛丝 S1), `reveal`.
- Immunities from enemy data (`stunImmune`, `silenceImmune`, `sleepImmune`, `frozenImmune`, `levitateImmune`) are honoured by `applyStatus`; a unit both `invulnerable` and `untargetable` (a 重生, a 永久无敌 leader part) takes no status from the other side at all (PRTS 无敌 "无法被不同阵营选中", §21.19). Flag `selfBound` = 自缚 (守墓石像's 转换模式, the 自缚 leaders) beside its `noMove`: 余 S2 never teleports such a unit (束缚 sets `noMove` only).
- Same-named debuffs on an enemy (奥术's arts damage taken, 灵知 坚冰, the 莱恩哈特 / 缄默德克萨斯 RES cuts): one instance per target whoever applies it, the strongest wins (`Battle.applyStrongest`; PRTS 作战机制 "同名buff…只能表现出一个" — strongest rather than latest [ASSUMED], §20.10). PRTS 盟约记录's current 奥术 note fits this under the default overlap; an equal re-hit refreshes the instance (an earlier PRTS wording forbade re-applying while one's own lasts) [ASSUMED, slightly stronger] (§21.7).
@@ -270,7 +270,7 @@ Engine helpers (content must use these, never touch internals): `battle.dealDama
- **Blocking** (contact radius, PRTS 游戏数据基础 §阻挡半径 / 作战机制 "中点判定…案例:阻挡"; `constants.js BLOCK_RADIUS`, `Battle._checkBlock`, §19.2): an operator with free capacity blocks an unblocked, blockable enemy whose position lies within its block radius of its centre — ground 0.7071 (squared 0.49999037), 0.8944 for blockFly units vs flyers (起飞), devices 0.4472 (阻隔工事 / 障碍物); air units (FLY, 近地悬浮, 浮空) only by blockFly units. A ground enemy is never blocked by a unit standing on a tile ground units cannot pass (围墙 / 围栏: PRTS 地形机制 "部署在其中的单位,若当前阻挡类型为'地面阻挡'则无法阻挡敌人" — an enemy pushed or pulled against the fence walks on; air blocking there stays [ASSUMED], §21.23) nor by an airborne (起飞) unit, which blocks flyers only (§21.22). The nearest such operator wins (ties: scan order [ASSUMED]). It is re-checked every tick, moving or not, so another operator in contact with room takes the enemy over when its blocker dies, is withdrawn or is stunned, and an operator whose capacity frees up grabs an overlapping enemy; one that finds no room walks on. A head-on enemy stops at contact (≈ 0.71 tile), outside the blocker's tile. Enemy uses `blockCnt` capacity (default 1). Blocked enemies stop moving and attack their blocker if melee (or anyone in range if ranged, blocker first). **Every blocker** — melee units (重装要塞 / 领主 / 哨戒铁卫 included), summons and a ranged operator on a melee tile alike — whose attack hits enemies may always target the enemies it blocks, in range or not, whatever its facing, and targets them first ("可以选择且优先选择阻挡单位", PRTS 选择器; the user's rule after playtest #6, "阻挡了就一定要能打到": officially the collision pushes a blocked enemy to its blocker's front; `Battle.blockedTargets`, `targeting.js sortEnemyTargets`; §20.3 — it replaces the playtest #5 QA's melee-only restriction of §19.2). A healer (dmgType `heal`, or a skill attack turned into a heal) selects injured allies only and keeps healing while it blocks (PRTS 卫戍协议/帮助 "对于医疗干员(咒愈师分支除外),攻击目标为需要治疗的单位"; §21.17); 咒愈师 follow the blocker rule. The huge leaders are 自缚 + unblockable (§7).
- **Operator attacks**: every `interval` s if a valid target exists; targets = up to `maxTargets` (default 1) from `unitsInGrid(rangeGrid)` (a `rangeAoe` profile — 阵法术师, 轰击术师 — takes every selectable enemy in range, §21.18), priority: (1) the enemies it blocks (every blocker, in range or not, §20.3), (2) profession/trait priorities (e.g. fly-first snipers, `trait` text parsed by build-data into `targetPriority`), (3) enemy with the **least remaining path distance** to its goal, ties → earliest spawned. Melee (no ranged trait) and ground-only attacks cannot hit air units (`Unit.isFlying`: FLY, or a 近地悬浮 / 浮空 enemy — PRTS "算作空中单位", §19.1). Stealthed enemies can't be targeted unless blocked or revealed — or within 3 s of a block's end (§22.8). Ranged ops fire projectiles (speed ~12 tiles/s, instant for melee/arts-beam classes as data indicates); damage applies on impact; a 锁定攻击范围 AoE (`rangeAoe`) strikes every selectable enemy on its range at once with instant hits (§21.18).
- **Healers** (`dmgType heal`): target the ally in range with the lowest HP% below 100%, never a 禁疗 / 孤立 unit (tokens.json `abnormal`, `noHeal`; §20.3; 史尔特尔 during 余烬, §22.7), and never the enemies they block — also while blocking (§21.17); do nothing otherwise (SP still regenerates).
- **Enemy AI**: follow the route (checkpoints; WALK paths recomputed on the grid when devices change); when blocked stop & fight (`dmgType 'none'` enemies never attack unless content arms them through `e.profile` — 转译基底's armed forms, §21.4); ranged enemies (`rangeRadius > 0`) attack ops whose 0.25 collider touches their radius (centre distance ≤ `rangeRadius` + 0.25, §3, §20.4): the enemy's own target rule filters the candidates first (`profile.canTarget`: 只攻击地面单位 …), then priority blocker → special priority → highest taunt → **latest deployed** (`aggroSeq`) — PRTS 索敌 "阻挡→特殊优先级→仇恨值→最早出现"; a stealthed ally is attacked only by the enemy it blocks (§19.1); an airborne (起飞) ally is never a ground enemy's selection — picks, area damage, zones and statuses skip it, while 无视无法选择 abilities, direct picks, debuff ticks and flying units still reach it (§21.22); they pause movement for `ATTACK_PAUSE = 0.35 s` per attack. FLY enemies ignore ground obstacles (only blockFly units block them) and follow their checkpoint route. A feared enemy runs between random 恐惧可达地块 away from the source (PRTS 诱发移动; `fear.js`); 恐惧 outranks 诱导; the 恐惧 / 诱导 walks re-plan from where the enemy stands after a push or an obstacle change, and the route re-plans when they end (§20.4).
- **Enemy AI**: follow the route (checkpoints; WALK paths recomputed on the grid when devices change); when blocked stop & fight (`dmgType 'none'` enemies never attack unless content arms them through `e.profile` — 转译基底's armed forms, §21.4); ranged enemies (`rangeRadius > 0`) attack ops whose 0.25 collider touches their radius (centre distance ≤ `rangeRadius` + 0.25, §3, §20.4): the enemy's own target rule filters the candidates first (`profile.canTarget`: 只攻击地面单位 …), then priority blocker → special priority → highest taunt → **latest deployed** (`aggroSeq`) — PRTS 索敌 "阻挡→特殊优先级→仇恨值→最早出现"; a stealthed ally is attacked only by the enemy it blocks (§19.1), and an enemy's area effects — splash, blasts, area skills, zones, jumps — and buff auras skip it unless it blocks that enemy, while 迷彩 does not protect from splash and the other area effects (`targeting.js areaSelectable` / `auraSelectable`, §22.12); an airborne (起飞) ally is never a ground enemy's selection — picks, area damage, zones and statuses skip it, while 无视无法选择 abilities, direct picks, debuff ticks and flying units still reach it (§21.22); they pause movement for `ATTACK_PAUSE = 0.35 s` per attack. FLY enemies ignore ground obstacles (only blockFly units block them) and follow their checkpoint route. A feared enemy runs between random 恐惧可达地块 away from the source (PRTS 诱发移动; `fear.js`); 恐惧 outranks 诱导; the 恐惧 / 诱导 walks re-plan from where the enemy stands after a push or an obstacle change, and the route re-plans when they end (§20.4).
- **Damage**: phys `max(A − max(0, D×(1−defIgnorePct) − defIgnoreFlat), 0.05·A)`; arts `max(A × (1 − R'/100), 0.05·A)` with `R' = max(0, R×(1−resIgnorePct) − resIgnoreFlat)`; true = A; 元素伤害 ('elemental') `max(A × (1 − 元素抗性/100), 0.05·A)`. Then × `dmgDealtMul` (source) × `dmgTakenMul` & type-specific taken multipliers (target) × `dmg.mul` — 元素伤害 takes 元素脆弱 (`elementalTakenMul`) alone, not `dmgTakenMul` (脆弱 = "受到的物理、法术、真实伤害提升"). Then **限伤**: on a leader (`isBoss` — the 10 bossInfoDict enemyIds and their mirrored copies; never parts, drones or escorts) in a `'boss'` / `'hidden'` battle a hit with `ceil(final) ≥ 300000` is cancelled — no HP or pool loss, no credit, no damage number, no `damaged` / kill (`shared/constants.js BOSS_HIT_LIMIT`, `damage.js leaderHitCancelled`, after every multiplier and the hit-step blocks, before HP shields; `loseHp` too — a part's 传递, a drone's death; the client's `MAX_BATTLE_DAMAGE`, §20.12). Then shields absorb; dodge checks for phys/arts. Element damage fills a gauge (1000; enemy leaders 2000) × `elemTakenMul` × max(0.05, 1 − 损伤抵抗/100), 损伤抵抗 = data `epResistance` (PRTS 元素; the 5 % floor of DMG_e, PRTS 游戏数据基础). An element fill on a target with no HP left is refused (damage.js `hasHp`: no burst on a corpse). A full gauge bursts with the side-specific official effects, as **无来源** damage (no damage-dealt multiplier or penetration of the unit that filled the gauge, which keeps the kill and the stats; §19.5) (sim/constants.js `ELEMENT`; operators: 灼燃 1200 arts + RES −20, 神经 stun 10 s then 1000 true, 凋亡 15 s of 阻回 (`noSp`: no SP gain of any kind) + 静默 with −1 SP/s and 100 arts/s, 侵蚀 DEF −100 then 800 phys; enemies: the "·我方" terms). Every burst starts a **爆发冷却** = its duration (灼燃 / 神经 / 侵蚀 10 s — enemies' 侵蚀 8 s — 凋亡 15 s) during which NO element of the unit fills or can be recovered; when it ends every gauge of the unit resets (§18.3).
- **Leak**: an enemy reaching its goal is removed, counted as leaked for its field owner (`sourcePlayerId` for unite). LP accounting is done by the match, not the battle.
- **Results**: `damageDealt` / a unit's `dmg` count only HP removed from the other side — a unit's own drain or 流失 and friendly damage are the target's `taken` only (§21.12).