feedback4: merge master — PR #73 (operator battle voices); golden regenerated: every moved roster / bonds / fields scenario differs only in events.engage (the sim's new 'engage' event for the voice slot; no gameplay change), matches unchanged
This commit is contained in:
commit
beb0296a52
25 files changed
+940
-166
No files matched your search
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":1,"hash":"8cb555456b2f","generator":"tools/fetch-assets.mjs","stats":{"files":4019,"bytes":289568237,"chars":138,"charsWithBack":135,"enemies":252,"enemiesWithSpine":251,"tokens":20,"tokensWithSpine":13,"spineModels":529,"bonds":23,"items":59,"bands":40,"skills":317,"ui":601,"sfxUnits":382},"chars":{"char_1012_skadi2":{"avatar":"/assets/char/avatar/char_1012_skadi2.png","avatarE2":"/assets/char/avatar/char_1012_skadi2_2.png","portrait":"/assets/char/portrait/char_1012_skadi2_1.png","portraitE2":"/assets/char/portrait/char_1012_skadi2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.skel","atlas":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.atlas","textures":["/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Skill_1_Loop","end":null,"via":"skill"},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null},"die":"Die","move":null,"stun":{"begin":"Stun_Begin","loop":"Stun","end":null},"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":null},"1":{"begin":"Skill_2_Begin","loop":"Skill_2_Loop","end":null,"index":1,"idle":null},"2":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null}}},"animations":{"Default":0,"Die":1,"Idle":8.767,"Skill_1_Begin":3.2,"Skill_1_Loop":8.767,"Skill_2_Begin":3.933,"Skill_2_Loop":3.333,"Skill_3_Begin":4.4,"Skill_3_Loop":8.767,"Start":1,"Stun":2,"Stun_Begin":1},"events":["OnAttack","OnStart"],"hits":{},"bounds":{"x":-190.586,"y":-25.785,"width":393.516,"height":443.426}}}},"char_1014_nearl2":{"avatar":"/assets/char/avatar/char_1014_nearl2.png","avatarE2":"/assets/char/avatar/char_1014_nearl2_2.png","portrait":"/assets/char/portrait/char_1014_nearl2_1.png","portraitE2":"/assets/char/portrait/char_1014_nearl2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":null,"loop":"Skill_2_Die","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Die":1,"Idle":2.667,"Skill_3":1.5,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_1_Loop_Down":1.5,"Skill_2_Die":1.167,"Skill_3_Down":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_3":[0.5],"Skill_1_Loop":[0.467],"Skill_1_Loop_Down":[0.467],"Skill_3_Down":[0.5]},"bounds":{"x":-400.77,"y":-461.007,"width":720.545,"height":872.242}},"back":{"skel":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End","via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Idle":2.667,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_3":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_1_Loop":[0.467],"Skill_3":[0.5]},"bounds":null}}},"char_1016_agoat2":{"avatar":"/assets/char/avatar/char_1016_agoat2.png","avatarE2":"/assets/char/avatar/char_1016_agoat2_2.png","portrait":"/assets/char/portrait/char_1016_agoat2_1.png","portraitE2":"/assets/char/portrait/char_1016_agoat2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.skel","atlas":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.atlas","textures":["/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":"Skill_3_End","index":2,"idle":"Skill_3_Idle"},"diLine truncated
|
{"version":1,"hash":"9e240a72da75","generator":"tools/fetch-assets.mjs","stats":{"files":5699,"bytes":331609355,"chars":138,"charsWithBack":135,"enemies":252,"enemiesWithSpine":251,"tokens":20,"tokensWithSpine":13,"spineModels":529,"bonds":23,"items":59,"bands":40,"skills":317,"ui":601,"sfxUnits":382,"voiceChars":120},"chars":{"char_1012_skadi2":{"avatar":"/assets/char/avatar/char_1012_skadi2.png","avatarE2":"/assets/char/avatar/char_1012_skadi2_2.png","portrait":"/assets/char/portrait/char_1012_skadi2_1.png","portraitE2":"/assets/char/portrait/char_1012_skadi2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.skel","atlas":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.atlas","textures":["/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Skill_1_Loop","end":null,"via":"skill"},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null},"die":"Die","move":null,"stun":{"begin":"Stun_Begin","loop":"Stun","end":null},"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":null},"1":{"begin":"Skill_2_Begin","loop":"Skill_2_Loop","end":null,"index":1,"idle":null},"2":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null}}},"animations":{"Default":0,"Die":1,"Idle":8.767,"Skill_1_Begin":3.2,"Skill_1_Loop":8.767,"Skill_2_Begin":3.933,"Skill_2_Loop":3.333,"Skill_3_Begin":4.4,"Skill_3_Loop":8.767,"Start":1,"Stun":2,"Stun_Begin":1},"events":["OnAttack","OnStart"],"hits":{},"bounds":{"x":-190.586,"y":-25.785,"width":393.516,"height":443.426}}}},"char_1014_nearl2":{"avatar":"/assets/char/avatar/char_1014_nearl2.png","avatarE2":"/assets/char/avatar/char_1014_nearl2_2.png","portrait":"/assets/char/portrait/char_1014_nearl2_1.png","portraitE2":"/assets/char/portrait/char_1014_nearl2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":null,"loop":"Skill_2_Die","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Die":1,"Idle":2.667,"Skill_3":1.5,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_1_Loop_Down":1.5,"Skill_2_Die":1.167,"Skill_3_Down":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_3":[0.5],"Skill_1_Loop":[0.467],"Skill_1_Loop_Down":[0.467],"Skill_3_Down":[0.5]},"bounds":{"x":-400.77,"y":-461.007,"width":720.545,"height":872.242}},"back":{"skel":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End","via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Idle":2.667,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_3":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_1_Loop":[0.467],"Skill_3":[0.5]},"bounds":null}}},"char_1016_agoat2":{"avatar":"/assets/char/avatar/char_1016_agoat2.png","avatarE2":"/assets/char/avatar/char_1016_agoat2_2.png","portrait":"/assets/char/portrait/char_1016_agoat2_1.png","portraitE2":"/assets/char/portrait/char_1016_agoat2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.skel","atlas":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.atlas","textures":["/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":"Skill_3_End","index":2,"idle":"SLine truncated
|
||||||
+17
-4
@@ -19,7 +19,9 @@ npm run assets # = node tools/vendor.mjs && node tools/fetch-assets.mjs
|
|||||||
| `--force` | Re-download everything. |
|
| `--force` | Re-download everything. |
|
||||||
| `--offline` | No network. Re-runs post-processing (atlas fixes, skeleton parsing, WOFF2) on what is already on disk, then rebuilds `data/assets.json`. |
|
| `--offline` | No network. Re-runs post-processing (atlas fixes, skeleton parsing, WOFF2) on what is already on disk, then rebuilds `data/assets.json`. |
|
||||||
| `--dry-run` | Print the plan (file and model counts, alias notes) and exit. |
|
| `--dry-run` | Print the plan (file and model counts, alias notes) and exit. |
|
||||||
| `--refresh-index` | Re-download the two upstream indexes: `audio_data.json` and `models_data.json`. |
|
| `--refresh-index` | Re-download the upstream indexes: `audio_data.json`, `charword_table.json` (the 干员战斗语音 slots) and `models_data.json`. |
|
||||||
|
| `--voice-lang=cn` | 干员战斗语音 language: `cn` (default) | `jp` | `en` | `kr` — the same file names under `voice_cn/`, `voice/`, `voice_en/`, `voice_kr/`. |
|
||||||
|
| `--voice-all` | Plan every official voice slot, including the prep-only lines no battle plays (干员报到 / 编入队伍 / 任命队长 — 360 files, one per operator and slot). Off by default: nothing requests them, so planning them only makes every run download more. |
|
||||||
| `--prune` | Delete files under `public/assets/` that the manifest no longer references, for example after a mapping change. Without this flag they are only listed in the report. `public/assets/local/` (written by `tools/local-extract`) is never pruned. Implies `--allow-shrink`. |
|
| `--prune` | Delete files under `public/assets/` that the manifest no longer references, for example after a mapping change. Without this flag they are only listed in the report. `public/assets/local/` (written by `tools/local-extract`) is never pruned. Implies `--allow-shrink`. |
|
||||||
| `--allow-shrink` | Write `data/assets.json` even when it loses entries the current one has (see "The manifest never shrinks by accident" below). |
|
| `--allow-shrink` | Write `data/assets.json` even when it loses entries the current one has (see "The manifest never shrinks by accident" below). |
|
||||||
| `--local-spines` | Rewrite `tools/assets/local-enemy-spines.json` (the metadata of the enemy models only the local client has, see "Enemy aliases") from the models `tools/local-extract/extract.py` extracted to `public/assets/local/spine/enemy/`. Run it after a game update changed them; without it the committed file is used and a differing extraction only gets a warning. |
|
| `--local-spines` | Rewrite `tools/assets/local-enemy-spines.json` (the metadata of the enemy models only the local client has, see "Enemy aliases") from the models `tools/local-extract/extract.py` extracted to `public/assets/local/spine/enemy/`. Run it after a game update changed them; without it the committed file is used and a differing extraction only gets a warning. |
|
||||||
@@ -31,7 +33,9 @@ audio entries short — all 42 still resolve upstream; PR #7). When the rebuilt
|
|||||||
one, the run keeps the current file, prints the entries it would drop (also in the report: `droppedEntries`,
|
one, the run keeps the current file, prints the entries it would drop (also in the report: `droppedEntries`,
|
||||||
`manifestWritten: false`) and exits 1. Re-run to retry the downloads, or pass `--allow-shrink` (or `--prune`) when the
|
`manifestWritten: false`) and exits 1. Re-run to retry the downloads, or pass `--allow-shrink` (or `--prune`) when the
|
||||||
smaller manifest is intended, for example after a mapping change. Build fields (`version`, `hash`, `generator`,
|
smaller manifest is intended, for example after a mapping change. Build fields (`version`, `hash`, `generator`,
|
||||||
`stats`), new entries and a changed value are never a drop (`tools/assets/manifest.mjs droppedEntries`).
|
`stats`), new entries and a changed value are never a drop (`tools/assets/manifest.mjs droppedEntries`). A run whose
|
||||||
|
plan legitimately narrows — like the 干员战斗语音 default, which no longer plans the three prep-only slots (360 entries,
|
||||||
|
DESIGN §21.30) — reports exactly those entries and needs `--allow-shrink` once; the list it prints is the check.
|
||||||
|
|
||||||
The script is **idempotent**. A file on disk is kept, not re-downloaded, when any one of these holds:
|
The script is **idempotent**. A file on disk is kept, not re-downloaded, when any one of these holds:
|
||||||
- its size matches the ledger entry from a previous download (`.cache/assets-ledger.json`);
|
- its size matches the ledger entry from a previous download (`.cache/assets-ledger.json`);
|
||||||
@@ -48,7 +52,7 @@ How downloads are fetched:
|
|||||||
- A manifest entry with fallbacks (for example an enemy icon that falls back to its base enemy's icon) only moves on to the next alternative after a **definitive 404**. When the primary fails transiently (network error, 5xx or an invalid payload after all retries), no fallback is fetched. The path is listed under `downloadErrors` in the report, and the next run retries the primary.
|
- A manifest entry with fallbacks (for example an enemy icon that falls back to its base enemy's icon) only moves on to the next alternative after a **definitive 404**. When the primary fails transiently (network error, 5xx or an invalid payload after all retries), no fallback is fetched. The path is listed under `downloadErrors` in the report, and the next run retries the primary.
|
||||||
- A skeleton that fails to parse is deleted and removed from the ledger, so the next online run downloads it again.
|
- A skeleton that fails to parse is deleted and removed from the ledger, so the next online run downloads it again.
|
||||||
|
|
||||||
The first run downloads about **269 MiB in about 4,000 files** (it took 134 s on a ~3 MB/s link before the 55 emote and 玩法说明 files, 21.3 MiB, were added). A re-run takes about 1 s.
|
The first run downloads about **309 MiB in about 5,690 files** (it took 134 s on a ~3 MB/s link before the 55 emote and 玩法说明 files, 21.3 MiB, and the 1,680 干员战斗语音 files, 40.1 MiB, were added). A re-run takes about 1 s. The voice count is the twelve slots a battle plays; the three prep-only slots the official client uses elsewhere (干员报到 / 编入队伍 / 任命队长, 360 more files, 18.4 MiB) are left out unless `--voice-all` is passed.
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
- `data/assets.json`: the manifest (committed).
|
- `data/assets.json`: the manifest (committed).
|
||||||
@@ -86,6 +90,7 @@ The research JSONs in `docs/research/` (03, 05, 07) define **which** ids are nee
|
|||||||
| Enemy Spine that no dump carries (灼热源石虫 / 炽焰源石虫) | the local client only (`tools/local-extract/extract.py ENEMY_SPINES`, optional); never downloaded and never required: an overlay of the web alias (`enemies[id].spineLocal`) | `local/spine/enemy/{enemyId}/{stem}.*` (listed in `data/local-assets.json`) |
|
| Enemy Spine that no dump carries (灼热源石虫 / 炽焰源石虫) | the local client only (`tools/local-extract/extract.py ENEMY_SPINES`, optional); never downloaded and never required: an overlay of the web alias (`enemies[id].spineLocal`) | `local/spine/enemy/{enemyId}/{stem}.*` (listed in `data/local-assets.json`) |
|
||||||
| BGM | AA2 `voice` branch `audio/sound_beta_2/music/**` (大厅/休整期 `act1autochess`, 开战 `act13side/m_bat_kazimierz2_{1,2}` — 骑士之日 / 无畏者; the 开战 track follows the round: `_2` 无畏者 rounds 1–7, `_1` 骑士之日 from round 8) | `audio/bgm/{file}.mp3` |
|
| BGM | AA2 `voice` branch `audio/sound_beta_2/music/**` (大厅/休整期 `act1autochess`, 开战 `act13side/m_bat_kazimierz2_{1,2}` — 骑士之日 / 无畏者; the 开战 track follows the round: `_2` 无畏者 rounds 1–7, `_1` 骑士之日 from round 8) | `audio/bgm/{file}.mp3` |
|
||||||
| SFX (UI, battle, per unit) | AA2 `voice` `audio/sound_beta_2/**`, mapped from `audio_data.json` banks | `audio/sfx/{same sub-path}.mp3` |
|
| SFX (UI, battle, per unit) | AA2 `voice` `audio/sound_beta_2/**`, mapped from `audio_data.json` banks | `audio/sfx/{same sub-path}.mp3` |
|
||||||
|
| 干员战斗语音 | AA2 `voice` `audio/sound_beta_2/voice_cn/{charId}/cn_nn.mp3` — the lines `charword_table.json` lists (`placeType` = when the game plays one, `voiceAsset` = the path); `--voice-lang=jp|en|kr` takes the same file names from `voice/`, `voice_en/`, `voice_kr/` | `audio/voice/{lang}/{charId}/{cn_nn}.mp3` |
|
||||||
| Fonts: Bender Regular and Light, Novecento Wide | TimWangZi/The-font-of-Arknights | `public/fonts/*.{otf,ttf,woff2}`, `public/fonts/fonts.css` |
|
| Fonts: Bender Regular and Light, Novecento Wide | TimWangZi/The-font-of-Arknights | `public/fonts/*.{otf,ttf,woff2}`, `public/fonts/fonts.css` |
|
||||||
|
|
||||||
The `stem` of a Spine model is the upstream file name. Two examples: `char_107_liskam` has the stem `char_107_liskarm`, and `enemy_9032_aclionk` uses `enemy_1559_vtlionk`. The skel and atlas of a model always share one stem. pixi-spine locates the atlas by swapping the extension, so this matters.
|
The `stem` of a Spine model is the upstream file name. Two examples: `char_107_liskam` has the stem `char_107_liskarm`, and `enemy_9032_aclionk` uses `enemy_1559_vtlionk`. The skel and atlas of a model always share one stem. pixi-spine locates the atlas by swapping the extension, so this matters.
|
||||||
@@ -136,7 +141,7 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
|
|||||||
hash: 'a1b2c3d4e5f6', // content hash (cache busting)
|
hash: 'a1b2c3d4e5f6', // content hash (cache busting)
|
||||||
generator: 'tools/fetch-assets.mjs',
|
generator: 'tools/fetch-assets.mjs',
|
||||||
stats: { files, bytes, chars, charsWithBack, enemies, enemiesWithSpine, tokens, tokensWithSpine,
|
stats: { files, bytes, chars, charsWithBack, enemies, enemiesWithSpine, tokens, tokensWithSpine,
|
||||||
spineModels, bonds, items, bands, skills, ui, sfxUnits },
|
spineModels, bonds, items, bands, skills, ui, sfxUnits, voiceChars },
|
||||||
chars: { [charId]: { avatar, avatarE2?, portrait, portraitE2?, spine: { front: Spine, back?: Spine } } },
|
chars: { [charId]: { avatar, avatarE2?, portrait, portraitE2?, spine: { front: Spine, back?: Spine } } },
|
||||||
enemies: { [enemyId]: { icon, spine?: Spine, spineAliasOf?: enemyId,
|
enemies: { [enemyId]: { icon, spine?: Spine, spineAliasOf?: enemyId,
|
||||||
spineLocal?: { group, skel, atlas, textures, …Spine } } },
|
spineLocal?: { group, skel, atlas, textures, …Spine } } },
|
||||||
@@ -161,6 +166,14 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
|
|||||||
// `bgmEvent = corrosion` (卡西米尔 act13d5d0), so the rescue phase does not reuse the 作战's track
|
// `bgmEvent = corrosion` (卡西米尔 act13d5d0), so the rescue phase does not reuse the 作战's track
|
||||||
// (audio.js bgmKeyFor 'unite', falling back to `bgm.combat` for a manifest that lacks it)
|
// (audio.js bgmKeyFor 'unite', falling back to `bgm.combat` for a manifest that lacks it)
|
||||||
bossBgm: { [bossId]: { intro?, loop } }, // per-boss track of its R14/R15 level
|
bossBgm: { [bossId]: { intro?, loop } }, // per-boss track of its R14/R15 level
|
||||||
|
voice: { [charId]: { start, faceEnemy, select, place, skill1…skill4,
|
||||||
|
resultFour, resultThree, resultTwo, resultLose } },
|
||||||
|
// 干员战斗语音: an operator's official battle lines (charword_table.json placeType → slot,
|
||||||
|
// tools/assets/audio.mjs VOICE_SLOTS); a slot with several lines is an array and the client
|
||||||
|
// draws one (public/js/audio.js voice). Only these twelve ever play (DESIGN §21.30): the
|
||||||
|
// prep-only slots 干员报到 / 编入队伍 / 任命队长 are left out of the plan by default —
|
||||||
|
// nothing requests them and they cost 360 files (19.3 MB) per run — and `--voice-all` adds
|
||||||
|
// them (audio.mjs VOICE_PREP_SLOTS) for the complete official set
|
||||||
sfx: {
|
sfx: {
|
||||||
ui: { click, back, confirm, tab, pick, drop, error, buy, sell, income, refresh, freeze, levelup,
|
ui: { click, back, confirm, tab, pick, drop, error, buy, sell, income, refresh, freeze, levelup,
|
||||||
merge, equip, itemMerge, bondUp, artPlace, ready, timer, draft, yourTurn, yourTurnCircle,
|
merge, equip, itemMerge, bondUp, artPlace, ready, timer, draft, yourTurn, yourTurnCircle,
|
||||||
|
|||||||
@@ -1476,6 +1476,20 @@ Two public GitHub issues after 0.1.0, both client-only. Normative lines rewritte
|
|||||||
- Tests: `test/sim/feedback1-tank-triggers.test.js` (the data, kit = data, the real battle for the six and for every other MANUAL 重装 record), `test/data.test.js`, `kits_t1t2` / `kits_alt_t1` (PR #12's), `kits_alt_t2` (灰毫 S2), `kits_t5` / `kits_alt_t5` (号角), `test/docs-consistency.test.js`.
|
- Tests: `test/sim/feedback1-tank-triggers.test.js` (the data, kit = data, the real battle for the six and for every other MANUAL 重装 record), `test/data.test.js`, `kits_t1t2` / `kits_alt_t1` (PR #12's), `kits_alt_t2` (灰毫 S2), `kits_t5` / `kits_alt_t5` (号角), `test/docs-consistency.test.js`.
|
||||||
- Extended 2026-10-04: 余 S2 (SKILL_RANGE on its x-1, §22.10); 14 records deviate now.
|
- Extended 2026-10-04: 余 S2 (SKILL_RANGE on its x-1, §22.10); 14 records deviate now.
|
||||||
|
|
||||||
|
### 21.30 干员战斗语音: an operator's official lines, in battle only (user request, 2026-10-03) — `tools/assets/plan.mjs` (`voiceSlots`), `tools/assets/audio.mjs` (`indexVoice` / `VOICE_SLOTS` / `VOICE_BATTLE_SLOTS` / `VOICE_PREP_SLOTS` / `VOICE_DIRS`), `tools/assets/cache.mjs`, `tools/fetch-assets.mjs --voice-lang / --voice-all`, `public/js/audio.js` (`VOICE_PRIORITY` / `VOICE_COOLDOWN_MS` / `VoiceGate` / `resultVoiceSlot` / `resultSpeaker` / `voice()`), `server/sim/ai.js performAttack 'engage'`, `battle/runner.js emit('result')`, `screens/game.js onResult`, `ui/detailPanel.js voice`, `ui/settings.js`, `ui/gameLogic.js sanitizeSettings`
|
||||||
|
|
||||||
|
User: 「给每个卫戍干员都加语音」, then, after the first build, 「整备阶段不需要干员语音,战斗开始后才需要语音,也就是只需要作战中语音」.
|
||||||
|
|
||||||
|
**Where the lines come from.** Every operator the mode can field already has official battle lines in the same voice dump the BGM and SFX come from — `voice_cn/{charId}/cn_nn.mp3` (JP/EN/KR: `voice/`, `voice_en/`, `voice_kr/`, the same file names, `tools/fetch-assets.mjs --voice-lang=jp|en|kr`). `charword_table.json`'s `placeType` says when each line is used, and `indexVoice()` turns it into the manifest's `audio.voice[charId]`: 行动出发 `start` (BATTLE_START), 行动开始 `faceEnemy` (BATTLE_FACE_ENEMY), 选中干员 `select`, 部署 `place`, 作战中1-4 `skill1..skill4` (BATTLE_SKILL_N), 编入队伍 `squad`, 任命队长 `squadFirst`, 结算 `resultFour/Three/Two/Lose` (完成高难行动 / 3星结束行动 / 非3星结束行动 / 行动失败) and 干员报到 `gacha`. A slot with several lines stays an array and the client draws one. Only the base word key is planned (`wordKey === charId`): the dump has no folder for a skin variant's word key (`char_x_ita`, `char_x_epoque#28`), so those lines 404. **Only the slots a battle can play are planned** (`audio.mjs VOICE_BATTLE_SLOTS`): 干员报到 / 编入队伍 / 任命队长 are lines of the official client's own 养成 / 编队 UI, nothing in this mode ever requests them (`test/docs-consistency.test.js` checks the client's `voice(...)` call sites), and downloading them cost 360 files (19.3 MB) on every run — `--voice-all` adds them back for the complete official set. Measured: 120 of the 121 in-mode charIds have a full set — the 17 预备干员 and the mode's own 盟约·辅助干员 have no battle voice at all, so they simply get no entry — 1680 lines of the 2040 upstream, in a manifest of 5694 files / 324.2 MB (files and `stats.voiceChars` in `data/assets.json`).
|
||||||
|
|
||||||
|
**Where each line plays — battle only.** Every line comes from a running battle's own event stream or from its settlement, so no hook needs a phase check and the 休整期 stays silent: the shop does not say 干员报到 (a merge-completing buy neither says 任命队长), a retreat does not say 编入队伍 and dragging a piece onto the board in prep says nothing. In battle: 开战 — the first operator deployed says 行动出发 and the others 部署 (the sim's `deploy` events; `setFieldUnits` resets the flag per field — in this mode every piece enters at once, which is why the gate below exists, and a knocked-out operator's redeploy says 部署 again); 接敌 — the sim's new `['engage', id]` (a unit's first attack that hits an enemy, official ENCOUNTER_ENEMY); 技能 — `skillN` from the unit's `skillIndex` (作战中1-4); 选中 — the detail panel opening on an operator **while a battle runs** (`DetailPanel voice` = the game screen's `combat`); 结算 — `resultVoiceSlot()`: 完美作战 ⇒ 3星结束行动 (绝境 / 终极 ⇒ 完成高难行动), 有漏怪 ⇒ 非3星结束行动, 一个没杀 ⇒ 行动失败, spoken by a drawn operator **of that battle's own field** (`resultSpeaker`, from the result's `perPlayer.unitsEnd`: the operators of that field, survivors first, a summon piece never), driven by the runner's new `emit('result', …)` — never the field on screen, so watching a teammate cannot put their operator's voice on the viewer's settlement line (review on #73).
|
||||||
|
|
||||||
|
**Why a channel, not SFX.** Voices are 1–4 s long and a battle deploys eight operators, then fires dozens of skills; playing them like SFX would flood the mix. `VoiceGate` copies the official scheduling data (`audio_data.json battleVoice.voiceTypeOptions`): one line at a time, a 1.2 s global gap, a per-unit per-slot cooldown (10 s for 作战中, 3 s for 接敌, 1.5 s for 选中) and takeover when a line's priority beats the playing one by a margin (the official 0.1 s `crossfade`) — the priorities are the official ones (BATTLE_START 100, ENCOUNTER_ENEMY 90, SKILL_ACTIVE 70, PLACE_CHAR 20, FOCUS_CHAR 10) and the settlement lines get 85, above 作战中 but below 接敌. Every deferred step of a line — its decode, its `onended`, the safety timer — carries its own token and returns without touching the gate once it is stale, so a line taken over while it was still loading can never free the channel its replacement just took (review on #73). The channel has its own gain and its own slider (设置 干员语音 / VOICE; `sanitizeSettings` adds `voice`, so a saved profile without it keeps the default and players can turn voices down without losing SFX). The buffer cache also gained a decoded-PCM budget (64 MB) beside its 180-entry cap: a voice decodes to ~0.4–1.3 MB, and 180 of them would be ~100 MB.
|
||||||
|
|
||||||
|
**[ASSUMED].** 接敌 as "the unit's first attack on an enemy" (the official trigger is the first enemy it encounters, not necessarily one it hits) and the settlement mapping (漏怪 ⇒ 非3星, 完美作战 in 绝境 / 终极 ⇒ 完成高难行动) are our readings. Lines follow the field on screen: watching a teammate's battle (前往查看) plays *their* operators' lines, while the settlement line fires for the player's own battle only (`e.own`, `battle/runner.js`) and is spoken by an operator of that battle's own field (`resultSpeaker`). 部署 and 选中干员 stay in the plan although the official groups them with the prep lines: a battle *does* play them (the first operator says 行动出发 and the others 部署, and the detail panel says 选中 while a battle runs), so only the three slots no call site ever asks for are dropped — `VOICE_PRIORITY` / `VOICE_COOLDOWN_MS` keep every level, so `--voice-all` restores 编入队伍 / 任命队长 / 干员报到 for the day the 休整期 speaks again.
|
||||||
|
|
||||||
|
- Tests: `test/ui/audio.test.js` (the priority table, the settlement slot and its speaker, the gate's global gap / per-unit cooldown / takeover, the stale-callback regression, and the battle events that drive the lines against a fake Web Audio), `test/ui/gameLogic.test.js` (the `voice` setting), `test/sim/battle.test.js` (the wire format: every emitted tuple is a known `EV` — the new `engage` included); the manifest's 1680 voice paths are covered by `test/assets.test.js`.
|
||||||
|
|
||||||
## 22. GitHub issues after 0.1.1 (v0.1.2)
|
## 22. GitHub issues after 0.1.1 (v0.1.2)
|
||||||
|
|
||||||
Public GitHub issues filed after the 0.1.1 release (2026-10-03), each triaged against 0.1.1 first; the real ones were fixed for 0.1.2 in fix branches WA–WL, each independently reviewed before it was merged into `feedback2`. Where each is handled: #25 back-facing operators keep their attack pose when knocked out → §22.1; #51 突袭's idle redeploy → §22.2; #33 items 1–2 阿戈尔's devour and 乌尔比安 S3 → §22.3; #33 item 6 活性源石, with the 沼泽 and 深水区 terrain → §22.4; #42 emotes and guide pages without a local client, and the English title (#38) → §22.5; #32 item 4 钩索师 / 推击手 on 高台 → §22.6; #52 史尔特尔's 余烬 → §22.7; #43 an enemy's 隐匿 after a block, and the 深池逐火 ember window → §22.8; #32 item 7 荒芜拉普兰德 S3's drones → §22.9; #32 item 1 余 S2 → §22.10; #44 归溟幽灵鲨's 替身 → §22.11; #32 item 6 enemy splash against a 隐匿 operator → §22.12; #35 / #16 (PR #48) 要塞 against air units and 莫斯提马's slow icon → §22.13; #58 / #60 / #64 follow in §22.14–§22.16. Decisions of the owner, 2026-10-04: 荒芜拉普兰德's drones measure the distance to an enemy's position, a huge enemy's centre (§22.9); 余 S2 casts with an enemy on its x-1 — a deliberate deviation from the official TAKE_DAMAGE row, added to §21.29's six (§22.10); 乌尔比安 keeps the devour gains through his 【移动】 — the deviation stays (§22.3).
|
Public GitHub issues filed after the 0.1.1 release (2026-10-03), each triaged against 0.1.1 first; the real ones were fixed for 0.1.2 in fix branches WA–WL, each independently reviewed before it was merged into `feedback2`. Where each is handled: #25 back-facing operators keep their attack pose when knocked out → §22.1; #51 突袭's idle redeploy → §22.2; #33 items 1–2 阿戈尔's devour and 乌尔比安 S3 → §22.3; #33 item 6 活性源石, with the 沼泽 and 深水区 terrain → §22.4; #42 emotes and guide pages without a local client, and the English title (#38) → §22.5; #32 item 4 钩索师 / 推击手 on 高台 → §22.6; #52 史尔特尔's 余烬 → §22.7; #43 an enemy's 隐匿 after a block, and the 深池逐火 ember window → §22.8; #32 item 7 荒芜拉普兰德 S3's drones → §22.9; #32 item 1 余 S2 → §22.10; #44 归溟幽灵鲨's 替身 → §22.11; #32 item 6 enemy splash against a 隐匿 operator → §22.12; #35 / #16 (PR #48) 要塞 against air units and 莫斯提马's slow icon → §22.13; #58 / #60 / #64 follow in §22.14–§22.16. Decisions of the owner, 2026-10-04: 荒芜拉普兰德's drones measure the distance to an enemy's position, a huge enemy's centre (§22.9); 余 S2 casts with an enemy on its x-1 — a deliberate deviation from the official TAKE_DAMAGE row, added to §21.29's six (§22.10); 乌尔比安 keeps the devour gains through his 【移动】 — the deviation stays (§22.3).
|
||||||
|
|||||||
+2
-1
@@ -1196,7 +1196,8 @@ Unknown subprofessions fall back to the profession default (test `professions.te
|
|||||||
(`none|arrow|bolt|bomb|lob|orb|drone|enemy|boomerang|droneBomb|chain|chainHeal`; a boomerang's way back has no event — the
|
(`none|arrow|bolt|bomb|lob|orb|drone|enemy|boomerang|droneBomb|chain|chainHeal`; a boomerang's way back has no event — the
|
||||||
renderer flies it back to the thrower at `BOOMERANG_RETURN_SPEED`; an enemy's `profile.shot` may name another kind,
|
renderer flies it back to the thrower at `BOOMERANG_RETURN_SPEED`; an enemy's `profile.shot` may name another kind,
|
||||||
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`),
|
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]`,
|
`['heal', tgt, amount]`, `['skill', id, 1|0]`, `['engage', id]` (an ally's first attack that hits an enemy — the client's
|
||||||
|
行动开始 voice, DESIGN §21.30), `['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;
|
`['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.js setForm`, a 傀儡师's 替身 — `shared/protocol.js fxForm`),
|
||||||
`['layer', playerId, bondId, n]` (n = the layers actually added, capped at 999), `['bounty', playerId, coins]`.
|
`['layer', playerId, bondId, n]` (n = the layers actually added, capped at 999), `['bounty', playerId, coins]`.
|
||||||
|
|||||||
+288
-13
@@ -1,21 +1,28 @@
|
|||||||
// Audio manager (Web Audio): BGM per phase, UI SFX, per-unit battle SFX. Never throws.
|
// Audio manager (Web Audio): BGM per phase, UI SFX, per-unit battle SFX, operator battle voice. Never throws.
|
||||||
//
|
//
|
||||||
// Sources: data/assets.json → audio (docs/ASSETS.md):
|
// Sources: data/assets.json → audio (docs/ASSETS.md):
|
||||||
// bgm { lobby, prep, combat, combatAlts?: [ {intro?, loop}, … ], boss: { intro?, loop } },
|
// bgm { lobby, prep, combat, combatAlts?: [ {intro?, loop}, … ], boss: { intro?, loop } },
|
||||||
// bossBgm { [bossId]: { intro?, loop } },
|
// bossBgm { [bossId]: { intro?, loop } },
|
||||||
|
// voice { [charId]: { start, faceEnemy, select, place, skill1…skill4, squad, squadFirst, result*, gacha } },
|
||||||
// sfx.ui { click, buy, sell, refresh, freeze, levelup, merge, equip, ready, timer, yourTurn, … },
|
// sfx.ui { click, buy, sell, refresh, freeze, levelup, merge, equip, ready, timer, yourTurn, … },
|
||||||
// sfx.battle { deploy, tokenDeploy, charDie, tokenDie?, enemyDie, enemyHit, heal, killCoin, … },
|
// sfx.battle { deploy, tokenDeploy, charDie, tokenDie?, enemyDie, enemyHit, heal, killCoin, … },
|
||||||
// sfx.units { [charId|tokenId|enemyId]: { attack?, hit?, skill?, die?, born?, mix?: { [role]: { p?, vol? } } } }.
|
// sfx.units { [charId|tokenId|enemyId]: { attack?, hit?, skill?, die?, born?, mix?: { [role]: { p?, vol? } } } }.
|
||||||
//
|
//
|
||||||
// - The AudioContext is created on the first user gesture (pointerdown/keydown/touchend), so browsers
|
// - The AudioContext is created on the first user gesture (pointerdown/keydown/touchend), so browsers
|
||||||
// never block or warn; everything requested before that is remembered (BGM) or dropped (SFX).
|
// never block or warn; everything requested before that is remembered (BGM) or dropped (SFX).
|
||||||
// - Channels: master → { bgm, sfx } gains; volumes from settings (0..1) + mute. Tab hidden ⇒ suspend.
|
// - Channels: master → { bgm, sfx, voice } gains; volumes from settings (0..1) + mute. Tab hidden ⇒ suspend.
|
||||||
// - BGM: `intro` then `loop` (1 s crossfade); switching tracks fades out/in (0.8 s). The same loop URL
|
// - BGM: `intro` then `loop` (1 s crossfade); switching tracks fades out/in (0.8 s). The same loop URL
|
||||||
// keeps playing across phases (prep and combat share a track).
|
// keeps playing across phases (prep and combat share a track).
|
||||||
// - 开战 BGM: `bgm.combatAlts` are the mode's own battle tracks (塞壬唱片 骑士之日 / 无畏者). The track is fixed per
|
// - 开战 BGM: `bgm.combatAlts` are the mode's own battle tracks (塞壬唱片 骑士之日 / 无畏者). The track is fixed per
|
||||||
// round, not drawn: 无畏者 through rounds 1–7 and 骑士之日 from round 8 on (`combatTrackFor`, the official
|
// round, not drawn: 无畏者 through rounds 1–7 and 骑士之日 from round 8 on (`combatTrackFor`, the official
|
||||||
// schedule), so every client of a match hears the same one, a fight never switches track halfway through and the
|
// schedule), so every client of a match hears the same one, a fight never switches track halfway through and the
|
||||||
// 联防 that follows a 作战 keeps its round's track.
|
// 联防 that follows a 作战 keeps its round's track.
|
||||||
|
// - 干员战斗语音 (`audio.voice`, user request): an operator says the official line of the moment it is in —
|
||||||
|
// 行动出发 start, 行动开始 faceEnemy, 选中干员 select, 部署 place, 作战中1-4 skillN and the settlement's
|
||||||
|
// 结算 result* (all battle-only; the 休整期 is silent). Voices are seconds long and a battle deploys eight
|
||||||
|
// operators, then fires dozens of skills, so they run on their own channel (own gain, settings 干员语音) through
|
||||||
|
// VoiceGate: one line at a time, a global gap, a per-unit per-slot cooldown, and a higher-priority line taking the
|
||||||
|
// channel over — the official scheduling of `audio_data.json battleVoice.voiceTypeOptions`.
|
||||||
// - Battle SFX from `b.ev` tuples (`handleBattleEvents`): at most MAX_VOICES concurrent unit sounds, at most
|
// - Battle SFX from `b.ev` tuples (`handleBattleEvents`): at most MAX_VOICES concurrent unit sounds, at most
|
||||||
// MAX_PER_URL overlapping copies of one sound (the official banks' maxSoundAllowed 2), a per-unit cooldown and a
|
// MAX_PER_URL overlapping copies of one sound (the official banks' maxSoundAllowed 2), a per-unit cooldown and a
|
||||||
// per-URL minimum gap (SfxLimiter), so a 60-unit fight stays listenable.
|
// per-URL minimum gap (SfxLimiter), so a 60-unit fight stays listenable.
|
||||||
@@ -47,8 +54,13 @@ const UNIT_COOLDOWN_MS = 160;
|
|||||||
const URL_GAP_MS = 45;
|
const URL_GAP_MS = 45;
|
||||||
const MAX_PER_URL = 2;
|
const MAX_PER_URL = 2;
|
||||||
const BUFFER_CACHE = 180;
|
const BUFFER_CACHE = 180;
|
||||||
|
/** Decoded-PCM budget of the buffer cache beside its entry count: a voice line decodes to 0.4–1.3 MB (see _buffer). */
|
||||||
|
const BUFFER_BYTES = 64 * 1024 * 1024;
|
||||||
const XFADE_S = 1;
|
const XFADE_S = 1;
|
||||||
const FADE_S = 0.8;
|
const FADE_S = 0.8;
|
||||||
|
/** Voice: shortest gap between two lines, and the crossfade of a higher-priority line taking the channel (official 0.1 s). */
|
||||||
|
const VOICE_GAP_MS = 1200;
|
||||||
|
const VOICE_XFADE_S = 0.1;
|
||||||
/** 'atk' projectile kinds whose first id is the previous bounce target (sim ai.js), not the attacker. */
|
/** 'atk' projectile kinds whose first id is the previous bounce target (sim ai.js), not the attacker. */
|
||||||
const CHAIN_KINDS = new Set(['chain', 'chainHeal']);
|
const CHAIN_KINDS = new Set(['chain', 'chainHeal']);
|
||||||
/** 'dmg' types that are an attack's impact (element gauge fills / 元素伤害 carry the element's name instead). */
|
/** 'dmg' types that are an attack's impact (element gauge fills / 元素伤害 carry the element's name instead). */
|
||||||
@@ -224,6 +236,67 @@ export function unitSoundPlays(mix, roll) {
|
|||||||
return !(Number.isFinite(p) && p >= 0 && p < 1) || roll < p;
|
return !(Number.isFinite(p) && p >= 0 && p < 1) || roll < p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voice priorities — the official battle voice types (`audio_data.json battleVoice.voiceTypeOptions`) mapped onto the
|
||||||
|
* manifest's slots: BATTLE_START 100, BATTLE_FACE_ENEMY 90, SKILL_ACTIVE 70, PASSIVE_IMP 60, PASSIVE_NOR 50,
|
||||||
|
* PLACE_CHAR 20, FOCUS_CHAR 10. The settlement lines are no battle voice of the official scheduler: they sit at 85,
|
||||||
|
* above 作战中 (70) but below 接敌 (90), so a battle's last word is never cut off by an ordinary line. The four prep
|
||||||
|
* slots (部署 / 编入队伍 / 任命队长 / 干员报到) keep their levels although the 休整期 is silent (see the header).
|
||||||
|
*/
|
||||||
|
export const VOICE_PRIORITY = Object.freeze({
|
||||||
|
start: 100, faceEnemy: 90,
|
||||||
|
skill1: 70, skill2: 70, skill3: 70, skill4: 70,
|
||||||
|
resultFour: 85, resultThree: 85, resultTwo: 85, resultLose: 85,
|
||||||
|
gacha: 60, squadFirst: 45, squad: 30, place: 20, select: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Per-unit per-slot cooldowns (ms): the official 10 s of the 作战中 (passive skill) lines, 3 s between 接敌 lines. */
|
||||||
|
export const VOICE_COOLDOWN_MS = Object.freeze({
|
||||||
|
start: 0, faceEnemy: 3000,
|
||||||
|
skill1: 10000, skill2: 10000, skill3: 10000, skill4: 10000,
|
||||||
|
resultFour: 0, resultThree: 0, resultTwo: 0, resultLose: 0,
|
||||||
|
gacha: 0, squadFirst: 0, squad: 0, place: 0, select: 1500,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settlement slot of a finished 作战: 完美作战 ⇒ 3星结束行动 (绝境 / 终极 ⇒ 完成高难行动 instead), a leaked enemy
|
||||||
|
* ⇒ 非3星结束行动, nothing killed at all ⇒ 行动失败.
|
||||||
|
* @param {{perfect?:boolean, leaked?:number, killed?:number, total?:number, hard?:boolean}} [o]
|
||||||
|
* @returns {'resultFour'|'resultThree'|'resultTwo'|'resultLose'}
|
||||||
|
*/
|
||||||
|
export function resultVoiceSlot(o = {}) {
|
||||||
|
const leaked = Number.isFinite(o.leaked) ? o.leaked : 0;
|
||||||
|
const killed = Number.isFinite(o.killed) ? o.killed : 0;
|
||||||
|
const total = Number.isFinite(o.total) ? o.total : 0;
|
||||||
|
if (o.perfect) return o.hard ? 'resultFour' : 'resultThree';
|
||||||
|
if (total > 0 && killed <= 0) return 'resultLose';
|
||||||
|
if (leaked > 0) return 'resultTwo';
|
||||||
|
return o.hard ? 'resultFour' : 'resultThree';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Who says a battle's **result** line (结算): an operator of THAT battle's own field. Never the field the player happens
|
||||||
|
* to be looking at (review on #73): reading the tracked units of the field on screen made a teammate's operator say the
|
||||||
|
* viewer's 作战结束 line while the viewer was watching them.
|
||||||
|
* `pp` is that battle's own `perPlayer` entry (BattleResult, sim/Battle.js): `unitsEnd` lists what stood on its field
|
||||||
|
* when the battle ended, `defId` being an operator (`char_*`) or a summon piece (`token_*`, which does not talk).
|
||||||
|
* Survivors speak first — the line reports how the battle went, and a wiped-out squad is the only case where a fallen
|
||||||
|
* operator ends up saying it. Ties are drawn like every other unit sound.
|
||||||
|
* @param {{ unitsEnd?: Array<{ defId?: string|null, alive?: boolean }> } | null | undefined} pp that battle's perPlayer
|
||||||
|
* @param {() => number} [random]
|
||||||
|
* @returns {string|null} charId, or null when that battle fielded no operator at all
|
||||||
|
*/
|
||||||
|
export function resultSpeaker(pp, random = Math.random) {
|
||||||
|
const ops = [];
|
||||||
|
for (const u of Array.isArray(pp?.unitsEnd) ? pp.unitsEnd : []) {
|
||||||
|
if (u && typeof u.defId === 'string' && u.defId.startsWith('char_')) ops.push({ id: u.defId, alive: !!u.alive });
|
||||||
|
}
|
||||||
|
const standing = ops.filter((o) => o.alive);
|
||||||
|
const pool = standing.length ? standing : ops; // only a wiped-out squad is spoken for by a fallen operator
|
||||||
|
if (!pool.length) return null;
|
||||||
|
return pool[Math.min(pool.length - 1, Math.floor(random() * pool.length))].id;
|
||||||
|
}
|
||||||
|
|
||||||
/** Concurrency + cooldown gate for battle SFX. Pure (time is passed in). */
|
/** Concurrency + cooldown gate for battle SFX. Pure (time is passed in). */
|
||||||
/** Gestures that may unlock audio: iOS Safari only accepts touchend / click / keydown; pointerdown covers the rest. */
|
/** Gestures that may unlock audio: iOS Safari only accepts touchend / click / keydown; pointerdown covers the rest. */
|
||||||
const UNLOCK_EVENTS = ['pointerdown', 'touchend', 'click', 'keydown'];
|
const UNLOCK_EVENTS = ['pointerdown', 'touchend', 'click', 'keydown'];
|
||||||
@@ -275,6 +348,63 @@ export class SfxLimiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voice gate: one line at a time, a global gap between two lines, a per-unit per-slot cooldown, and takeover by a
|
||||||
|
* clearly more important line (the caller fades the playing one out first). Pure — the clock is passed in.
|
||||||
|
*/
|
||||||
|
export class VoiceGate {
|
||||||
|
/** @param {{ gapMs?: number, preemptMargin?: number, maxUnits?: number }} [o] */
|
||||||
|
constructor(o = {}) {
|
||||||
|
this.gapMs = Number.isFinite(o.gapMs) ? o.gapMs : VOICE_GAP_MS;
|
||||||
|
// a line only takes the channel over when its priority beats the playing one by this margin: an equal-priority
|
||||||
|
// line (two 作战中 of different operators) waits for its turn instead of cutting the other off
|
||||||
|
this.preemptMargin = Number.isFinite(o.preemptMargin) ? o.preemptMargin : 10;
|
||||||
|
this.maxUnits = Number.isFinite(o.maxUnits) ? o.maxUnits : 400;
|
||||||
|
this.playing = null; // { slot, pri } of the line on air
|
||||||
|
this.lastAt = -Infinity;
|
||||||
|
this.unitUntil = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drop all state (a new field must not inherit the previous battle's cooldowns). */
|
||||||
|
reset() {
|
||||||
|
this.playing = null;
|
||||||
|
this.lastAt = -Infinity;
|
||||||
|
this.unitUntil.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May a `slot` line from `unitKey` start now?
|
||||||
|
* @param {string} slot
|
||||||
|
* @param {string|number|null} unitKey the cooldown key (a battle unit id; null = no per-unit cooldown)
|
||||||
|
* @param {number} now ms
|
||||||
|
* @returns {'play'|'preempt'|'drop'}
|
||||||
|
*/
|
||||||
|
request(slot, unitKey, now) {
|
||||||
|
const pri = VOICE_PRIORITY[slot] ?? 0;
|
||||||
|
if (this.playing) {
|
||||||
|
if (pri < this.playing.pri + this.preemptMargin) return 'drop';
|
||||||
|
return 'preempt'; // a clearly more important line takes the channel
|
||||||
|
}
|
||||||
|
if (now - this.lastAt < this.gapMs) return 'drop';
|
||||||
|
if (unitKey != null && now < (this.unitUntil.get(`${unitKey}:${slot}`) || 0)) return 'drop';
|
||||||
|
return 'play';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Record a line that started (call right after request() answered play / preempt). */
|
||||||
|
start(slot, unitKey, now) {
|
||||||
|
this.playing = { slot, pri: VOICE_PRIORITY[slot] ?? 0 };
|
||||||
|
this.lastAt = now;
|
||||||
|
const cd = VOICE_COOLDOWN_MS[slot] ?? 0;
|
||||||
|
if (unitKey != null && cd > 0) {
|
||||||
|
if (this.unitUntil.size > this.maxUnits) this.unitUntil.clear();
|
||||||
|
this.unitUntil.set(`${unitKey}:${slot}`, now + cd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The line ended (naturally, by takeover or by a stop). */
|
||||||
|
release() { this.playing = null; }
|
||||||
|
}
|
||||||
|
|
||||||
// ---- manager -----------------------------------------------------------------------------------------------
|
// ---- manager -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -303,10 +433,16 @@ export class AudioManager {
|
|||||||
this.master = null;
|
this.master = null;
|
||||||
this.bgmGain = null;
|
this.bgmGain = null;
|
||||||
this.sfxGain = null;
|
this.sfxGain = null;
|
||||||
this.volumes = { bgm: 0.6, sfx: 0.8, muted: false };
|
this.voiceGain = null;
|
||||||
|
this.volumes = { bgm: 0.6, sfx: 0.8, voice: 0.8, muted: false };
|
||||||
this.buffers = new Map(); // url → Promise<AudioBuffer|null> (insertion order = LRU)
|
this.buffers = new Map(); // url → Promise<AudioBuffer|null> (insertion order = LRU)
|
||||||
|
this.bufBytes = new Map(); // url → decoded PCM bytes (the byte budget of the LRU, see _buffer)
|
||||||
this.warned = new Set();
|
this.warned = new Set();
|
||||||
this.limiter = new SfxLimiter();
|
this.limiter = new SfxLimiter();
|
||||||
|
this.voiceGate = new VoiceGate();
|
||||||
|
this.voiceNode = null; // { src, gain, url, token } of the line on air
|
||||||
|
this.voiceToken = 0;
|
||||||
|
this.startVoiceDone = false; // 行动出发 of this field (the first operator deployed says it)
|
||||||
this.uiVoices = 0;
|
this.uiVoices = 0;
|
||||||
this.wantBgm = null; // desired key (kept while locked)
|
this.wantBgm = null; // desired key (kept while locked)
|
||||||
this.bgm = null; // { key, loopUrl, nodes: [{src, gain}], gain }
|
this.bgm = null; // { key, loopUrl, nodes: [{src, gain}], gain }
|
||||||
@@ -359,8 +495,10 @@ export class AudioManager {
|
|||||||
this.master = this.ctx.createGain();
|
this.master = this.ctx.createGain();
|
||||||
this.bgmGain = this.ctx.createGain();
|
this.bgmGain = this.ctx.createGain();
|
||||||
this.sfxGain = this.ctx.createGain();
|
this.sfxGain = this.ctx.createGain();
|
||||||
|
this.voiceGain = this.ctx.createGain();
|
||||||
this.bgmGain.connect(this.master);
|
this.bgmGain.connect(this.master);
|
||||||
this.sfxGain.connect(this.master);
|
this.sfxGain.connect(this.master);
|
||||||
|
this.voiceGain.connect(this.master);
|
||||||
this.master.connect(this.ctx.destination);
|
this.master.connect(this.ctx.destination);
|
||||||
// iOS / iPadOS: a call, Siri or another app's audio moves a running context to 'interrupted' (or 'suspended');
|
// iOS / iPadOS: a call, Siri or another app's audio moves a running context to 'interrupted' (or 'suspended');
|
||||||
// a resume without a gesture may then be refused — listen for the next gesture again (dropped once it runs)
|
// a resume without a gesture may then be refused — listen for the next gesture again (dropped once it runs)
|
||||||
@@ -430,11 +568,16 @@ export class AudioManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set channel volumes (0..1) and mute.
|
* Set channel volumes (0..1) and mute.
|
||||||
* @param {{ bgm?: number, sfx?: number, muted?: boolean }} v
|
* @param {{ bgm?: number, sfx?: number, voice?: number, muted?: boolean }} v
|
||||||
*/
|
*/
|
||||||
setVolumes(v) {
|
setVolumes(v) {
|
||||||
const n = (x, d) => (Number.isFinite(x) ? Math.max(0, Math.min(1, x)) : d);
|
const n = (x, d) => (Number.isFinite(x) ? Math.max(0, Math.min(1, x)) : d);
|
||||||
this.volumes = { bgm: n(v?.bgm, this.volumes.bgm), sfx: n(v?.sfx, this.volumes.sfx), muted: typeof v?.muted === 'boolean' ? v.muted : this.volumes.muted };
|
this.volumes = {
|
||||||
|
bgm: n(v?.bgm, this.volumes.bgm),
|
||||||
|
sfx: n(v?.sfx, this.volumes.sfx),
|
||||||
|
voice: n(v?.voice, this.volumes.voice),
|
||||||
|
muted: typeof v?.muted === 'boolean' ? v.muted : this.volumes.muted,
|
||||||
|
};
|
||||||
this._applyVolumes();
|
this._applyVolumes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,6 +589,8 @@ export class AudioManager {
|
|||||||
// perceptual curve
|
// perceptual curve
|
||||||
this.bgmGain.gain.setTargetAtTime(this.volumes.bgm ** 2 * 0.55, t, 0.05);
|
this.bgmGain.gain.setTargetAtTime(this.volumes.bgm ** 2 * 0.55, t, 0.05);
|
||||||
this.sfxGain.gain.setTargetAtTime(this.volumes.sfx ** 2 * 0.9, t, 0.03);
|
this.sfxGain.gain.setTargetAtTime(this.volumes.sfx ** 2 * 0.9, t, 0.03);
|
||||||
|
// no 0.9: a voice line is already mastered as loud as the rest of the official mix (settings 干员语音 tunes it)
|
||||||
|
this.voiceGain.gain.setTargetAtTime(this.volumes.voice ** 2, t, 0.03);
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,15 +627,35 @@ export class AudioManager {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
this.buffers.set(url, p);
|
this.buffers.set(url, p);
|
||||||
while (this.buffers.size > BUFFER_CACHE) {
|
p.then((buf) => {
|
||||||
const first = this.buffers.keys().next().value;
|
if (!buf) return;
|
||||||
// never evict the playing BGM
|
try {
|
||||||
if (this.bgm && first === this.bgm.loopUrl) { const v = this.buffers.get(first); this.buffers.delete(first); this.buffers.set(first, v); break; }
|
this.bufBytes.set(url, (buf.length || 0) * (buf.numberOfChannels || 1) * 4);
|
||||||
this.buffers.delete(first);
|
this._trimBuffers();
|
||||||
}
|
} catch { /* ignore */ }
|
||||||
|
}, () => {});
|
||||||
|
this._trimBuffers();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evict least-recently-used buffers until both the entry count and the decoded-PCM budget hold. The count alone is
|
||||||
|
* not enough once voice lines are in the cache: 180 of them are ~100 MB of PCM (a voice decodes to 0.4–1.3 MB).
|
||||||
|
*/
|
||||||
|
_trimBuffers() {
|
||||||
|
let bytes = 0;
|
||||||
|
for (const n of this.bufBytes.values()) bytes += n;
|
||||||
|
if (this.buffers.size <= BUFFER_CACHE && bytes <= BUFFER_BYTES) return;
|
||||||
|
for (const url of [...this.buffers.keys()]) {
|
||||||
|
if (this.buffers.size <= BUFFER_CACHE && bytes <= BUFFER_BYTES) break;
|
||||||
|
// never evict the playing BGM (a voice keeps its own reference to its buffer)
|
||||||
|
if (this.bgm && url === this.bgm.loopUrl) continue;
|
||||||
|
bytes -= this.bufBytes.get(url) || 0;
|
||||||
|
this.buffers.delete(url);
|
||||||
|
this.bufBytes.delete(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Preload a list of URLs (e.g. UI SFX) once unlocked. */
|
/** Preload a list of URLs (e.g. UI SFX) once unlocked. */
|
||||||
preload(urls) {
|
preload(urls) {
|
||||||
if (!this.ctx) return;
|
if (!this.ctx) return;
|
||||||
@@ -640,6 +805,93 @@ export class AudioManager {
|
|||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- operator battle voice ----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play an operator's battle line (`audio.voice[charId][slot]`; a slot with several lines draws one at random).
|
||||||
|
* Only in battle: every caller is a running battle's own event stream or its settlement (user request — the 休整期
|
||||||
|
* is silent). The line must pass VoiceGate: one at a time, a global gap, a per-unit cooldown, higher priority wins.
|
||||||
|
* @param {string} charId e.g. 'char_263_skadi'
|
||||||
|
* @param {'start'|'faceEnemy'|'select'|'place'|'skill1'|'skill2'|'skill3'|'skill4'|'squad'|'squadFirst'
|
||||||
|
* |'resultFour'|'resultThree'|'resultTwo'|'resultLose'|'gacha'} slot
|
||||||
|
* @param {{ unitKey?: string|number|null, volume?: number }} [o] `unitKey` = the cooldown key (a battle unit id)
|
||||||
|
* @returns {boolean} whether such a line exists and started
|
||||||
|
*/
|
||||||
|
voice(charId, slot, o = {}) {
|
||||||
|
try {
|
||||||
|
if (!this.ctx || !this.voiceGain || this.volumes.muted || this.volumes.voice <= 0) return false;
|
||||||
|
if (typeof charId !== 'string' || typeof slot !== 'string') return false;
|
||||||
|
const line = this.getManifest()?.audio?.voice?.[charId]?.[slot];
|
||||||
|
const url = Array.isArray(line) ? line[Math.floor(Math.random() * line.length)] : line;
|
||||||
|
if (typeof url !== 'string' || !url) return false;
|
||||||
|
const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
||||||
|
const verdict = this.voiceGate.request(slot, o.unitKey ?? null, now);
|
||||||
|
if (verdict === 'drop') return false;
|
||||||
|
if (verdict === 'preempt') this._stopVoice();
|
||||||
|
this.voiceGate.start(slot, o.unitKey ?? null, now);
|
||||||
|
const token = ++this.voiceToken;
|
||||||
|
this._playVoice(url, token, o.volume);
|
||||||
|
return true;
|
||||||
|
} catch (err) { this._warn('voice', err); return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch/decode and start one voice line through the voice channel. */
|
||||||
|
_playVoice(url, token, volume) {
|
||||||
|
// `token` is the line's own `voiceToken`. Every deferred step below — the decode, a failed fetch, `onended` and the
|
||||||
|
// safety timer — can land AFTER this line was taken over or stopped: `voiceToken` has moved on and the channel then
|
||||||
|
// belongs to the line that replaced it. So each step re-checks its token and, when it is stale, touches NOTHING:
|
||||||
|
// `_stopVoice` (takeover / stop) and `setFieldUnits` released the gate themselves. An unconditional release here let
|
||||||
|
// a stale callback free the channel the NEW line had just taken, and the next line walked in on top of it (review
|
||||||
|
// on #73).
|
||||||
|
this._buffer(url).then((buf) => {
|
||||||
|
if (token !== this.voiceToken) return; // taken over / stopped while it decoded: not ours to release
|
||||||
|
if (!buf || !this.ctx || !this.voiceGain) { this.voiceGate.release(); return; }
|
||||||
|
try {
|
||||||
|
const src = this.ctx.createBufferSource();
|
||||||
|
src.buffer = buf;
|
||||||
|
const gain = this.ctx.createGain();
|
||||||
|
gain.gain.value = Math.max(0, Math.min(1.5, Number.isFinite(volume) ? volume : 1));
|
||||||
|
src.connect(gain); gain.connect(this.voiceGain);
|
||||||
|
const node = { src, gain, url, token };
|
||||||
|
let done = false;
|
||||||
|
const end = () => {
|
||||||
|
if (done) return;
|
||||||
|
done = true;
|
||||||
|
// this line's own end (natural, or the safety timer): only the line that still owns the channel may free it.
|
||||||
|
// A stale end is the takeover's leftovers — `_stopVoice` already faded it out and released the gate.
|
||||||
|
if (token === this.voiceToken) {
|
||||||
|
if (this.voiceNode === node) this.voiceNode = null;
|
||||||
|
this.voiceGate.release();
|
||||||
|
}
|
||||||
|
try { gain.disconnect(); } catch { /* ignore */ }
|
||||||
|
};
|
||||||
|
src.onended = end;
|
||||||
|
setTimeout(end, (buf.duration + 0.3) * 1000); // safety if onended never fires
|
||||||
|
src.start();
|
||||||
|
this.voiceNode = node;
|
||||||
|
} catch (err) {
|
||||||
|
this._warn('voice-play', err);
|
||||||
|
if (token === this.voiceToken) this.voiceGate.release();
|
||||||
|
}
|
||||||
|
}, () => { if (token === this.voiceToken) this.voiceGate.release(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fade the line on air out (a higher priority line is taking the channel over). */
|
||||||
|
_stopVoice() {
|
||||||
|
const cur = this.voiceNode;
|
||||||
|
this.voiceNode = null;
|
||||||
|
this.voiceToken += 1; // a line still decoding must not start afterwards
|
||||||
|
this.voiceGate.release();
|
||||||
|
if (!cur || !this.ctx) return;
|
||||||
|
try {
|
||||||
|
const t = this.ctx.currentTime;
|
||||||
|
cur.gain.gain.cancelScheduledValues(t);
|
||||||
|
cur.gain.gain.setValueAtTime(cur.gain.gain.value, t);
|
||||||
|
cur.gain.gain.linearRampToValueAtTime(0, t + VOICE_XFADE_S);
|
||||||
|
cur.src.stop(t + VOICE_XFADE_S + 0.02);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
// ---- battle events ------------------------------------------------------------------------------------------
|
// ---- battle events ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Reset the unit map for a new field (m.field.units = UnitInfo[]). */
|
/** Reset the unit map for a new field (m.field.units = UnitInfo[]). */
|
||||||
@@ -647,6 +899,9 @@ export class AudioManager {
|
|||||||
this.units.clear();
|
this.units.clear();
|
||||||
this.lastAttacker.clear();
|
this.lastAttacker.clear();
|
||||||
this.consumed.clear();
|
this.consumed.clear();
|
||||||
|
// a new field is a new battle: the first operator deployed says 行动出发 again and no cooldown carries over
|
||||||
|
this.startVoiceDone = false;
|
||||||
|
try { this.voiceGate.reset(); this._stopVoice(); } catch { /* ignore */ }
|
||||||
for (const u of Array.isArray(units) ? units : []) this._track(u);
|
for (const u of Array.isArray(units) ? units : []) this._track(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,7 +948,18 @@ export class AudioManager {
|
|||||||
this.battle('heal', { unitKey: `heal:${e[1]}`, volume: 0.35 });
|
this.battle('heal', { unitKey: `heal:${e[1]}`, volume: 0.35 });
|
||||||
} else if (kind === 'skill' && e[2]) {
|
} else if (kind === 'skill' && e[2]) {
|
||||||
const u = this.units.get(e[1]);
|
const u = this.units.get(e[1]);
|
||||||
if (u) this.unit(u.def, 'skill', e[1], u.skillIndex ?? undefined);
|
if (u) {
|
||||||
|
this.unit(u.def, 'skill', e[1], u.skillIndex ?? undefined);
|
||||||
|
// 作战中N: the equipped skill's own slot (0-based; 作战中4 is the fallback of a 4th slot)
|
||||||
|
if (unitSoundClass(u) === 'char') {
|
||||||
|
const n = Number.isInteger(u.skillIndex) ? Math.min(4, u.skillIndex + 1) : 1;
|
||||||
|
this.voice(u.def, `skill${n}`, { unitKey: e[1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (kind === 'engage') {
|
||||||
|
// 行动开始: the first attack a unit makes on an enemy (the sim's ENGAGE, official ENCOUNTER_ENEMY, 3 s apart)
|
||||||
|
const u = this.units.get(e[1]);
|
||||||
|
if (u && unitSoundClass(u) === 'char') this.voice(u.def, 'faceEnemy', { unitKey: e[1] });
|
||||||
} else if (kind === 'die') {
|
} else if (kind === 'die') {
|
||||||
const u = this.units.get(e[1]);
|
const u = this.units.get(e[1]);
|
||||||
if (!u) continue;
|
if (!u) continue;
|
||||||
@@ -710,6 +976,15 @@ export class AudioManager {
|
|||||||
if (!u || u.side === 'enemy') continue;
|
if (!u || u.side === 'enemy') continue;
|
||||||
const m = this.getManifest();
|
const m = this.getManifest();
|
||||||
const url = deploySfxUrl(m, u);
|
const url = deploySfxUrl(m, u);
|
||||||
|
// 行动出发 / 部署: the first operator of the battle says the battle-start line, the others their deploy line
|
||||||
|
// (a knocked-out operator redeploying in the same battle is one of the others; a summon says nothing).
|
||||||
|
// Kept ahead of the deploy-SFX guards below: the voice channel is independent of the unit sound's roll.
|
||||||
|
if (unitSoundClass(u) === 'char') {
|
||||||
|
if (!this.startVoiceDone) {
|
||||||
|
this.startVoiceDone = true;
|
||||||
|
if (!this.voice(u.def, 'start')) this.voice(u.def, 'place', { unitKey: e[1] });
|
||||||
|
} else this.voice(u.def, 'place', { unitKey: e[1] });
|
||||||
|
}
|
||||||
if (!url) continue;
|
if (!url) continue;
|
||||||
const own = url === m?.audio?.sfx?.units?.[u.def]?.born;
|
const own = url === m?.audio?.sfx?.units?.[u.def]?.born;
|
||||||
const mix = own ? m.audio.sfx.units[u.def].mix?.born : null;
|
const mix = own ? m.audio.sfx.units[u.def].mix?.born : null;
|
||||||
@@ -739,7 +1014,7 @@ export const audio = new AudioManager({ getManifest: () => manifestGetter() });
|
|||||||
/**
|
/**
|
||||||
* Wire the singleton to the app (called once by main.js): manifest source, settings and store-driven BGM.
|
* Wire the singleton to the app (called once by main.js): manifest source, settings and store-driven BGM.
|
||||||
* @param {{ getManifest: () => any, subscribe: (fn: (s:any, prev:any) => void) => () => void, getState: () => any,
|
* @param {{ getManifest: () => any, subscribe: (fn: (s:any, prev:any) => void) => () => void, getState: () => any,
|
||||||
* selectRoute: (s:any) => string, settings?: { bgm:number, sfx:number, muted:boolean } }} deps
|
* selectRoute: (s:any) => string, settings?: { bgm:number, sfx:number, voice:number, muted:boolean } }} deps
|
||||||
*/
|
*/
|
||||||
export function installAudio(deps) {
|
export function installAudio(deps) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -452,6 +452,9 @@ export function createBattleRunner(deps) {
|
|||||||
} catch (err) { console.warn('[runner] result failed', err); }
|
} catch (err) { console.warn('[runner] result failed', err); }
|
||||||
if (result) {
|
if (result) {
|
||||||
e.result = result;
|
e.result = result;
|
||||||
|
// the view answers with the settlement voice of this battle (screens/game.js → audio.voice result*): the
|
||||||
|
// compact result carries the leaks and the kill count the slot is picked from
|
||||||
|
emit('result', { fieldId: e.fieldId, battleId: e.battleId, own: !!e.own, result });
|
||||||
deliver(e);
|
deliver(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ import { battleRunner } from '../battle/runner.js';
|
|||||||
import { isClientCombat, observeTarget, teammateProgress, cameraLayers, layerCamera, sidesOf, resumedWatch } from '../battle/observe.js';
|
import { isClientCombat, observeTarget, teammateProgress, cameraLayers, layerCamera, sidesOf, resumedWatch } from '../battle/observe.js';
|
||||||
import { screenStrip, playerBonds, playerLayer, detailBondOwner, toggleBond, popupView } from '../ui/watchBonds.js';
|
import { screenStrip, playerBonds, playerLayer, detailBondOwner, toggleBond, popupView } from '../ui/watchBonds.js';
|
||||||
import { data, localAsset, getMode } from '../data.js';
|
import { data, localAsset, getMode } from '../data.js';
|
||||||
import { audio } from '../audio.js';
|
import { audio, resultSpeaker, resultVoiceSlot } from '../audio.js';
|
||||||
import { useDocClass, FullscreenButton } from '../ui/device.js';
|
import { useDocClass, FullscreenButton } from '../ui/device.js';
|
||||||
|
|
||||||
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
||||||
@@ -493,8 +493,30 @@ function MatchScreen() {
|
|||||||
view?.pushEvents(msg);
|
view?.pushEvents(msg);
|
||||||
audio.handleBattleEvents(msg.ev);
|
audio.handleBattleEvents(msg.ev);
|
||||||
};
|
};
|
||||||
|
// 干员语音 (结算): the own battle's result just came in — the operator's line depends on how it went
|
||||||
|
// (完美作战 ⇒ 3星结束行动, 绝境 / 终极 ⇒ 完成高难行动, 有漏怪 ⇒ 非3星结束行动, 一个没杀 ⇒ 行动失败)
|
||||||
|
const onResult = (msg) => {
|
||||||
|
try {
|
||||||
|
if (!msg || !msg.result) return;
|
||||||
|
const st = store.get();
|
||||||
|
const pid = st.me?.playerId;
|
||||||
|
const mine = (pid && msg.result.perPlayer && msg.result.perPlayer[pid]) || null;
|
||||||
|
const diff = st.match?.public?.difficulty;
|
||||||
|
// the speaker comes from THIS battle's own field (`mine.unitsEnd`), not from the field on screen: watching a
|
||||||
|
// teammate used to make THEIR operator say the viewer's line (review on #73)
|
||||||
|
const charId = resultSpeaker(mine);
|
||||||
|
if (!charId) return;
|
||||||
|
audio.voice(charId, resultVoiceSlot({
|
||||||
|
perfect: !!(mine?.perfect),
|
||||||
|
leaked: Array.isArray(mine?.leaked) ? mine.leaked.length : 0,
|
||||||
|
killed: mine?.killed ?? msg.result.killed,
|
||||||
|
total: mine?.total ?? msg.result.total,
|
||||||
|
hard: diff === 'HARD' || diff === 'ABYSS',
|
||||||
|
}));
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
};
|
||||||
const offs = [net.on('m.field', onFieldMeta), net.on('b.snap', onSnap), net.on('b.ev', onEv)];
|
const offs = [net.on('m.field', onFieldMeta), net.on('b.snap', onSnap), net.on('b.ev', onEv)];
|
||||||
if (battleRunner) offs.push(battleRunner.on('field', onFieldMeta), battleRunner.on('snap', onSnap), battleRunner.on('ev', onEv));
|
if (battleRunner) offs.push(battleRunner.on('field', onFieldMeta), battleRunner.on('snap', onSnap), battleRunner.on('ev', onEv), battleRunner.on('result', onResult));
|
||||||
return () => { for (const off of offs) { try { off(); } catch { /* ignore */ } } clearTimeout(pending); };
|
return () => { for (const off of offs) { try { off(); } catch { /* ignore */ } } clearTimeout(pending); };
|
||||||
}, [view]);
|
}, [view]);
|
||||||
|
|
||||||
@@ -1311,7 +1333,7 @@ function MatchScreen() {
|
|||||||
onClose=${() => setBondOpen(null)} onMember=${(id, items) => setDetail({ kind: 'chess', id, owner: bondPop.ownerId, items: items || null })} />` : null}
|
onClose=${() => setBondOpen(null)} onMember=${(id, items) => setDetail({ kind: 'chess', id, owner: bondPop.ownerId, items: items || null })} />` : null}
|
||||||
|
|
||||||
${resolved ? html`<${DetailPanel} detail=${resolved} snapHp=${snapHp} onClose=${() => { setDetail(null); setSel(null); }}
|
${resolved ? html`<${DetailPanel} detail=${resolved} snapHp=${snapHp} onClose=${() => { setDetail(null); setSel(null); }}
|
||||||
bonds=${detailBonds} offBonds=${offBonds} loadout=${detailLoadout} side=${dSide} shopOpen=${shopOpen} live=${liveStats}
|
bonds=${detailBonds} offBonds=${offBonds} loadout=${detailLoadout} side=${dSide} shopOpen=${shopOpen} live=${liveStats} voice=${combat}
|
||||||
onBond=${(id) => openBond(id, detailOwner, 'detail')} />` : null}
|
onBond=${(id) => openBond(id, detailOwner, 'detail')} />` : null}
|
||||||
|
|
||||||
${selEntry && editable && !facing && !drag && showPrep ? html`<${Underframe} key=${sel.uid} view=${view} uid=${sel.uid}
|
${selEntry && editable && !facing && !drag && showPrep ? html`<${Underframe} key=${sel.uid} view=${view} uid=${sel.uid}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export const ICONS = {
|
|||||||
info: { d: 'M12 2a10 10 0 1 1 0 20 10 10 0 0 1 0-20zm-1 8v7h2v-7zm0-3v2h2V7z', eo: true },
|
info: { d: 'M12 2a10 10 0 1 1 0 20 10 10 0 0 1 0-20zm-1 8v7h2v-7zm0-3v2h2V7z', eo: true },
|
||||||
warn: { d: 'M12 2 1 21h22zm-1 7v6h2V9zm0 7.5v2h2v-2z', eo: true },
|
warn: { d: 'M12 2 1 21h22zm-1 7v6h2V9zm0 7.5v2h2v-2z', eo: true },
|
||||||
play: { d: 'M7 4v16l13-8z' },
|
play: { d: 'M7 4v16l13-8z' },
|
||||||
|
mic: { d: 'M12 14a3 3 0 0 0 3-3V5a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3zm5-3a5 5 0 0 1-10 0H5a7 7 0 0 0 6 6.9V21H8v2h8v-2h-3v-3.1A7 7 0 0 0 19 11z' },
|
||||||
edit: { d: 'M3 17.2V21h3.8l11-11-3.8-3.8zM20.7 7.1a1 1 0 0 0 0-1.4l-2.4-2.4a1 1 0 0 0-1.4 0l-1.8 1.8 3.8 3.8z' },
|
edit: { d: 'M3 17.2V21h3.8l11-11-3.8-3.8zM20.7 7.1a1 1 0 0 0 0-1.4l-2.4-2.4a1 1 0 0 0-1.4 0l-1.8 1.8 3.8 3.8z' },
|
||||||
sword: { d: 'M20 3h1v4L10.4 17.6l1.4 1.4-1.4 1.4-2.1-2.1-3.5 3.5-1.4-1.4 3.5-3.5L4.8 14.8l1.4-1.4 1.4 1.4L18 4z' },
|
sword: { d: 'M20 3h1v4L10.4 17.6l1.4 1.4-1.4 1.4-2.1-2.1-3.5 3.5-1.4-1.4 3.5-3.5L4.8 14.8l1.4-1.4 1.4 1.4L18 4z' },
|
||||||
shield: { d: 'M12 2 4 5v6c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5z' },
|
shield: { d: 'M12 2 4 5v6c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5z' },
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
// The stats block (chessStatsBlock), the 特性 text (traitText) and the talent list (chessTalents) are exported: the 干员调配
|
// The stats block (chessStatsBlock), the 特性 text (traitText) and the talent list (chessTalents) are exported: the 干员调配
|
||||||
// screen's 局内数值 section draws the same ones for the chosen skill / module, without a live entry (GitHub issue #64).
|
// screen's 局内数值 section draws the same ones for the chosen skill / module, without a live entry (GitHub issue #64).
|
||||||
|
|
||||||
|
import { useEffect } from '../../vendor/hooks.module.js';
|
||||||
import { html, Icon, TierChip, MicroLabel, Button, confirmDialog, useTicker } from './components.js';
|
import { html, Icon, TierChip, MicroLabel, Button, confirmDialog, useTicker } from './components.js';
|
||||||
import { Img, RichText, UnitThumb, BondGlyph, GIcon } from './gameComponents.js';
|
import { Img, RichText, UnitThumb, BondGlyph, GIcon } from './gameComponents.js';
|
||||||
import { attackInterval, rangeGridBox, fmtNum, tileKey, chessLoadout, nextThreshold, bondTier, briefingBondTip, pieceBondIds, grantedBonds, morphPairings } from './gameLogic.js';
|
import { attackInterval, rangeGridBox, fmtNum, tileKey, chessLoadout, nextThreshold, bondTier, briefingBondTip, pieceBondIds, grantedBonds, morphPairings } from './gameLogic.js';
|
||||||
@@ -40,6 +41,7 @@ import { data } from '../data.js';
|
|||||||
import { attackRangeGrid } from '../../../shared/loadoutRecord.js';
|
import { attackRangeGrid } from '../../../shared/loadoutRecord.js';
|
||||||
import { SKILL_SUMMON_START_DEPLOY } from '../../../shared/constants.js';
|
import { SKILL_SUMMON_START_DEPLOY } from '../../../shared/constants.js';
|
||||||
import { moduleBadge } from './loadoutModel.js';
|
import { moduleBadge } from './loadoutModel.js';
|
||||||
|
import { audio } from '../audio.js';
|
||||||
|
|
||||||
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
||||||
|
|
||||||
@@ -651,10 +653,19 @@ export function resolveDetail(target, pieces) {
|
|||||||
* = the defaults
|
* = the defaults
|
||||||
* live: the unit's live stats (unitStatsEntry + src 'battle' | 'prep') — an object, or a getter the panel re-reads 4×
|
* live: the unit's live stats (unitStatsEntry + src 'battle' | 'prep') — an object, or a getter the panel re-reads 4×
|
||||||
* a second (the battle's own sim, battle/runner.js unitStats); null ⇒ the record's numbers
|
* a second (the battle's own sim, battle/runner.js unitStats); null ⇒ the record's numbers
|
||||||
|
* voice: whether the panel may speak — 选中干员 (audio.voice 'select') plays only while a battle runs (user request:
|
||||||
|
* 整备期不播干员语音), so the game screen passes its combat flag
|
||||||
*/
|
*/
|
||||||
export function DetailPanel({ detail, editable, snapHp, onClose, onSell, onDestroy, bonds = [], offBonds = null, loadout = null, onBond = null, side = 'left', shopOpen = false, live = null }) {
|
export function DetailPanel({ detail, editable, snapHp, onClose, onSell, onDestroy, bonds = [], offBonds = null, loadout = null, onBond = null, side = 'left', shopOpen = false, live = null, voice = false }) {
|
||||||
const getter = typeof live === 'function' ? live : null;
|
const getter = typeof live === 'function' ? live : null;
|
||||||
useTicker(detail && getter ? 250 : 0);
|
useTicker(detail && getter ? 250 : 0);
|
||||||
|
// 选中干员 voice (audio.voice 'select'): once per opened operator — the panel stays mounted while the target changes,
|
||||||
|
// so the key carries what identifies it (its chess record and its piece / battle unit id)
|
||||||
|
const selectKey = voice && detail?.type === 'chess' ? `${detail.chess?.chessId || ''}:${detail.unitId ?? detail.piece?.uid ?? ''}` : null;
|
||||||
|
const selectChar = voice && detail?.type === 'chess' ? detail.chess?.charId || null : null;
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectKey && selectChar) audio.voice(selectChar, 'select');
|
||||||
|
}, [selectKey, selectChar]);
|
||||||
if (!detail) return null;
|
if (!detail) return null;
|
||||||
let liveNow = null;
|
let liveNow = null;
|
||||||
try { liveNow = getter ? getter() : live && typeof live === 'object' ? live : null; } catch { liveNow = null; }
|
try { liveNow = getter ? getter() : live && typeof live === 'object' ? live : null; } catch { liveNow = null; }
|
||||||
|
|||||||
@@ -1580,13 +1580,13 @@ export function shortcutBlocked(act, { modal = false, drawer = false } = {}) {
|
|||||||
|
|
||||||
// ---- settings ------------------------------------------------------------------------------------------------------
|
// ---- settings ------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS = Object.freeze({ bgm: 0.6, sfx: 0.8, muted: false, damageNumbers: true, quality: 'high' });
|
export const DEFAULT_SETTINGS = Object.freeze({ bgm: 0.6, sfx: 0.8, voice: 0.8, muted: false, damageNumbers: true, quality: 'high' });
|
||||||
const QUALITIES = ['high', 'medium', 'low'];
|
const QUALITIES = ['high', 'medium', 'low'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize persisted settings.
|
* Sanitize persisted settings.
|
||||||
* @param {any} raw
|
* @param {any} raw
|
||||||
* @returns {{ bgm: number, sfx: number, muted: boolean, damageNumbers: boolean, quality: 'high'|'medium'|'low' }}
|
* @returns {{ bgm: number, sfx: number, voice: number, muted: boolean, damageNumbers: boolean, quality: 'high'|'medium'|'low' }}
|
||||||
*/
|
*/
|
||||||
export function sanitizeSettings(raw) {
|
export function sanitizeSettings(raw) {
|
||||||
const r = isObj(raw) ? raw : {};
|
const r = isObj(raw) ? raw : {};
|
||||||
@@ -1594,6 +1594,7 @@ export function sanitizeSettings(raw) {
|
|||||||
return {
|
return {
|
||||||
bgm: vol(r.bgm, DEFAULT_SETTINGS.bgm),
|
bgm: vol(r.bgm, DEFAULT_SETTINGS.bgm),
|
||||||
sfx: vol(r.sfx, DEFAULT_SETTINGS.sfx),
|
sfx: vol(r.sfx, DEFAULT_SETTINGS.sfx),
|
||||||
|
voice: vol(r.voice, DEFAULT_SETTINGS.voice),
|
||||||
muted: typeof r.muted === 'boolean' ? r.muted : DEFAULT_SETTINGS.muted,
|
muted: typeof r.muted === 'boolean' ? r.muted : DEFAULT_SETTINGS.muted,
|
||||||
damageNumbers: typeof r.damageNumbers === 'boolean' ? r.damageNumbers : DEFAULT_SETTINGS.damageNumbers,
|
damageNumbers: typeof r.damageNumbers === 'boolean' ? r.damageNumbers : DEFAULT_SETTINGS.damageNumbers,
|
||||||
quality: QUALITIES.includes(r.quality) ? r.quality : DEFAULT_SETTINGS.quality,
|
quality: QUALITIES.includes(r.quality) ? r.quality : DEFAULT_SETTINGS.quality,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Player settings (BGM/SFX volume, mute, damage numbers, render quality): a tiny observable store
|
// Player settings (BGM/SFX/voice volume, mute, damage numbers, render quality): a tiny observable store
|
||||||
// persisted in localStorage (`sp.pref.settings`), applied to the audio manager on every change, plus
|
// persisted in localStorage (`sp.pref.settings`), applied to the audio manager on every change, plus
|
||||||
// the settings modal.
|
// the settings modal.
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import { audio } from '../audio.js';
|
|||||||
import { openGuide } from './guide.js';
|
import { openGuide } from './guide.js';
|
||||||
import { detectFeatures } from './device.js';
|
import { detectFeatures } from './device.js';
|
||||||
|
|
||||||
/** Settings store: { bgm, sfx, muted, damageNumbers, quality }. */
|
/** Settings store: { bgm, sfx, voice, muted, damageNumbers, quality }. */
|
||||||
export const settingsStore = createStore(sanitizeSettings(loadPref('settings', null)));
|
export const settingsStore = createStore(sanitizeSettings(loadPref('settings', null)));
|
||||||
|
|
||||||
settingsStore.subscribe((s) => {
|
settingsStore.subscribe((s) => {
|
||||||
@@ -60,6 +60,7 @@ export function SettingsModal({ open, onClose }) {
|
|||||||
<${Button} variant="primary" icon="check" onClick=${onClose}>完成<//>`}>
|
<${Button} variant="primary" icon="check" onClick=${onClose}>完成<//>`}>
|
||||||
<div class="set-list">
|
<div class="set-list">
|
||||||
<${Slider} label="背景音乐" micro="BGM" icon="play" value=${s.bgm} onInput=${(v) => updateSettings({ bgm: v })} />
|
<${Slider} label="背景音乐" micro="BGM" icon="play" value=${s.bgm} onInput=${(v) => updateSettings({ bgm: v })} />
|
||||||
|
<${Slider} label="干员语音" micro="VOICE" icon="mic" value=${s.voice} onInput=${(v) => updateSettings({ voice: v })} />
|
||||||
<${Slider} label="音效" micro="SFX" icon="signal" value=${s.sfx}
|
<${Slider} label="音效" micro="SFX" icon="signal" value=${s.sfx}
|
||||||
onInput=${(v) => { updateSettings({ sfx: v }); if (!tested) { setTested(true); setTimeout(() => setTested(false), 400); audio.sfx('click'); } }} />
|
onInput=${(v) => { updateSettings({ sfx: v }); if (!tested) { setTested(true); setTimeout(() => setTested(false), 400); audio.sfx('click'); } }} />
|
||||||
<${Toggle} label="静音" micro="MUTE" value=${s.muted} onChange=${(v) => updateSettings({ muted: v })} />
|
<${Toggle} label="静音" micro="MUTE" value=${s.muted} onChange=${(v) => updateSettings({ muted: v })} />
|
||||||
|
|||||||
@@ -155,6 +155,12 @@ export function performAttack(b, u, prof, targets, opts = null) {
|
|||||||
u.stats.attacks++;
|
u.stats.attacks++;
|
||||||
const attackId = ++b._attackSeq; // every damage instance of this attack (all targets, splash, chain) carries it
|
const attackId = ++b._attackSeq; // every damage instance of this attack (all targets, splash, chain) carries it
|
||||||
const isHeal = !!(prof.heal && prof.dmgType === 'heal');
|
const isHeal = !!(prof.heal && prof.dmgType === 'heal');
|
||||||
|
// 首次接敌 (official voice type ENCOUNTER_ENEMY, ≥ 3 s between two such lines): one event the first time a unit
|
||||||
|
// attacks an enemy, whatever the attack is — the client answers with that operator's 行动开始 line (audio.js voice).
|
||||||
|
if (!isHeal && u.side === 'ally' && !u.mem.engaged && targets.some((t) => t && t.side === 'enemy')) {
|
||||||
|
u.mem.engaged = true;
|
||||||
|
b._ev(['engage', u.id]);
|
||||||
|
}
|
||||||
const ranged = !prof._fortressMelee && prof.attack === 'ranged' && prof.projectile && prof.projectile !== 'none' && prof.projectile !== 'beam';
|
const ranged = !prof._fortressMelee && prof.attack === 'ranged' && prof.projectile && prof.projectile !== 'none' && prof.projectile !== 'beam';
|
||||||
const vis = prof._fortressMelee ? 'none' : (prof.projectile || 'none');
|
const vis = prof._fortressMelee ? 'none' : (prof.projectile || 'none');
|
||||||
for (let i = 0; i < targets.length; i++) {
|
for (let i = 0; i < targets.length; i++) {
|
||||||
|
|||||||
+1
-1
@@ -341,7 +341,7 @@ export function validateC2S(msg) {
|
|||||||
|
|
||||||
// Event tuple kinds inside `b.ev` (DESIGN §8.2).
|
// Event tuple kinds inside `b.ev` (DESIGN §8.2).
|
||||||
export const EV = Object.freeze({
|
export const EV = Object.freeze({
|
||||||
SPAWN: 'spawn', ATK: 'atk', DMG: 'dmg', HEAL: 'heal', SKILL: 'skill', DIE: 'die', LEAK: 'leak',
|
SPAWN: 'spawn', ATK: 'atk', DMG: 'dmg', HEAL: 'heal', SKILL: 'skill', ENGAGE: 'engage', DIE: 'die', LEAK: 'leak',
|
||||||
STATUS: 'status', FX: 'fx', LAYER: 'layer', BOUNTY: 'bounty', DEPLOY: 'deploy',
|
STATUS: 'status', FX: 'fx', LAYER: 'layer', BOUNTY: 'bounty', DEPLOY: 'deploy',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,9 @@
|
|||||||
// feedback after 0.1.0 (DESIGN §21, v0.1.1) including batch 6 (§21.21–§21.25: 坚固维式重锤 once per deployment, 起飞,
|
// feedback after 0.1.0 (DESIGN §21, v0.1.1) including batch 6 (§21.21–§21.25: 坚固维式重锤 once per deployment, 起飞,
|
||||||
// fenced tiles, knocked-out bodies, the dispatcher snapshot and the manifest shrink guard credited to PR #2 / PR #7) and
|
// fenced tiles, knocked-out bodies, the dispatcher snapshot and the manifest shrink guard credited to PR #2 / PR #7) and
|
||||||
// the 突变细胞 bench rule (§21.1: the carrier destroyed, its new operator gained into the 整备区 — official footage, PR #2),
|
// the 突变细胞 bench rule (§21.1: the carrier destroyed, its new operator gained into the 整备区 — official footage, PR #2),
|
||||||
// the closing additions §21.26–§21.28 (GitHub issues #1 / #5 / #8) and the owner's deliberate trigger deviation for six
|
// the closing additions §21.26–§21.28 (GitHub issues #1 / #5 / #8), the owner's deliberate trigger deviation for six
|
||||||
// 重装 skills (§21.29, GitHub issue #4 / PR #12).
|
// 重装 skills (§21.29, GitHub issue #4 / PR #12) and the operator battle voice the user asked for the same day (§21.30,
|
||||||
|
// battle only — the 休整期 is silent).
|
||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
@@ -845,3 +846,57 @@ test('the deliberate trigger deviation (DESIGN §21.29): six 重装 skills DEFAU
|
|||||||
assert.match(PLAYING, /深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放/);
|
assert.match(PLAYING, /深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放/);
|
||||||
assert.match(doc('CHANGELOG.md'), /深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能改为攻击范围内有敌人时就释放/);
|
assert.match(doc('CHANGELOG.md'), /深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能改为攻击范围内有敌人时就释放/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('干员战斗语音 (DESIGN §21.30): the manifest data, the official priorities, and the 休整期 stays silent', async () => {
|
||||||
|
const { VOICE_PRIORITY, VOICE_COOLDOWN_MS, resultVoiceSlot } = await import('../public/js/audio.js');
|
||||||
|
const manifest = JSON.parse(readFileSync(join(ROOT, 'data/assets.json'), 'utf8'));
|
||||||
|
const voice = manifest.audio?.voice ?? {};
|
||||||
|
const charIds = Object.keys(voice);
|
||||||
|
assert.ok(charIds.length >= 100, `${charIds.length} operators carry official battle voice`);
|
||||||
|
assert.equal(manifest.stats.voiceChars, charIds.length, 'stats.voiceChars counts them');
|
||||||
|
// one operator carries every slot a battle can play — and none of the prep-only ones (plan.mjs VOICE_BATTLE_SLOTS):
|
||||||
|
// 干员报到 / 编入队伍 / 任命队长 are never requested by the client, so planning them only made every `npm run assets`
|
||||||
|
// download 360 files (19.3 MB, one per operator and slot) nobody hears. `--voice-all` brings the complete set back.
|
||||||
|
const slots = ['start', 'faceEnemy', 'select', 'place', 'skill1', 'skill2', 'skill3', 'skill4', 'resultFour', 'resultThree', 'resultTwo', 'resultLose'];
|
||||||
|
const prepOnly = ['gacha', 'squad', 'squadFirst'];
|
||||||
|
const lines = [];
|
||||||
|
const walk = (x) => {
|
||||||
|
if (typeof x === 'string') lines.push(x);
|
||||||
|
else if (Array.isArray(x)) x.forEach(walk);
|
||||||
|
else if (x && typeof x === 'object') Object.values(x).forEach(walk);
|
||||||
|
};
|
||||||
|
for (const id of charIds) {
|
||||||
|
for (const s of slots) assert.ok(voice[id][s], `${id}.${s}`);
|
||||||
|
for (const s of prepOnly) assert.equal(voice[id][s], undefined, `${id}.${s} is not planned by default`);
|
||||||
|
walk(voice[id]);
|
||||||
|
}
|
||||||
|
// a battle slot usually carries several lines (选中干员 / 部署 have two), so the battle set alone stays well above 10 each
|
||||||
|
assert.ok(lines.length >= charIds.length * 10, `${lines.length} voice lines for ${charIds.length} operators`);
|
||||||
|
for (const u of lines) assert.match(u, /^\/assets\/audio\/voice\/cn\/char_[^/]+\/cn_\d+\.mp3$/);
|
||||||
|
// the official scheduling numbers (audio_data.json battleVoice.voiceTypeOptions)
|
||||||
|
assert.equal(VOICE_PRIORITY.start, 100);
|
||||||
|
assert.equal(VOICE_PRIORITY.faceEnemy, 90);
|
||||||
|
assert.equal(VOICE_PRIORITY.skill1, 70);
|
||||||
|
assert.equal(VOICE_PRIORITY.place, 20);
|
||||||
|
assert.equal(VOICE_PRIORITY.select, 10);
|
||||||
|
assert.equal(VOICE_COOLDOWN_MS.skill1, 10000);
|
||||||
|
assert.equal(VOICE_COOLDOWN_MS.faceEnemy, 3000);
|
||||||
|
assert.equal(VOICE_COOLDOWN_MS.select, 1500);
|
||||||
|
assert.equal(resultVoiceSlot({ perfect: true }), 'resultThree');
|
||||||
|
// the docs
|
||||||
|
assert.match(DESIGN, /### 21\.30 /);
|
||||||
|
assert.match(DESIGN, /\*\*Where each line plays — battle only\.\*\*/);
|
||||||
|
assert.match(doc('docs/ASSETS.md'), /\| 干员战斗语音 \|/);
|
||||||
|
assert.match(doc('docs/ASSETS.md'), /voiceChars/);
|
||||||
|
assert.match(SIM, /\['engage', id\]/);
|
||||||
|
// the code: every slot the client asks for comes from a running battle's own stream — the three prep-only lines
|
||||||
|
// (干员报到 / 编入队伍 / 任命队长) are never requested, and 选中干员 sits behind the panel's combat flag
|
||||||
|
const panel = doc('public/js/ui/detailPanel.js');
|
||||||
|
const game = doc('public/js/screens/game.js');
|
||||||
|
for (const [name, src] of [['audio.js', doc('public/js/audio.js')], ['game.js', game], ['detailPanel.js', panel]]) {
|
||||||
|
assert.ok(!/voice\([^)]*'(gacha|squad|squadFirst)'/.test(src), `${name}: no prep slot is played`);
|
||||||
|
}
|
||||||
|
assert.match(panel, /const selectKey = voice && detail\?\.type === 'chess'/);
|
||||||
|
assert.match(game, /voice=\$\{combat\}/);
|
||||||
|
assert.match(game, /audio\.voice\(charId, resultVoiceSlot\(/);
|
||||||
|
});
|
||||||
@@ -68,6 +68,10 @@ test('fetch-assets shrink guard: the committed manifest minus some audio entries
|
|||||||
assert.deepEqual(shrinkGuard(null, next, parseArgs([])), { dropped: [], write: true }, 'no current manifest: written');
|
assert.deepEqual(shrinkGuard(null, next, parseArgs([])), { dropped: [], write: true }, 'no current manifest: written');
|
||||||
assert.equal(parseArgs(['--allow-shrink']).allowShrink, true);
|
assert.equal(parseArgs(['--allow-shrink']).allowShrink, true);
|
||||||
assert.throws(() => parseArgs(['--allow-shrinks']), /unknown option/);
|
assert.throws(() => parseArgs(['--allow-shrinks']), /unknown option/);
|
||||||
|
// 干员战斗语音: the prep-only slots are opt-in (DESIGN §21.30 — no battle requests them)
|
||||||
|
assert.equal(parseArgs([]).voiceAll, false, 'the battle slots are what a plain run plans');
|
||||||
|
assert.equal(parseArgs(['--voice-all']).voiceAll, true);
|
||||||
|
assert.equal(parseArgs(['--voice-lang=jp']).voiceLang, 'jp');
|
||||||
assert.equal(statSync(MANIFEST).mtimeMs, before, 'importing the tool runs nothing');
|
assert.equal(statSync(MANIFEST).mtimeMs, before, 'importing the tool runs nothing');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+46
-46
@@ -42,7 +42,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1019_jshoot":[5,5,0,513,10368],"enemy_1043_zomsbr":[18,18,0,351,54677],"enemy_1299_ymkilr":[1,1,0,2251,9792]},
|
"enemies": {"enemy_1019_jshoot":[5,5,0,513,10368],"enemy_1043_zomsbr":[18,18,0,351,54677],"enemy_1299_ymkilr":[1,1,0,2251,9792]},
|
||||||
"hooks": [1,35,1535,152,152,200,200,1064,24,24,3,1,0,6482,0,19,24,0,1,24,0,0,0],
|
"hooks": [1,35,1535,152,152,200,200,1064,24,24,3,1,0,6482,0,19,24,0,1,24,0,0,0],
|
||||||
"events": {"atk":152,"deploy":8,"die":24,"dmg":200,"fx":4,"skill":6,"spawn":35,"status":1},
|
"events": {"atk":152,"deploy":8,"die":24,"dmg":200,"engage":5,"fx":4,"skill":6,"spawn":35,"status":1},
|
||||||
"fx": {"buff":1,"coin":3},
|
"fx": {"buff":1,"coin":3},
|
||||||
"snaps": "6a513536 c150215b 81084fd8 9870355a f707b691 df63fdcd"
|
"snaps": "6a513536 c150215b 81084fd8 9870355a f707b691 df63fdcd"
|
||||||
},
|
},
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1381_winman":[9,9,0,0,129890],"enemy_1381_winman_2":[5,5,0,0,97044],"enemy_1387_winshd":[4,4,0,0,119439]},
|
"enemies": {"enemy_1381_winman":[9,9,0,0,129890],"enemy_1381_winman_2":[5,5,0,0,97044],"enemy_1387_winshd":[4,4,0,0,119439]},
|
||||||
"hooks": [1,29,2013,67,67,100,100,0,18,18,5,3,0,2605,44,0,18,0,1,18,0,0,0],
|
"hooks": [1,29,2013,67,67,100,100,0,18,18,5,3,0,2605,44,0,18,0,1,18,0,0,0],
|
||||||
"events": {"atk":79,"deploy":8,"die":18,"dmg":100,"fx":4,"skill":8,"spawn":29,"status":29},
|
"events": {"atk":79,"deploy":8,"die":18,"dmg":100,"engage":4,"fx":4,"skill":8,"spawn":29,"status":29},
|
||||||
"fx": {"crit":2,"dilemma":1,"shield":1},
|
"fx": {"crit":2,"dilemma":1,"shield":1},
|
||||||
"snaps": "db18b5ac a7e81526 897c5e72 065cee09 e78d2988 e1d0e7f9 cab65b67"
|
"snaps": "db18b5ac a7e81526 897c5e72 065cee09 e78d2988 e1d0e7f9 cab65b67"
|
||||||
},
|
},
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1249_lysdb_2":[5,3,2,346,24167],"enemy_1251_lysyta":[15,13,2,48,35396],"enemy_1328_cbjedi":[1,1,0,1940,9000]},
|
"enemies": {"enemy_1249_lysdb_2":[5,3,2,346,24167],"enemy_1251_lysyta":[15,13,2,48,35396],"enemy_1328_cbjedi":[1,1,0,1940,9000]},
|
||||||
"hooks": [1,31,1739,175,175,223,218,4,18,22,15,15,0,9423,15,9,21,4,1,18,0,0,0],
|
"hooks": [1,31,1739,175,175,223,218,4,18,22,15,15,0,9423,15,9,21,4,1,18,0,0,0],
|
||||||
"events": {"atk":192,"deploy":8,"die":18,"dmg":218,"fx":36,"leak":4,"skill":30,"spawn":31,"status":25},
|
"events": {"atk":192,"deploy":8,"die":18,"dmg":218,"engage":8,"fx":36,"leak":4,"skill":30,"spawn":31,"status":25},
|
||||||
"fx": {"aoe":1,"displace":11,"pull":11,"shieldBreak":5,"strike":3,"taunt":1,"volley":3,"zone":1},
|
"fx": {"aoe":1,"displace":11,"pull":11,"shieldBreak":5,"strike":3,"taunt":1,"volley":3,"zone":1},
|
||||||
"snaps": "b677776a 05df3ce3 3f4ea1ef 7c86ba28 c343548f 5e5789b8"
|
"snaps": "b677776a 05df3ce3 3f4ea1ef 7c86ba28 c343548f 5e5789b8"
|
||||||
},
|
},
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1005_yokai_2":[4,4,0,96,5357],"enemy_1005_yokai_3":[2,2,0,862,69120],"enemy_9009_acfort":[2,2,0,685,55296]},
|
"enemies": {"enemy_1005_yokai_2":[4,4,0,96,5357],"enemy_1005_yokai_3":[2,2,0,862,69120],"enemy_9009_acfort":[2,2,0,685,55296]},
|
||||||
"hooks": [1,21,2948,217,217,357,357,231,8,8,13,11,0,11234,107,0,8,0,1,7,0,0,0],
|
"hooks": [1,21,2948,217,217,357,357,231,8,8,13,11,0,11234,107,0,8,0,1,7,0,0,0],
|
||||||
"events": {"atk":217,"deploy":10,"die":8,"dmg":357,"fx":36,"heal":189,"skill":24,"spawn":21,"status":80},
|
"events": {"atk":217,"deploy":10,"die":8,"dmg":357,"engage":5,"fx":36,"heal":189,"skill":24,"spawn":21,"status":80},
|
||||||
"fx": {"beam":2,"counter":16,"pull":1,"slippery":2,"taunt":4,"tornado":1,"tornadoPulse":5,"zone":5},
|
"fx": {"beam":2,"counter":16,"pull":1,"slippery":2,"taunt":4,"tornado":1,"tornadoPulse":5,"zone":5},
|
||||||
"snaps": "f66ffc26 0aedc293 48dbf768 e46fb28f af06619a 9bc3f9d6 ea76c807 fc688b81 22b52f32 978323eb"
|
"snaps": "f66ffc26 0aedc293 48dbf768 e46fb28f af06619a 9bc3f9d6 ea76c807 fc688b81 22b52f32 978323eb"
|
||||||
},
|
},
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1166_dusbr":[15,15,0,761,51840],"enemy_1169_duphlx":[5,5,0,659,23040],"enemy_1170_dushld":[2,2,0,3376,16128]},
|
"enemies": {"enemy_1166_dusbr":[15,15,0,761,51840],"enemy_1169_duphlx":[5,5,0,659,23040],"enemy_1170_dushld":[2,2,0,3376,16128]},
|
||||||
"hooks": [1,32,2870,344,344,593,594,0,23,23,10,7,0,10052,41,13,22,0,1,24,7,0,0],
|
"hooks": [1,32,2870,344,344,593,594,0,23,23,10,7,0,10052,41,13,22,0,1,24,7,0,0],
|
||||||
"events": {"atk":395,"deploy":10,"die":23,"dmg":594,"fx":11,"layer":7,"skill":17,"spawn":31,"status":46},
|
"events": {"atk":395,"deploy":10,"die":23,"dmg":594,"engage":6,"fx":11,"layer":7,"skill":17,"spawn":31,"status":46},
|
||||||
"fx": {"devour":1,"dp":1,"garrison":3,"volley":3,"wolfShadow":2,"wolfShadowLost":1},
|
"fx": {"devour":1,"dp":1,"garrison":3,"volley":3,"wolfShadow":2,"wolfShadowLost":1},
|
||||||
"snaps": "d0724fb0 dc731840 afc7b763 32af0154 58ec6e8c ad015606 93516fa3 8d4b0184 6309839b fcfc5b4e"
|
"snaps": "d0724fb0 dc731840 afc7b763 32af0154 58ec6e8c ad015606 93516fa3 8d4b0184 6309839b fcfc5b4e"
|
||||||
},
|
},
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1229_darmy":[10,10,0,4638,104509],"enemy_1229_darmy_2":[7,7,0,18088,95800],"enemy_9007_acelem":[2,2,0,4609,179159]},
|
"enemies": {"enemy_1229_darmy":[10,10,0,4638,104509],"enemy_1229_darmy_2":[7,7,0,18088,95800],"enemy_9007_acelem":[2,2,0,4609,179159]},
|
||||||
"hooks": [1,29,2221,148,148,256,276,251,20,21,21,20,0,4154,485,13,19,0,1,20,5,3,0],
|
"hooks": [1,29,2221,148,148,256,276,251,20,21,21,20,0,4154,485,13,19,0,1,20,5,3,0],
|
||||||
"events": {"atk":167,"deploy":10,"die":21,"dmg":276,"fx":34,"layer":5,"skill":41,"spawn":27,"status":132},
|
"events": {"atk":167,"deploy":10,"die":21,"dmg":276,"engage":4,"fx":34,"layer":5,"skill":41,"spawn":27,"status":132},
|
||||||
"fx": {"beam":2,"blink":1,"burst":3,"devour":4,"dilemma":2,"frostNova":15,"garrison":5,"swordStorm":2},
|
"fx": {"beam":2,"blink":1,"burst":3,"devour":4,"dilemma":2,"frostNova":15,"garrison":5,"swordStorm":2},
|
||||||
"snaps": "066fec91 aec41fca b75071e0 478fe370 a68e442a 1b0b0907 cc527129 08c4150f"
|
"snaps": "066fec91 aec41fca b75071e0 478fe370 a68e442a 1b0b0907 cc527129 08c4150f"
|
||||||
},
|
},
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_10042_prtrop":[4,4,0,2248,21600],"enemy_10042_prtrop_2":[2,2,0,0,14400],"enemy_10043_sailor":[11,11,0,556,54450]},
|
"enemies": {"enemy_10042_prtrop":[4,4,0,2248,21600],"enemy_10042_prtrop_2":[2,2,0,0,14400],"enemy_10043_sailor":[11,11,0,556,54450]},
|
||||||
"hooks": [1,25,1676,144,144,252,252,34,18,18,5,3,0,6015,8,7,17,0,1,18,0,0,0],
|
"hooks": [1,25,1676,144,144,252,252,34,18,18,5,3,0,6015,8,7,17,0,1,18,0,0,0],
|
||||||
"events": {"atk":182,"deploy":8,"die":18,"dmg":251,"fx":26,"heal":34,"skill":8,"spawn":25,"status":39},
|
"events": {"atk":182,"deploy":8,"die":18,"dmg":251,"engage":6,"fx":26,"heal":34,"skill":8,"spawn":25,"status":39},
|
||||||
"fx": {"crit":6,"displace":8,"ripple":6,"shadowWeave":2,"shield":2,"shieldBreak":1,"stealth":1},
|
"fx": {"crit":6,"displace":8,"ripple":6,"shadowWeave":2,"shield":2,"shieldBreak":1,"stealth":1},
|
||||||
"snaps": "190730ef 965c573a 3f2abe1e 5c577114 4a917fdf 5586f99d"
|
"snaps": "190730ef 965c573a 3f2abe1e 5c577114 4a917fdf 5586f99d"
|
||||||
},
|
},
|
||||||
@@ -316,7 +316,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1045_hammer_2":[2,0,2,7798,28315],"enemy_1061_zomshd":[11,11,0,983,179484]},
|
"enemies": {"enemy_1045_hammer_2":[2,0,2,7798,28315],"enemy_1061_zomshd":[11,11,0,983,179484]},
|
||||||
"hooks": [1,23,3551,281,281,569,560,7994,12,14,14,13,0,5016,14,31,13,2,1,14,0,0,2],
|
"hooks": [1,23,3551,281,281,569,560,7994,12,14,14,13,0,5016,14,31,13,2,1,14,0,0,2],
|
||||||
"events": {"atk":376,"deploy":8,"die":12,"dmg":552,"fx":86,"heal":58,"leak":2,"skill":29,"spawn":23,"status":38},
|
"events": {"atk":376,"deploy":8,"die":12,"dmg":552,"engage":6,"fx":86,"heal":58,"leak":2,"skill":29,"spawn":23,"status":38},
|
||||||
"fx": {"block":7,"dodge":2,"flame":1,"garrisonGrant":1,"rockslide":6,"shieldBreak":8,"slash":26,"thorns":33,"truesilver":2},
|
"fx": {"block":7,"dodge":2,"flame":1,"garrisonGrant":1,"rockslide":6,"shieldBreak":8,"slash":26,"thorns":33,"truesilver":2},
|
||||||
"snaps": "48d2a0bc 7a5c72a3 804d5560 8c6ca634 c9bf651b 26081476 486ac1ff 38cf6fca 1c52bb36 3480567c b20bd7bb 65dfc07e"
|
"snaps": "48d2a0bc 7a5c72a3 804d5560 8c6ca634 c9bf651b 26081476 486ac1ff 38cf6fca 1c52bb36 3480567c b20bd7bb 65dfc07e"
|
||||||
},
|
},
|
||||||
@@ -358,7 +358,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1158_divman":[15,15,0,2085,57024],"enemy_1160_hvyslr":[2,2,0,4543,23040],"enemy_1161_tidmag":[1,1,0,2691,6912]},
|
"enemies": {"enemy_1158_divman":[15,15,0,2085,57024],"enemy_1160_hvyslr":[2,2,0,4543,23040],"enemy_1161_tidmag":[1,1,0,2691,6912]},
|
||||||
"hooks": [1,32,1887,159,159,249,257,1001,21,21,11,11,0,5499,102,7,18,0,1,21,1,1,3],
|
"hooks": [1,32,1887,159,159,249,257,1001,21,21,11,11,0,5499,102,7,18,0,1,21,1,1,3],
|
||||||
"events": {"atk":207,"deploy":11,"die":21,"dmg":257,"fx":25,"layer":1,"skill":22,"spawn":31,"status":80},
|
"events": {"atk":207,"deploy":11,"die":21,"dmg":257,"engage":6,"fx":25,"layer":1,"skill":22,"spawn":31,"status":80},
|
||||||
"fx": {"burst":1,"dilemma":1,"dodge":3,"explosion":4,"frostNova":2,"garrison":1,"levitate":7,"overclock":1,"shield":2,"slippery":1,"splash":2},
|
"fx": {"burst":1,"dilemma":1,"dodge":3,"explosion":4,"frostNova":2,"garrison":1,"levitate":7,"overclock":1,"shield":2,"slippery":1,"splash":2},
|
||||||
"snaps": "910f43b9 691d4675 c5aa756a 98b2fa4d ce14416b c2b3dc9f f49377cb"
|
"snaps": "910f43b9 691d4675 c5aa756a 98b2fa4d ce14416b c2b3dc9f f49377cb"
|
||||||
},
|
},
|
||||||
@@ -393,7 +393,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1166_dusbr_2":[14,11,3,27453,136496],"enemy_1170_dushld_2":[4,1,3,36563,52797],"enemy_9011_acrefr":[4,1,2,57576,114879]},
|
"enemies": {"enemy_1166_dusbr_2":[14,11,3,27453,136496],"enemy_1170_dushld_2":[4,1,3,36563,52797],"enemy_9011_acrefr":[4,1,2,57576,114879]},
|
||||||
"hooks": [1,52,5700,834,834,1143,1124,403,35,43,29,28,102,4003,202,60,22,8,1,35,0,0,19],
|
"hooks": [1,52,5700,834,834,1143,1124,403,35,43,29,28,102,4003,202,60,22,8,1,35,0,0,19],
|
||||||
"events": {"atk":1176,"deploy":30,"die":35,"dmg":1121,"fx":177,"heal":356,"leak":8,"skill":57,"spawn":30,"status":530},
|
"events": {"atk":1176,"deploy":30,"die":35,"dmg":1121,"engage":4,"fx":177,"heal":356,"leak":8,"skill":57,"spawn":30,"status":530},
|
||||||
"fx": {"bandage":4,"dodge":19,"extraAttack":54,"garrisonGrant":1,"mote":40,"redistribute":30,"revive":19,"reweave":2,"sandChains":3,"shield":4,"spGift":1},
|
"fx": {"bandage":4,"dodge":19,"extraAttack":54,"garrisonGrant":1,"mote":40,"redistribute":30,"revive":19,"reweave":2,"sandChains":3,"shield":4,"spGift":1},
|
||||||
"snaps": "a5613700 74b53427 dd5cc97e 0a644be4 18999580 50032d82 eacff749 082c6c93 279f01a1 f348cb6e 5e51d837 27e023a3 3cdea049 dadd297d 9129f525 42525d2f 77ff9862 aa26c61a 0b186db9"
|
"snaps": "a5613700 74b53427 dd5cc97e 0a644be4 18999580 50032d82 eacff749 082c6c93 279f01a1 f348cb6e 5e51d837 27e023a3 3cdea049 dadd297d 9129f525 42525d2f 77ff9862 aa26c61a 0b186db9"
|
||||||
},
|
},
|
||||||
@@ -430,7 +430,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1158_divman":[15,15,0,1502,44550],"enemy_1160_hvyslr":[2,2,0,4278,18000],"enemy_1161_tidmag":[1,1,0,3141,5400]},
|
"enemies": {"enemy_1158_divman":[15,15,0,1502,44550],"enemy_1160_hvyslr":[2,2,0,4278,18000],"enemy_1161_tidmag":[1,1,0,3141,5400]},
|
||||||
"hooks": [1,29,2220,218,218,320,333,873,22,20,14,14,0,6407,56,13,18,0,1,24,0,1,4],
|
"hooks": [1,29,2220,218,218,320,333,873,22,20,14,14,0,6407,56,13,18,0,1,24,0,1,4],
|
||||||
"events": {"atk":230,"deploy":9,"die":20,"dmg":330,"fx":56,"heal":15,"skill":32,"spawn":28,"status":91},
|
"events": {"atk":230,"deploy":9,"die":20,"dmg":330,"engage":7,"fx":56,"heal":15,"skill":32,"spawn":28,"status":91},
|
||||||
"fx": {"aoe":6,"burst":1,"coin":3,"dodge":4,"featherArrow":1,"flame":1,"garrisonGrant":1,"revive":2,"riposte":2,"rockslide":1,"shieldBreak":3,"sp":1,"swordRain":24,"swordStorm":4,"zone":2},
|
"fx": {"aoe":6,"burst":1,"coin":3,"dodge":4,"featherArrow":1,"flame":1,"garrisonGrant":1,"revive":2,"riposte":2,"rockslide":1,"shieldBreak":3,"sp":1,"swordRain":24,"swordStorm":4,"zone":2},
|
||||||
"snaps": "d6fd923e 40279e79 d9530f36 e7c8fb9d c7652603 66aea5b2 e03bbfae a99a96e2"
|
"snaps": "d6fd923e 40279e79 d9530f36 e7c8fb9d c7652603 66aea5b2 e03bbfae a99a96e2"
|
||||||
},
|
},
|
||||||
@@ -468,7 +468,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1229_darmy":[10,10,0,418,48384],"enemy_1229_darmy_2":[7,7,0,662,44352],"enemy_1275_dwlock_2":[3,3,0,4287,48384]},
|
"enemies": {"enemy_1229_darmy":[10,10,0,418,48384],"enemy_1229_darmy_2":[7,7,0,662,44352],"enemy_1275_dwlock_2":[3,3,0,4287,48384]},
|
||||||
"hooks": [1,31,2291,130,130,170,354,1,20,21,7,5,47,3964,0,9,20,0,1,64,0,1,0],
|
"hooks": [1,31,2291,130,130,170,354,1,20,21,7,5,47,3964,0,9,20,0,1,64,0,1,0],
|
||||||
"events": {"atk":132,"deploy":8,"die":21,"dmg":203,"fx":62,"skill":14,"spawn":31,"status":3},
|
"events": {"atk":132,"deploy":8,"die":21,"dmg":203,"engage":3,"fx":62,"skill":14,"spawn":31,"status":3},
|
||||||
"fx": {"anchor":2,"aoe":2,"beam":2,"burst":1,"coin":4,"ember":1,"explode":1,"revive":2,"splash":47},
|
"fx": {"anchor":2,"aoe":2,"beam":2,"burst":1,"coin":4,"ember":1,"explode":1,"revive":2,"splash":47},
|
||||||
"snaps": "a56e679b 7e159567 a5350d42 7ffa012b 9ceba6f2 a8bde46f 89918fc2 d5e5149a"
|
"snaps": "a56e679b 7e159567 a5350d42 7ffa012b 9ceba6f2 a8bde46f 89918fc2 d5e5149a"
|
||||||
},
|
},
|
||||||
@@ -508,7 +508,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr":[11,11,0,3450,69696],"enemy_1116_liprr_2":[4,4,0,394,34560],"enemy_1121_lifbos":[1,1,0,7192,23040]},
|
"enemies": {"enemy_1116_liprr":[11,11,0,3450,69696],"enemy_1116_liprr_2":[4,4,0,394,34560],"enemy_1121_lifbos":[1,1,0,7192,23040]},
|
||||||
"hooks": [1,28,1921,209,209,376,376,95,18,18,8,7,0,6440,13,21,16,0,1,18,2,0,0],
|
"hooks": [1,28,1921,209,209,376,376,95,18,18,8,7,0,6440,13,21,16,0,1,18,2,0,0],
|
||||||
"events": {"atk":246,"deploy":9,"die":18,"dmg":356,"fx":58,"heal":94,"layer":2,"skill":15,"spawn":27,"status":50},
|
"events": {"atk":246,"deploy":9,"die":18,"dmg":356,"engage":5,"fx":58,"heal":94,"layer":2,"skill":15,"spawn":27,"status":50},
|
||||||
"fx": {"displace":15,"garrison":2,"healField":1,"liberate":3,"redistribute":14,"reweave":1,"sandChains":1,"shield":2,"slash":18,"truesilver":1},
|
"fx": {"displace":15,"garrison":2,"healField":1,"liberate":3,"redistribute":14,"reweave":1,"sandChains":1,"shield":2,"slash":18,"truesilver":1},
|
||||||
"snaps": "c0ba6ff7 bba0d7f8 f42e9c18 f6f0af84 b51edddf 58a55b0c 987b2ee8"
|
"snaps": "c0ba6ff7 bba0d7f8 f42e9c18 f6f0af84 b51edddf 58a55b0c 987b2ee8"
|
||||||
},
|
},
|
||||||
@@ -545,7 +545,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_10124_uashld_2":[2,2,0,4508,124416],"enemy_1043_zomsbr":[23,23,0,20,145812],"enemy_1061_zomshd":[5,5,0,4181,132243]},
|
"enemies": {"enemy_10124_uashld_2":[2,2,0,4508,124416],"enemy_1043_zomsbr":[23,23,0,20,145812],"enemy_1061_zomshd":[5,5,0,4181,132243]},
|
||||||
"hooks": [1,41,2701,239,239,406,518,2221,33,33,13,12,0,5249,874,19,30,0,1,44,0,0,8],
|
"hooks": [1,41,2701,239,239,406,518,2221,33,33,13,12,0,5249,874,19,30,0,1,44,0,0,8],
|
||||||
"events": {"atk":239,"deploy":9,"die":33,"dmg":397,"fx":65,"heal":22,"skill":25,"spawn":40,"status":128},
|
"events": {"atk":239,"deploy":9,"die":33,"dmg":397,"engage":7,"fx":65,"heal":22,"skill":25,"spawn":40,"status":128},
|
||||||
"fx": {"aoe":28,"displace":17,"dodge":8,"flame":2,"overload":2,"riposte":3,"shield":2,"volley":3},
|
"fx": {"aoe":28,"displace":17,"dodge":8,"flame":2,"overload":2,"riposte":3,"shield":2,"volley":3},
|
||||||
"snaps": "1f83614e bead82e1 cf903f9e dcc2392a 63ec0242 18d85986 45dac5d7 c55ace88 05326cdb"
|
"snaps": "1f83614e bead82e1 cf903f9e dcc2392a 63ec0242 18d85986 45dac5d7 c55ace88 05326cdb"
|
||||||
},
|
},
|
||||||
@@ -588,7 +588,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1000_gopro_2":[19,19,0,0,29070],"enemy_1325_cbgpro":[5,5,0,0,17100],"enemy_1325_cbgpro_2":[3,3,0,0,12420]},
|
"enemies": {"enemy_1000_gopro_2":[19,19,0,0,29070],"enemy_1325_cbgpro":[5,5,0,0,17100],"enemy_1325_cbgpro_2":[3,3,0,0,12420]},
|
||||||
"hooks": [1,43,1318,64,64,120,120,0,27,31,3,3,0,5234,1,0,27,0,1,27,1,0,0],
|
"hooks": [1,43,1318,64,64,120,120,0,27,31,3,3,0,5234,1,0,27,0,1,27,1,0,0],
|
||||||
"events": {"atk":64,"deploy":13,"die":31,"dmg":120,"fx":37,"layer":1,"skill":8,"spawn":43,"status":2},
|
"events": {"atk":64,"deploy":13,"die":31,"dmg":120,"engage":4,"fx":37,"layer":1,"skill":8,"spawn":43,"status":2},
|
||||||
"fx": {"buff":1,"coin":8,"explode":4,"garrison":1,"garrisonGrant":1,"spGift":13,"strike":2,"summon":5,"volley":2},
|
"fx": {"buff":1,"coin":8,"explode":4,"garrison":1,"garrisonGrant":1,"spGift":13,"strike":2,"summon":5,"volley":2},
|
||||||
"snaps": "75591eda 13ef5faa 3682393e a23e5fa6 5d4daa5d"
|
"snaps": "75591eda 13ef5faa 3682393e a23e5fa6 5d4daa5d"
|
||||||
},
|
},
|
||||||
@@ -624,7 +624,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1267_nhpbr_2":[11,11,0,2752,69696],"enemy_1270_nhstlk_2":[4,4,0,0,73728],"enemy_1272_nhtank_2":[2,2,0,2359,34560]},
|
"enemies": {"enemy_1267_nhpbr_2":[11,11,0,2752,69696],"enemy_1270_nhstlk_2":[4,4,0,0,73728],"enemy_1272_nhtank_2":[2,2,0,2359,34560]},
|
||||||
"hooks": [1,26,2174,74,74,125,125,16,18,18,29,29,0,7659,1586,4,17,0,1,18,0,0,0],
|
"hooks": [1,26,2174,74,74,125,125,16,18,18,29,29,0,7659,1586,4,17,0,1,18,0,0,0],
|
||||||
"events": {"atk":78,"deploy":9,"die":18,"dmg":125,"fx":41,"heal":16,"skill":58,"spawn":26,"status":23},
|
"events": {"atk":78,"deploy":9,"die":18,"dmg":125,"engage":6,"fx":41,"heal":16,"skill":58,"spawn":26,"status":23},
|
||||||
"fx": {"coldWind":2,"frostNova":3,"healAoe":4,"strike":9,"summon":1,"volley":9,"zone":13},
|
"fx": {"coldWind":2,"frostNova":3,"healAoe":4,"strike":9,"summon":1,"volley":9,"zone":13},
|
||||||
"snaps": "89cecaf9 38614dcd 7761e0b8 9aeebba4 1f07677c 0a06c2c1 3c72c25c f68984bb"
|
"snaps": "89cecaf9 38614dcd 7761e0b8 9aeebba4 1f07677c 0a06c2c1 3c72c25c f68984bb"
|
||||||
},
|
},
|
||||||
@@ -661,7 +661,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1267_nhpbr":[11,11,0,4618,57024],"enemy_1267_nhpbr_2":[5,5,0,1141,31680],"enemy_1272_nhtank":[1,1,0,1689,13824]},
|
"enemies": {"enemy_1267_nhpbr":[11,11,0,4618,57024],"enemy_1267_nhpbr_2":[5,5,0,1141,31680],"enemy_1272_nhtank":[1,1,0,1689,13824]},
|
||||||
"hooks": [1,25,1853,134,134,486,486,0,20,20,7,4,15,7704,0,2,17,0,1,20,1,0,0],
|
"hooks": [1,25,1853,134,134,486,486,0,20,20,7,4,15,7704,0,2,17,0,1,20,1,0,0],
|
||||||
"events": {"atk":173,"deploy":8,"die":20,"dmg":486,"fx":24,"layer":1,"skill":11,"spawn":25,"status":1},
|
"events": {"atk":173,"deploy":8,"die":20,"dmg":486,"engage":7,"fx":24,"layer":1,"skill":11,"spawn":25,"status":1},
|
||||||
"fx": {"buff":1,"displace":2,"garrison":1,"pull":2,"zone":18},
|
"fx": {"buff":1,"displace":2,"garrison":1,"pull":2,"zone":18},
|
||||||
"snaps": "c9571e9b 447301c4 210539b8 00f65df0 cca396c8 6e1a7a83 293704b2"
|
"snaps": "c9571e9b 447301c4 210539b8 00f65df0 cca396c8 6e1a7a83 293704b2"
|
||||||
},
|
},
|
||||||
@@ -696,7 +696,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1006_shield_3":[3,3,0,3179,298598],"enemy_1043_zomsbr":[23,23,0,0,156273],"enemy_1402_tgshd_2":[4,4,0,4580,129393]},
|
"enemies": {"enemy_1006_shield_3":[3,3,0,3179,298598],"enemy_1043_zomsbr":[23,23,0,0,156273],"enemy_1402_tgshd_2":[4,4,0,4580,129393]},
|
||||||
"hooks": [1,38,2418,432,432,755,755,5077,32,32,14,11,243,4675,28,10,30,0,1,39,0,0,0],
|
"hooks": [1,38,2418,432,432,755,755,5077,32,32,14,11,243,4675,28,10,30,0,1,39,0,0,0],
|
||||||
"events": {"atk":524,"deploy":8,"die":32,"dmg":749,"fx":354,"skill":25,"spawn":38,"status":119},
|
"events": {"atk":524,"deploy":8,"die":32,"dmg":749,"engage":8,"fx":354,"skill":25,"spawn":38,"status":119},
|
||||||
"fx": {"aoe":5,"buff":2,"counter":6,"displace":40,"expose":286,"reload":1,"ripple":9,"sermon":1,"shield":3,"substitute":1},
|
"fx": {"aoe":5,"buff":2,"counter":6,"displace":40,"expose":286,"reload":1,"ripple":9,"sermon":1,"shield":3,"substitute":1},
|
||||||
"snaps": "86f46177 30356dbb 497a95c9 3fa2d7cd 201ae571 cff27ab7 d0813121 a9f6dc45"
|
"snaps": "86f46177 30356dbb 497a95c9 3fa2d7cd 201ae571 cff27ab7 d0813121 a9f6dc45"
|
||||||
},
|
},
|
||||||
@@ -734,7 +734,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1195_sfyin":[11,11,0,0,19800],"enemy_1196_msfyin":[22,22,0,0,44],"enemy_1197_sfshu":[5,5,0,0,11250],"enemy_1198_msfshu":[15,15,0,0,45]},
|
"enemies": {"enemy_1195_sfyin":[11,11,0,0,19800],"enemy_1196_msfyin":[22,22,0,0,44],"enemy_1197_sfshu":[5,5,0,0,11250],"enemy_1198_msfshu":[15,15,0,0,45]},
|
||||||
"hooks": [1,64,1377,123,123,188,188,0,53,53,13,10,0,7888,30,0,53,0,1,53,0,0,0],
|
"hooks": [1,64,1377,123,123,188,188,0,53,53,13,10,0,7888,30,0,53,0,1,53,0,0,0],
|
||||||
"events": {"atk":159,"deploy":9,"die":53,"dmg":188,"fx":25,"skill":23,"spawn":64,"status":47},
|
"events": {"atk":159,"deploy":9,"die":53,"dmg":188,"engage":7,"fx":25,"skill":23,"spawn":64,"status":47},
|
||||||
"fx": {"copy":1,"dp":1,"sleep":3,"strike":2,"summon":16,"volley":2},
|
"fx": {"copy":1,"dp":1,"sleep":3,"strike":2,"summon":16,"volley":2},
|
||||||
"snaps": "5711acf3 3aaf6dc4 ebdfdc7e a54fcb41 1044a950"
|
"snaps": "5711acf3 3aaf6dc4 ebdfdc7e a54fcb41 1044a950"
|
||||||
},
|
},
|
||||||
@@ -776,7 +776,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1288_duskls":[11,11,0,210,63415],"enemy_1288_duskls_2":[7,7,0,0,48419],"enemy_1292_duskld":[5,5,0,1224,57650]},
|
"enemies": {"enemy_1288_duskls":[11,11,0,210,63415],"enemy_1288_duskls_2":[7,7,0,0,48419],"enemy_1292_duskld":[5,5,0,1224,57650]},
|
||||||
"hooks": [1,38,2506,288,330,531,531,411,23,25,19,16,0,11219,246,29,23,0,1,53,0,0,0],
|
"hooks": [1,38,2506,288,330,531,531,411,23,25,19,16,0,11219,246,29,23,0,1,53,0,0,0],
|
||||||
"events": {"atk":346,"deploy":12,"die":25,"dmg":531,"fx":200,"skill":35,"spawn":38,"status":422},
|
"events": {"atk":346,"deploy":12,"die":25,"dmg":531,"engage":12,"fx":200,"skill":35,"spawn":38,"status":422},
|
||||||
"fx": {"aoe":2,"copy":1,"crit":6,"dp":2,"ember":23,"iceSpike":66,"palsy":3,"sleep":3,"split":3,"weightless":2,"zone":89},
|
"fx": {"aoe":2,"copy":1,"crit":6,"dp":2,"ember":23,"iceSpike":66,"palsy":3,"sleep":3,"split":3,"weightless":2,"zone":89},
|
||||||
"snaps": "936990ce cac0c6e5 0c5910e5 94cfd566 931c176f 4042d89c fadbeae3 2767137e 57fe0fc3"
|
"snaps": "936990ce cac0c6e5 0c5910e5 94cfd566 931c176f 4042d89c fadbeae3 2767137e 57fe0fc3"
|
||||||
},
|
},
|
||||||
@@ -811,7 +811,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr":[11,11,0,1505,69696],"enemy_1116_liprr_2":[4,4,0,774,34560],"enemy_1118_lidbox_2":[2,2,0,965,21888]},
|
"enemies": {"enemy_1116_liprr":[11,11,0,1505,69696],"enemy_1116_liprr_2":[4,4,0,774,34560],"enemy_1118_lidbox_2":[2,2,0,965,21888]},
|
||||||
"hooks": [1,25,1466,165,165,364,364,0,18,18,7,5,0,4660,11,12,17,0,1,18,0,0,0],
|
"hooks": [1,25,1466,165,165,364,364,0,18,18,7,5,0,4660,11,12,17,0,1,18,0,0,0],
|
||||||
"events": {"atk":206,"deploy":8,"die":18,"dmg":363,"fx":12,"skill":12,"spawn":25,"status":73},
|
"events": {"atk":206,"deploy":8,"die":18,"dmg":363,"engage":5,"fx":12,"skill":12,"spawn":25,"status":73},
|
||||||
"fx": {"buff":1,"displace":4,"ripple":1,"shield":2,"shieldBreak":1,"volley":3},
|
"fx": {"buff":1,"displace":4,"ripple":1,"shield":2,"shieldBreak":1,"volley":3},
|
||||||
"snaps": "303551a0 a1b61e4e 3d1fabc6 7f04a31d 9df08e32"
|
"snaps": "303551a0 a1b61e4e 3d1fabc6 7f04a31d 9df08e32"
|
||||||
},
|
},
|
||||||
@@ -850,7 +850,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1381_winman":[9,9,0,283,129890],"enemy_1381_winman_2":[5,5,0,2524,97044],"enemy_1387_winshd":[4,4,0,2683,119439]},
|
"enemies": {"enemy_1381_winman":[9,9,0,283,129890],"enemy_1381_winman_2":[5,5,0,2524,97044],"enemy_1387_winshd":[4,4,0,2683,119439]},
|
||||||
"hooks": [1,32,4532,473,473,816,829,0,20,21,37,35,12,11061,79,9,18,0,1,21,3,3,0],
|
"hooks": [1,32,4532,473,473,816,829,0,20,21,37,35,12,11061,79,9,18,0,1,21,3,3,0],
|
||||||
"events": {"atk":506,"deploy":12,"die":21,"dmg":816,"fx":168,"layer":3,"skill":72,"spawn":30,"status":152},
|
"events": {"atk":506,"deploy":12,"die":21,"dmg":816,"engage":10,"fx":168,"layer":3,"skill":72,"spawn":30,"status":152},
|
||||||
"fx": {"aoe":10,"buff":1,"burst":3,"charge":18,"counter":12,"crit":61,"displace":24,"garrison":3,"quadShot":2,"ripple":9,"sentry":1,"sermon":1,"shadowWeave":12,"shield":1,"soul":3,"stealth":3,"substitute":1,"summon":2,"swap":1},
|
"fx": {"aoe":10,"buff":1,"burst":3,"charge":18,"counter":12,"crit":61,"displace":24,"garrison":3,"quadShot":2,"ripple":9,"sentry":1,"sermon":1,"shadowWeave":12,"shield":1,"soul":3,"stealth":3,"substitute":1,"summon":2,"swap":1},
|
||||||
"snaps": "d2b18fc5 bd98a396 2815c1da cd98bca5 f9430f26 13b06e1e c8764716 073aa206 ddb6d29d c8d7eba3 26ac5044 a58c61f6 2c76342d ae7f4a4d 301020be c76c6184"
|
"snaps": "d2b18fc5 bd98a396 2815c1da cd98bca5 f9430f26 13b06e1e c8764716 073aa206 ddb6d29d c8d7eba3 26ac5044 a58c61f6 2c76342d ae7f4a4d 301020be c76c6184"
|
||||||
},
|
},
|
||||||
@@ -890,7 +890,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,0,27000],"enemy_1251_lysyta":[15,15,0,0,37125],"enemy_1328_cbjedi":[1,1,0,898,9000]},
|
"enemies": {"enemy_1249_lysdb_2":[5,5,0,0,27000],"enemy_1251_lysyta":[15,15,0,0,37125],"enemy_1328_cbjedi":[1,1,0,898,9000]},
|
||||||
"hooks": [1,34,1704,101,101,161,156,0,21,21,10,8,0,6803,29,1,21,0,1,21,3,0,0],
|
"hooks": [1,34,1704,101,101,161,156,0,21,21,10,8,0,6803,29,1,21,0,1,21,3,0,0],
|
||||||
"events": {"atk":125,"deploy":10,"die":21,"dmg":156,"fx":59,"layer":3,"skill":18,"spawn":34,"status":46},
|
"events": {"atk":125,"deploy":10,"die":21,"dmg":156,"engage":5,"fx":59,"layer":3,"skill":18,"spawn":34,"status":46},
|
||||||
"fx": {"charge":5,"dilemma":2,"displace":14,"garrison":3,"levitate":11,"pull":1,"shadowWeave":8,"shieldBreak":5,"slippery":1,"splash":2,"stealth":1,"tornado":1,"tornadoPulse":5},
|
"fx": {"charge":5,"dilemma":2,"displace":14,"garrison":3,"levitate":11,"pull":1,"shadowWeave":8,"shieldBreak":5,"slippery":1,"splash":2,"stealth":1,"tornado":1,"tornadoPulse":5},
|
||||||
"snaps": "c27c0600 6b7bfecf d505aebd b3d60eec b1e621c0 52cc3598"
|
"snaps": "c27c0600 6b7bfecf d505aebd b3d60eec b1e621c0 52cc3598"
|
||||||
},
|
},
|
||||||
@@ -930,7 +930,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1019_jshoot_2":[7,7,0,3373,21773],"enemy_1299_ymkilr_2":[3,3,0,1837,38016],"enemy_1422_lrsldr_2":[14,14,0,0,88704]},
|
"enemies": {"enemy_1019_jshoot_2":[7,7,0,3373,21773],"enemy_1299_ymkilr_2":[3,3,0,1837,38016],"enemy_1422_lrsldr_2":[14,14,0,0,88704]},
|
||||||
"hooks": [1,37,2071,84,84,166,166,663,26,26,7,6,0,4290,103,10,24,0,1,26,0,0,0],
|
"hooks": [1,37,2071,84,84,166,166,663,26,26,7,6,0,4290,103,10,24,0,1,26,0,0,0],
|
||||||
"events": {"atk":84,"deploy":10,"die":26,"dmg":166,"fx":21,"skill":13,"spawn":37,"status":10},
|
"events": {"atk":84,"deploy":10,"die":26,"dmg":166,"engage":5,"fx":21,"skill":13,"spawn":37,"status":10},
|
||||||
"fx": {"crit":2,"frostNova":1,"levitate":7,"overclock":1,"pull":1,"slippery":1,"strike":2,"tornado":1,"tornadoPulse":5},
|
"fx": {"crit":2,"frostNova":1,"levitate":7,"overclock":1,"pull":1,"slippery":1,"strike":2,"tornado":1,"tornadoPulse":5},
|
||||||
"snaps": "1c35197a b5a550c2 449f2d84 3e5d7032 3c466668 15903287 c2362cab"
|
"snaps": "1c35197a b5a550c2 449f2d84 3e5d7032 3c466668 15903287 c2362cab"
|
||||||
},
|
},
|
||||||
@@ -981,7 +981,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr":[11,11,0,27,69696],"enemy_1116_liprr_2":[4,4,0,324,34560],"enemy_1118_lidbox_2":[2,2,0,6654,21888]},
|
"enemies": {"enemy_1116_liprr":[11,11,0,27,69696],"enemy_1116_liprr_2":[4,4,0,324,34560],"enemy_1118_lidbox_2":[2,2,0,6654,21888]},
|
||||||
"hooks": [1,28,1632,185,185,252,250,65,20,21,13,13,0,5675,255,14,17,0,1,20,4,0,3],
|
"hooks": [1,28,1632,185,185,252,250,65,20,21,13,13,0,5675,255,14,17,0,1,20,4,0,3],
|
||||||
"events": {"atk":239,"deploy":9,"die":21,"dmg":225,"fx":30,"heal":38,"layer":4,"skill":28,"spawn":27,"status":64},
|
"events": {"atk":239,"deploy":9,"die":21,"dmg":225,"engage":7,"fx":30,"heal":38,"layer":4,"skill":28,"spawn":27,"status":64},
|
||||||
"fx": {"aoe":1,"blink":1,"dodge":3,"explosion":3,"flame":1,"frostNova":6,"garrison":4,"garrisonGrant":1,"liberate":5,"riposte":3,"rockslide":1,"shieldBreak":1},
|
"fx": {"aoe":1,"blink":1,"dodge":3,"explosion":3,"flame":1,"frostNova":6,"garrison":4,"garrisonGrant":1,"liberate":5,"riposte":3,"rockslide":1,"shieldBreak":1},
|
||||||
"snaps": "d63c71d9 d887e4cb 55b00fa0 66fcfa30 9df5bcce 63545fa4"
|
"snaps": "d63c71d9 d887e4cb 55b00fa0 66fcfa30 9df5bcce 63545fa4"
|
||||||
},
|
},
|
||||||
@@ -1021,7 +1021,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon":[7,7,0,3616,130637],"enemy_1422_lrsldr_2":[14,14,0,259,191601],"enemy_1425_lrcmra_2":[3,3,0,5726,186624]},
|
"enemies": {"enemy_1010_demon":[7,7,0,3616,130637],"enemy_1422_lrsldr_2":[14,14,0,259,191601],"enemy_1425_lrcmra_2":[3,3,0,5726,186624]},
|
||||||
"hooks": [1,38,1911,99,99,109,112,68,24,27,6,5,0,1925,0,14,24,0,1,24,1,0,0],
|
"hooks": [1,38,1911,99,99,109,112,68,24,27,6,5,0,1925,0,14,24,0,1,24,1,0,0],
|
||||||
"events": {"atk":107,"deploy":11,"die":27,"dmg":111,"fx":9,"heal":62,"layer":1,"skill":13,"spawn":35,"status":8},
|
"events": {"atk":107,"deploy":11,"die":27,"dmg":111,"engage":4,"fx":9,"heal":62,"layer":1,"skill":13,"spawn":35,"status":8},
|
||||||
"fx": {"aoe":3,"blink":3,"explosion":1,"garrison":1,"garrisonGrant":1},
|
"fx": {"aoe":3,"blink":3,"explosion":1,"garrison":1,"garrisonGrant":1},
|
||||||
"snaps": "c2c35b01 bcc47598 01104ea6 bc1ad03d c117d4a0 c04f551c 785ecf20"
|
"snaps": "c2c35b01 bcc47598 01104ea6 bc1ad03d c117d4a0 c04f551c 785ecf20"
|
||||||
},
|
},
|
||||||
@@ -1058,7 +1058,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1267_nhpbr":[11,10,0,4196,43172],"enemy_1267_nhpbr_2":[5,5,0,1614,24750],"enemy_1272_nhtank":[1,0,0,11294,2493]},
|
"enemies": {"enemy_1267_nhpbr":[11,10,0,4196,43172],"enemy_1267_nhpbr_2":[5,5,0,1614,24750],"enemy_1272_nhtank":[1,0,0,11294,2493]},
|
||||||
"hooks": [1,25,3300,239,239,525,525,337,15,15,20,20,0,14425,0,5,17,0,1,15,1,0,0],
|
"hooks": [1,25,3300,239,239,525,525,337,15,15,20,20,0,14425,0,5,17,0,1,15,1,0,0],
|
||||||
"events": {"atk":354,"deploy":8,"die":15,"dmg":486,"fx":112,"heal":337,"layer":1,"skill":40,"spawn":25,"status":247},
|
"events": {"atk":354,"deploy":8,"die":15,"dmg":486,"engage":3,"fx":112,"heal":337,"layer":1,"skill":40,"spawn":25,"status":247},
|
||||||
"fx": {"buff":1,"counter":15,"garrison":1,"healField":1,"mote":38,"redistribute":15,"reweave":1,"slash":17,"spGift":1,"truesilver":1,"zone":21},
|
"fx": {"buff":1,"counter":15,"garrison":1,"healField":1,"mote":38,"redistribute":15,"reweave":1,"slash":17,"spGift":1,"truesilver":1,"zone":21},
|
||||||
"snaps": "0bdd9351 b24b323e 7000bcb0 603642af 64df0f27 665eb936 b4c5e8d2 9fde297d b69942c5 7af576c0 dc317609"
|
"snaps": "0bdd9351 b24b323e 7000bcb0 603642af 64df0f27 665eb936 b4c5e8d2 9fde297d b69942c5 7af576c0 dc317609"
|
||||||
},
|
},
|
||||||
@@ -1093,7 +1093,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1006_shield_3":[3,3,0,5125,138240],"enemy_1043_zomsbr":[23,23,0,48,70867],"enemy_1402_tgshd_2":[4,4,0,1710,59904]},
|
"enemies": {"enemy_1006_shield_3":[3,3,0,5125,138240],"enemy_1043_zomsbr":[23,23,0,48,70867],"enemy_1402_tgshd_2":[4,4,0,1710,59904]},
|
||||||
"hooks": [1,39,3442,191,191,497,653,1788,32,32,16,14,0,6394,0,15,30,0,1,46,0,1,0],
|
"hooks": [1,39,3442,191,191,497,653,1788,32,32,16,14,0,6394,0,15,30,0,1,46,0,1,0],
|
||||||
"events": {"atk":225,"deploy":9,"die":32,"dmg":653,"fx":138,"heal":13,"skill":30,"spawn":38,"status":56},
|
"events": {"atk":225,"deploy":9,"die":32,"dmg":653,"engage":4,"fx":138,"heal":13,"skill":30,"spawn":38,"status":56},
|
||||||
"fx": {"aoe":5,"burst":1,"expose":63,"jungleSoul":1,"mote":20,"redistribute":25,"reweave":2,"slash":20,"truesilver":1},
|
"fx": {"aoe":5,"burst":1,"expose":63,"jungleSoul":1,"mote":20,"redistribute":25,"reweave":2,"slash":20,"truesilver":1},
|
||||||
"snaps": "2c6321b4 cd26a52a 90107827 1e040533 bf55c7a2 587efe24 04e3a230 2316a1be 704aab56 56e8ca2f 9b645a1a 7f429b8f"
|
"snaps": "2c6321b4 cd26a52a 90107827 1e040533 bf55c7a2 587efe24 04e3a230 2316a1be 704aab56 56e8ca2f 9b645a1a 7f429b8f"
|
||||||
},
|
},
|
||||||
@@ -1128,7 +1128,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon":[3,3,0,7576,25920],"enemy_1010_demon_2":[1,1,0,5601,17280],"enemy_1071_dftman":[15,15,0,880,48384]},
|
"enemies": {"enemy_1010_demon":[3,3,0,7576,25920],"enemy_1010_demon_2":[1,1,0,5601,17280],"enemy_1071_dftman":[15,15,0,880,48384]},
|
||||||
"hooks": [1,27,2214,233,233,292,318,0,24,24,4,3,0,4409,27,28,19,0,1,24,0,0,0],
|
"hooks": [1,27,2214,233,233,292,318,0,24,24,4,3,0,4409,27,28,19,0,1,24,0,0,0],
|
||||||
"events": {"atk":233,"deploy":8,"die":24,"dmg":318,"fx":50,"skill":11,"spawn":27,"status":53},
|
"events": {"atk":233,"deploy":8,"die":24,"dmg":318,"engage":8,"fx":50,"skill":11,"spawn":27,"status":53},
|
||||||
"fx": {"aoe":8,"coin":3,"crit":7,"dp":1,"strike":31},
|
"fx": {"aoe":8,"coin":3,"crit":7,"dp":1,"strike":31},
|
||||||
"snaps": "a194c978 d501eda8 b671f9fb e2590e39 2ab25c9c 6714c2bf ff63f649 9794310b"
|
"snaps": "a194c978 d501eda8 b671f9fb e2590e39 2ab25c9c 6714c2bf ff63f649 9794310b"
|
||||||
},
|
},
|
||||||
@@ -1166,7 +1166,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1158_divman":[20,20,0,7809,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1341,59720]},
|
"enemies": {"enemy_1158_divman":[20,20,0,7809,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1341,59720]},
|
||||||
"hooks": [1,39,2292,311,311,481,484,2,30,30,6,5,0,3813,40,4,26,0,1,35,3,0,0],
|
"hooks": [1,39,2292,311,311,481,484,2,30,30,6,5,0,3813,40,4,26,0,1,35,3,0,0],
|
||||||
"events": {"atk":312,"deploy":11,"die":30,"dmg":484,"fx":26,"heal":2,"layer":3,"skill":13,"spawn":37,"status":73},
|
"events": {"atk":312,"deploy":11,"die":30,"dmg":484,"engage":5,"fx":26,"heal":2,"layer":3,"skill":13,"spawn":37,"status":73},
|
||||||
"fx": {"aoe":3,"bondProc":8,"crit":5,"dp":4,"garrison":3,"wolfShadowLost":3},
|
"fx": {"aoe":3,"bondProc":8,"crit":5,"dp":4,"garrison":3,"wolfShadowLost":3},
|
||||||
"snaps": "d1ead0d3 cda7c00c 8843b429 bd54e255 1de8d432 e6f225a5 7cba25d9 d80a750d"
|
"snaps": "d1ead0d3 cda7c00c 8843b429 bd54e255 1de8d432 e6f225a5 7cba25d9 d80a750d"
|
||||||
},
|
},
|
||||||
@@ -1204,7 +1204,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon":[3,3,0,208,20250],"enemy_1010_demon_2":[1,1,0,202,13500],"enemy_1071_dftman":[15,15,0,0,37800]},
|
"enemies": {"enemy_1010_demon":[3,3,0,208,20250],"enemy_1010_demon_2":[1,1,0,202,13500],"enemy_1071_dftman":[15,15,0,0,37800]},
|
||||||
"hooks": [1,30,1601,160,160,234,234,0,19,19,8,7,0,8098,44,3,19,0,1,19,0,0,0],
|
"hooks": [1,30,1601,160,160,234,234,0,19,19,8,7,0,8098,44,3,19,0,1,19,0,0,0],
|
||||||
"events": {"atk":217,"deploy":8,"die":19,"dmg":234,"fx":45,"skill":15,"spawn":30,"status":70},
|
"events": {"atk":217,"deploy":8,"die":19,"dmg":234,"engage":8,"fx":45,"skill":15,"spawn":30,"status":70},
|
||||||
"fx": {"aoe":1,"displace":19,"pull":19,"sleep":2,"strike":2,"volley":2},
|
"fx": {"aoe":1,"displace":19,"pull":19,"sleep":2,"strike":2,"volley":2},
|
||||||
"snaps": "ccacd2b6 245aa361 460d6c7e 6f4413db 6fc4040a f85f49f4"
|
"snaps": "ccacd2b6 245aa361 460d6c7e 6f4413db 6fc4040a f85f49f4"
|
||||||
},
|
},
|
||||||
@@ -1239,7 +1239,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr_2":[10,10,0,0,86400],"enemy_1119_vofsd":[5,5,0,1379,63360],"enemy_1121_lifbos_2":[3,3,0,859,86400]},
|
"enemies": {"enemy_1116_liprr_2":[10,10,0,0,86400],"enemy_1119_vofsd":[5,5,0,1379,63360],"enemy_1121_lifbos_2":[3,3,0,859,86400]},
|
||||||
"hooks": [1,26,2146,166,166,326,386,8,19,19,7,5,0,4669,550,4,18,0,1,24,0,0,0],
|
"hooks": [1,26,2146,166,166,326,386,8,19,19,7,5,0,4669,550,4,18,0,1,24,0,0,0],
|
||||||
"events": {"atk":166,"deploy":8,"die":19,"dmg":326,"fx":9,"heal":8,"skill":12,"spawn":26,"status":122},
|
"events": {"atk":166,"deploy":8,"die":19,"dmg":326,"engage":4,"fx":9,"heal":8,"skill":12,"spawn":26,"status":122},
|
||||||
"fx": {"anchor":2,"overload":1,"sleep":1,"wake":5},
|
"fx": {"anchor":2,"overload":1,"sleep":1,"wake":5},
|
||||||
"snaps": "48e98491 573c930f b71c322f cfbb72a5 8df950de d67595c9 37d059c7 e5c9270e"
|
"snaps": "48e98491 573c930f b71c322f cfbb72a5 8df950de d67595c9 37d059c7 e5c9270e"
|
||||||
},
|
},
|
||||||
@@ -1291,7 +1291,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr":[11,11,0,81,69696],"enemy_1116_liprr_2":[4,4,0,977,34560],"enemy_1118_lidbox_2":[2,2,0,7396,21888]},
|
"enemies": {"enemy_1116_liprr":[11,11,0,81,69696],"enemy_1116_liprr_2":[4,4,0,977,34560],"enemy_1118_lidbox_2":[2,2,0,7396,21888]},
|
||||||
"hooks": [1,28,1536,202,202,317,317,0,19,19,7,5,9,6814,23,25,17,0,1,23,4,0,0],
|
"hooks": [1,28,1536,202,202,317,317,0,19,19,7,5,9,6814,23,25,17,0,1,23,4,0,0],
|
||||||
"events": {"atk":237,"deploy":9,"die":19,"dmg":313,"fx":43,"layer":4,"skill":12,"spawn":28,"status":81},
|
"events": {"atk":237,"deploy":9,"die":19,"dmg":313,"engage":8,"fx":43,"layer":4,"skill":12,"spawn":28,"status":81},
|
||||||
"fx": {"aoe":1,"camouflage":1,"counter":9,"displace":14,"dp":3,"garrison":4,"liberate":3,"ripple":4,"sermon":1,"shield":2,"wolfShadow":1},
|
"fx": {"aoe":1,"camouflage":1,"counter":9,"displace":14,"dp":3,"garrison":4,"liberate":3,"ripple":4,"sermon":1,"shield":2,"wolfShadow":1},
|
||||||
"snaps": "d8f99986 9338e549 3d97bb3e bfa3b539 5cca309f b0d6a324"
|
"snaps": "d8f99986 9338e549 3d97bb3e bfa3b539 5cca309f b0d6a324"
|
||||||
},
|
},
|
||||||
@@ -1336,7 +1336,7 @@
|
|||||||
"enemy_1208_msfji": [1,1,0,0,25]
|
"enemy_1208_msfji": [1,1,0,0,25]
|
||||||
},
|
},
|
||||||
"hooks": [1,78,2587,146,146,298,297,414,67,68,23,23,0,7567,60,14,65,1,1,79,0,0,1],
|
"hooks": [1,78,2587,146,146,298,297,414,67,68,23,23,0,7567,60,14,65,1,1,79,0,0,1],
|
||||||
"events": {"atk":192,"deploy":10,"die":67,"dmg":297,"fx":86,"heal":2,"leak":1,"skill":46,"spawn":76,"status":142},
|
"events": {"atk":192,"deploy":10,"die":67,"dmg":297,"engage":8,"fx":86,"heal":2,"leak":1,"skill":46,"spawn":76,"status":142},
|
||||||
"fx": {"aoe":9,"crit":7,"dodge":1,"dp":3,"sp":2,"substitute":2,"summon":20,"swap":1,"weightless":2,"zone":39},
|
"fx": {"aoe":9,"crit":7,"dodge":1,"dp":3,"sp":2,"substitute":2,"summon":20,"swap":1,"weightless":2,"zone":39},
|
||||||
"snaps": "f2b40c86 cb23fc22 5b0fad69 cc0ea8d5 97d90aeb 8b83ef35 d4ddce39 5be90841 2f3ae2d2"
|
"snaps": "f2b40c86 cb23fc22 5b0fad69 cc0ea8d5 97d90aeb 8b83ef35 d4ddce39 5be90841 2f3ae2d2"
|
||||||
},
|
},
|
||||||
@@ -1376,7 +1376,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1267_nhpbr":[11,11,0,1834,44550],"enemy_1267_nhpbr_2":[5,5,0,1600,24750],"enemy_1270_nhstlk":[2,2,0,1537,21600]},
|
"enemies": {"enemy_1267_nhpbr":[11,11,0,1834,44550],"enemy_1267_nhpbr_2":[5,5,0,1600,24750],"enemy_1270_nhstlk":[2,2,0,1537,21600]},
|
||||||
"hooks": [1,31,1508,78,78,266,266,2,20,20,8,5,0,6237,34,2,18,0,1,20,1,0,0],
|
"hooks": [1,31,1508,78,78,266,266,2,20,20,8,5,0,6237,34,2,18,0,1,20,1,0,0],
|
||||||
"events": {"atk":120,"deploy":10,"die":20,"dmg":266,"fx":52,"heal":2,"layer":1,"skill":13,"spawn":31,"status":62},
|
"events": {"atk":120,"deploy":10,"die":20,"dmg":266,"engage":5,"fx":52,"heal":2,"layer":1,"skill":13,"spawn":31,"status":62},
|
||||||
"fx": {"charge":1,"crit":3,"dilemma":1,"garrison":1,"heal":2,"levitate":13,"sentry":1,"shadowWeave":10,"slippery":1,"splash":2,"stealth":1,"zone":16},
|
"fx": {"charge":1,"crit":3,"dilemma":1,"garrison":1,"heal":2,"levitate":13,"sentry":1,"shadowWeave":10,"slippery":1,"splash":2,"stealth":1,"zone":16},
|
||||||
"snaps": "22a08fb1 8e30016a 0f02e464 8332af62 cf150c69"
|
"snaps": "22a08fb1 8e30016a 0f02e464 8332af62 cf150c69"
|
||||||
},
|
},
|
||||||
@@ -1413,7 +1413,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1251_lysyta_2":[19,19,0,133,70042],"enemy_1252_lysytb_2":[7,7,0,1898,72576],"enemy_1329_cbshld_2":[4,4,0,4619,59904]},
|
"enemies": {"enemy_1251_lysyta_2":[19,19,0,133,70042],"enemy_1252_lysytb_2":[7,7,0,1898,72576],"enemy_1329_cbshld_2":[4,4,0,4619,59904]},
|
||||||
"hooks": [1,40,3837,261,268,401,416,59,30,30,67,66,0,15797,44,5,30,0,1,30,0,2,0],
|
"hooks": [1,40,3837,261,268,401,416,59,30,30,67,66,0,15797,44,5,30,0,1,30,0,2,0],
|
||||||
"events": {"atk":299,"deploy":8,"die":30,"dmg":416,"fx":143,"heal":59,"skill":133,"spawn":40,"status":96},
|
"events": {"atk":299,"deploy":8,"die":30,"dmg":416,"engage":3,"fx":143,"heal":59,"skill":133,"spawn":40,"status":96},
|
||||||
"fx": {"aoe":9,"burst":2,"counter":10,"displace":30,"heal":45,"iceSpike":11,"ripple":9,"shadowWeave":20,"soul":2,"stealth":5},
|
"fx": {"aoe":9,"burst":2,"counter":10,"displace":30,"heal":45,"iceSpike":11,"ripple":9,"shadowWeave":20,"soul":2,"stealth":5},
|
||||||
"snaps": "d1a4c271 3f59c254 aa4de239 40a5e684 b35896d7 c3fa7c5d 3d19b457 305a5a12 0cbbe4fe 220ff09a d4060b93 f4a07a32 b6167539"
|
"snaps": "d1a4c271 3f59c254 aa4de239 40a5e684 b35896d7 c3fa7c5d 3d19b457 305a5a12 0cbbe4fe 220ff09a d4060b93 f4a07a32 b6167539"
|
||||||
},
|
},
|
||||||
@@ -1451,7 +1451,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,453,34560],"enemy_1251_lysyta":[15,15,0,229,47520],"enemy_1328_cbjedi":[1,1,0,1182,11520]},
|
"enemies": {"enemy_1249_lysdb_2":[5,5,0,453,34560],"enemy_1251_lysyta":[15,15,0,229,47520],"enemy_1328_cbjedi":[1,1,0,1182,11520]},
|
||||||
"hooks": [1,32,1604,173,173,234,225,9,23,22,15,14,21,5887,502,14,21,0,1,23,0,0,0],
|
"hooks": [1,32,1604,173,173,234,225,9,23,22,15,14,21,5887,502,14,21,0,1,23,0,0,0],
|
||||||
"events": {"atk":173,"deploy":8,"die":22,"dmg":219,"fx":50,"heal":2,"skill":31,"spawn":32,"status":98},
|
"events": {"atk":173,"deploy":8,"die":22,"dmg":219,"engage":6,"fx":50,"heal":2,"skill":31,"spawn":32,"status":98},
|
||||||
"fx": {"block":4,"explosion":3,"frostNova":6,"rockslide":2,"shieldBreak":11,"swordRain":12,"swordStorm":2,"thorns":10},
|
"fx": {"block":4,"explosion":3,"frostNova":6,"rockslide":2,"shieldBreak":11,"swordRain":12,"swordStorm":2,"thorns":10},
|
||||||
"snaps": "241cb7e7 8164a048 e60d878b 54b007e8 2a842e99 9f62b1be"
|
"snaps": "241cb7e7 8164a048 e60d878b 54b007e8 2a842e99 9f62b1be"
|
||||||
},
|
},
|
||||||
@@ -1486,7 +1486,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1166_dusbr_2":[14,8,6,3587,114489],"enemy_1169_duphlx_2":[7,0,7,3841,27585],"enemy_1172_dugago":[3,0,3,10832,66787]},
|
"enemies": {"enemy_1166_dusbr_2":[14,8,6,3587,114489],"enemy_1169_duphlx_2":[7,0,7,3841,27585],"enemy_1172_dugago":[3,0,3,10832,66787]},
|
||||||
"hooks": [1,35,4146,667,667,877,877,37,14,30,19,19,82,14466,301,11,24,16,1,16,0,0,0],
|
"hooks": [1,35,4146,667,667,877,877,37,14,30,19,19,82,14466,301,11,24,16,1,16,0,0,0],
|
||||||
"events": {"atk":762,"deploy":11,"die":14,"dmg":877,"fx":13,"heal":14,"leak":16,"skill":38,"spawn":32,"status":246},
|
"events": {"atk":762,"deploy":11,"die":14,"dmg":877,"engage":7,"fx":13,"heal":14,"leak":16,"skill":38,"spawn":32,"status":246},
|
||||||
"fx": {"featherArrow":2,"flame":3,"garrisonGrant":1,"revive":2,"stone":2,"taunt":3},
|
"fx": {"featherArrow":2,"flame":3,"garrisonGrant":1,"revive":2,"stone":2,"taunt":3},
|
||||||
"snaps": "e8a58cae e45c0ada 1d0104f3 5a4162ce f459b0c6 6078554c 0da631c2 89a53382 35812639 be18c73e 09fd2d12 0c74c047 b302eee8 1b6c5142"
|
"snaps": "e8a58cae e45c0ada 1d0104f3 5a4162ce f459b0c6 6078554c 0da631c2 89a53382 35812639 be18c73e 09fd2d12 0c74c047 b302eee8 1b6c5142"
|
||||||
},
|
},
|
||||||
@@ -1521,7 +1521,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,0,27000],"enemy_1251_lysyta":[15,15,0,0,37125],"enemy_1328_cbjedi":[1,1,0,0,9000]},
|
"enemies": {"enemy_1249_lysdb_2":[5,5,0,0,27000],"enemy_1251_lysyta":[15,15,0,0,37125],"enemy_1328_cbjedi":[1,1,0,0,9000]},
|
||||||
"hooks": [1,29,1341,115,115,277,272,1,21,21,5,3,14,5402,27,0,21,0,1,21,1,0,0],
|
"hooks": [1,29,1341,115,115,277,272,1,21,21,5,3,14,5402,27,0,21,0,1,21,1,0,0],
|
||||||
"events": {"atk":135,"deploy":8,"die":21,"dmg":272,"fx":54,"layer":1,"skill":8,"spawn":29,"status":81},
|
"events": {"atk":135,"deploy":8,"die":21,"dmg":272,"engage":5,"fx":54,"layer":1,"skill":8,"spawn":29,"status":81},
|
||||||
"fx": {"buff":1,"displace":14,"garrison":1,"garrisonGrant":1,"mote":16,"sandChains":1,"shieldBreak":5,"slash":7,"spGift":7,"truesilver":1},
|
"fx": {"buff":1,"displace":14,"garrison":1,"garrisonGrant":1,"mote":16,"sandChains":1,"shieldBreak":5,"slash":7,"spGift":7,"truesilver":1},
|
||||||
"snaps": "5fab4324 df1c9981 5d0b2fb3 40f389ee beecf492"
|
"snaps": "5fab4324 df1c9981 5d0b2fb3 40f389ee beecf492"
|
||||||
},
|
},
|
||||||
@@ -1558,7 +1558,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon":[7,7,0,1205,60480],"enemy_1422_lrsldr_2":[14,14,0,74,88704],"enemy_1425_lrcmra_2":[3,2,1,10147,70389]},
|
"enemies": {"enemy_1010_demon":[7,7,0,1205,60480],"enemy_1422_lrsldr_2":[14,14,0,74,88704],"enemy_1425_lrcmra_2":[3,2,1,10147,70389]},
|
||||||
"hooks": [1,34,3251,301,301,557,557,400,27,28,27,25,76,3567,117,10,24,1,1,27,0,0,0],
|
"hooks": [1,34,3251,301,301,557,557,400,27,28,27,25,76,3567,117,10,24,1,1,27,0,0,0],
|
||||||
"events": {"atk":349,"deploy":10,"die":27,"dmg":557,"fx":146,"leak":1,"skill":52,"spawn":34,"status":196},
|
"events": {"atk":349,"deploy":10,"die":27,"dmg":557,"engage":7,"fx":146,"leak":1,"skill":52,"spawn":34,"status":196},
|
||||||
"fx": {"charge":10,"crit":53,"dilemma":3,"displace":16,"garrisonGrant":1,"levitate":21,"overclock":1,"pull":1,"quadShot":2,"slippery":2,"sp":23,"splash":7,"tornado":1,"tornadoPulse":5},
|
"fx": {"charge":10,"crit":53,"dilemma":3,"displace":16,"garrisonGrant":1,"levitate":21,"overclock":1,"pull":1,"quadShot":2,"slippery":2,"sp":23,"splash":7,"tornado":1,"tornadoPulse":5},
|
||||||
"snaps": "52481b26 a970d314 2696635b 9c15cc8e 501817d0 a8faf661 7e05b7cf ffceee93 c505d863 f99ccd20 1c4895d8"
|
"snaps": "52481b26 a970d314 2696635b 9c15cc8e 501817d0 a8faf661 7e05b7cf ffceee93 c505d863 f99ccd20 1c4895d8"
|
||||||
},
|
},
|
||||||
@@ -1595,7 +1595,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr":[11,11,0,389,69696],"enemy_1116_liprr_2":[4,4,0,319,34560],"enemy_1118_lidbox_2":[2,0,2,13211,18290]},
|
"enemies": {"enemy_1116_liprr":[11,11,0,389,69696],"enemy_1116_liprr_2":[4,4,0,319,34560],"enemy_1118_lidbox_2":[2,0,2,13211,18290]},
|
||||||
"hooks": [1,27,1907,236,236,388,417,42,19,21,6,5,0,6837,0,20,17,2,1,20,0,0,0],
|
"hooks": [1,27,1907,236,236,388,417,42,19,21,6,5,0,6837,0,20,17,2,1,20,0,0,0],
|
||||||
"events": {"atk":236,"deploy":8,"die":19,"dmg":417,"fx":57,"heal":32,"leak":2,"skill":12,"spawn":27,"status":30},
|
"events": {"atk":236,"deploy":8,"die":19,"dmg":417,"engage":7,"fx":57,"heal":32,"leak":2,"skill":12,"spawn":27,"status":30},
|
||||||
"fx": {"aoe":7,"coin":3,"counter":3,"liberate":5,"overload":1,"revive":1,"strike":36,"taunt":1},
|
"fx": {"aoe":7,"coin":3,"counter":3,"liberate":5,"overload":1,"revive":1,"strike":36,"taunt":1},
|
||||||
"snaps": "0e312a6e 45b64065 23e7a18c 93f3311a 14f12d67 80b532e0 88f0c499"
|
"snaps": "0e312a6e 45b64065 23e7a18c 93f3311a 14f12d67 80b532e0 88f0c499"
|
||||||
},
|
},
|
||||||
@@ -1635,7 +1635,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1005_yokai_2":[4,4,0,1618,15428],"enemy_1041_lazerd_2":[3,3,0,5218,44790],"enemy_1042_frostd":[5,5,0,0,80870]},
|
"enemies": {"enemy_1005_yokai_2":[4,4,0,1618,15428],"enemy_1041_lazerd_2":[3,3,0,5218,44790],"enemy_1042_frostd":[5,5,0,0,80870]},
|
||||||
"hooks": [1,25,1933,76,76,78,78,17,12,12,10,9,0,5082,274,0,12,0,1,12,0,0,0],
|
"hooks": [1,25,1933,76,76,78,78,17,12,12,10,9,0,5082,274,0,12,0,1,12,0,0,0],
|
||||||
"events": {"atk":76,"deploy":10,"die":12,"dmg":78,"fx":12,"heal":12,"skill":19,"spawn":25,"status":19},
|
"events": {"atk":76,"deploy":10,"die":12,"dmg":78,"engage":3,"fx":12,"heal":12,"skill":19,"spawn":25,"status":19},
|
||||||
"fx": {"counter":3,"explosion":6,"overload":1,"taunt":2},
|
"fx": {"counter":3,"explosion":6,"overload":1,"taunt":2},
|
||||||
"snaps": "aa5b6d09 e8b3aed4 d166ba60 3b476f55 5f0dfd72 520958c0 1d7f9e77"
|
"snaps": "aa5b6d09 e8b3aed4 d166ba60 3b476f55 5f0dfd72 520958c0 1d7f9e77"
|
||||||
},
|
},
|
||||||
@@ -1671,7 +1671,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1116_liprr":[11,11,0,2113,54450],"enemy_1116_liprr_2":[4,4,0,932,27000],"enemy_1118_lidbox_2":[2,1,1,5973,16462]},
|
"enemies": {"enemy_1116_liprr":[11,11,0,2113,54450],"enemy_1116_liprr_2":[4,4,0,932,27000],"enemy_1118_lidbox_2":[2,1,1,5973,16462]},
|
||||||
"hooks": [1,28,1811,193,193,210,210,32,18,21,16,16,0,8197,3,27,17,1,1,18,0,0,0],
|
"hooks": [1,28,1811,193,193,210,210,32,18,21,16,16,0,8197,3,27,17,1,1,18,0,0,0],
|
||||||
"events": {"atk":228,"deploy":11,"die":20,"dmg":210,"fx":50,"heal":32,"leak":1,"skill":32,"spawn":26,"status":26},
|
"events": {"atk":228,"deploy":11,"die":20,"dmg":210,"engage":6,"fx":50,"heal":32,"leak":1,"skill":32,"spawn":26,"status":26},
|
||||||
"fx": {"aoe":1,"displace":19,"liberate":3,"pull":19,"strike":3,"summon":2,"volley":3},
|
"fx": {"aoe":1,"displace":19,"liberate":3,"pull":19,"strike":3,"summon":2,"volley":3},
|
||||||
"snaps": "4f49e250 3e34819c 722978d2 a4f896f1 12fedc68 96a398c2"
|
"snaps": "4f49e250 3e34819c 722978d2 a4f896f1 12fedc68 96a398c2"
|
||||||
},
|
},
|
||||||
@@ -1709,7 +1709,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1234_dsubrl_2":[2,2,0,9847,32256],"enemy_1433_dsbasi_2":[16,16,0,700,105062]},
|
"enemies": {"enemy_1234_dsubrl_2":[2,2,0,9847,32256],"enemy_1433_dsbasi_2":[16,16,0,700,105062]},
|
||||||
"hooks": [1,32,2169,163,163,249,300,41,22,23,12,11,0,2963,17,18,18,0,1,22,0,0,12],
|
"hooks": [1,32,2169,163,163,249,300,41,22,23,12,11,0,2963,17,18,18,0,1,22,0,0,12],
|
||||||
"events": {"atk":167,"deploy":12,"die":23,"dmg":300,"fx":94,"heal":24,"skill":25,"spawn":29,"status":31},
|
"events": {"atk":167,"deploy":12,"die":23,"dmg":300,"engage":4,"fx":94,"heal":24,"skill":25,"spawn":29,"status":31},
|
||||||
"fx": {"dodge":12,"featherArrow":1,"flame":3,"garrisonGrant":1,"healField":1,"pulse":43,"riposte":10,"summon":2,"swordRain":17,"swordStorm":4},
|
"fx": {"dodge":12,"featherArrow":1,"flame":3,"garrisonGrant":1,"healField":1,"pulse":43,"riposte":10,"summon":2,"swordRain":17,"swordStorm":4},
|
||||||
"snaps": "849e61b3 b054e595 7736283f c3ff5997 f8df75cd d3a1062a ce887350 42803555"
|
"snaps": "849e61b3 b054e595 7736283f c3ff5997 f8df75cd d3a1062a ce887350 42803555"
|
||||||
},
|
},
|
||||||
@@ -1748,7 +1748,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1166_dusbr":[15,15,0,203,51840],"enemy_1169_duphlx":[5,5,0,102,23040],"enemy_1170_dushld":[2,2,0,2736,16128]},
|
"enemies": {"enemy_1166_dusbr":[15,15,0,203,51840],"enemy_1169_duphlx":[5,5,0,102,23040],"enemy_1170_dushld":[2,2,0,2736,16128]},
|
||||||
"hooks": [1,34,2252,218,218,308,308,127,24,24,10,9,0,7847,60,19,22,0,1,26,0,0,0],
|
"hooks": [1,34,2252,218,218,308,308,127,24,24,10,9,0,7847,60,19,22,0,1,26,0,0,0],
|
||||||
"events": {"atk":270,"deploy":9,"die":24,"dmg":308,"fx":9,"skill":19,"spawn":34,"status":142},
|
"events": {"atk":270,"deploy":9,"die":24,"dmg":308,"engage":7,"fx":9,"skill":19,"spawn":34,"status":142},
|
||||||
"fx": {"camouflage":1,"dp":3,"sleep":1,"volley":3,"wolfShadow":1},
|
"fx": {"camouflage":1,"dp":3,"sleep":1,"volley":3,"wolfShadow":1},
|
||||||
"snaps": "597235cf c1cf1b3d d9739806 4aa90bb7 00b32341 db0c0cef 65594803 f479bb7a"
|
"snaps": "597235cf c1cf1b3d d9739806 4aa90bb7 00b32341 db0c0cef 65594803 f479bb7a"
|
||||||
},
|
},
|
||||||
@@ -1794,7 +1794,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1019_jshoot_2":[7,7,0,1544,47029],"enemy_1381_winman":[9,9,0,0,129890],"enemy_1389_winbab_2":[5,5,0,5384,87091]},
|
"enemies": {"enemy_1019_jshoot_2":[7,7,0,1544,47029],"enemy_1381_winman":[9,9,0,0,129890],"enemy_1389_winbab_2":[5,5,0,5384,87091]},
|
||||||
"hooks": [1,40,2378,128,128,168,212,1108,22,26,11,11,6,5334,1,8,21,0,1,23,0,0,0],
|
"hooks": [1,40,2378,128,128,168,212,1108,22,26,11,11,6,5334,1,8,21,0,1,23,0,0,0],
|
||||||
"events": {"atk":128,"deploy":16,"die":26,"dmg":209,"fx":31,"heal":8,"skill":26,"spawn":40,"status":3},
|
"events": {"atk":128,"deploy":16,"die":26,"dmg":209,"engage":7,"fx":31,"heal":8,"skill":26,"spawn":40,"status":3},
|
||||||
"fx": {"coin":8,"downed":1,"explode":4,"firewall":1,"revive":1,"splash":6,"summon":5,"yanyouFlame":3,"yanyouSummon":2},
|
"fx": {"coin":8,"downed":1,"explode":4,"firewall":1,"revive":1,"splash":6,"summon":5,"yanyouFlame":3,"yanyouSummon":2},
|
||||||
"snaps": "9de1a377 006ae259 36d64b47 07539063 05e6c9d6 a572b9f9 d780cb71 6f89109e"
|
"snaps": "9de1a377 006ae259 36d64b47 07539063 05e6c9d6 a572b9f9 d780cb71 6f89109e"
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-22
@@ -78,7 +78,7 @@
|
|||||||
"enemy_9016_acstmr": [7,7,0,0,56]
|
"enemy_9016_acstmr": [7,7,0,0,56]
|
||||||
},
|
},
|
||||||
"hooks": [1,70,5249,1013,1013,2478,2830,1935,34,41,83,79,18,33653,2658,14,28,0,1,33,0,20,0],
|
"hooks": [1,70,5249,1013,1013,2478,2830,1935,34,41,83,79,18,33653,2658,14,28,0,1,33,0,20,0],
|
||||||
"events": {"atk":1288,"deploy":36,"die":41,"dmg":2551,"fx":569,"heal":569,"skill":162,"spawn":66,"status":654},
|
"events": {"atk":1288,"deploy":36,"die":41,"dmg":2551,"engage":16,"fx":569,"heal":569,"skill":162,"spawn":66,"status":654},
|
||||||
"fx": {"airstrike":5,"beam":48,"blink":1,"bombard":18,"bombardShell":18,"burst":20,"copy":3,"dp":3,"drone":252,"droneLock":22,"drones":3,"firewall":1,"firewallBlock":1,"garrisonGrant":1,"lightning":101,"link":3,"lock":18,"phase":1,"radiantSword":3,"shell":7,"snow":5,"split":2,"storm":9,"summon":7,"talent":4,"tide":4,"wanted":9},
|
"fx": {"airstrike":5,"beam":48,"blink":1,"bombard":18,"bombardShell":18,"burst":20,"copy":3,"dp":3,"drone":252,"droneLock":22,"drones":3,"firewall":1,"firewallBlock":1,"garrisonGrant":1,"lightning":101,"link":3,"lock":18,"phase":1,"radiantSword":3,"shell":7,"snow":5,"split":2,"storm":9,"summon":7,"talent":4,"tide":4,"wanted":9},
|
||||||
"snaps": "894f44af 313e3a72 a1ca1bcc 538c8df7 836414dd 44afd7b8 7ae02b33 acea2d90 d875735a b27b203d 8f33b14c 1286669e 3b6def40 8e947c0d b1a852d9 3b9ffa7e 6d96d8b1 28aa20b4"
|
"snaps": "894f44af 313e3a72 a1ca1bcc 538c8df7 836414dd 44afd7b8 7ae02b33 acea2d90 d875735a b27b203d 8f33b14c 1286669e 3b6def40 8e947c0d b1a852d9 3b9ffa7e 6d96d8b1 28aa20b4"
|
||||||
},
|
},
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
"enemy_9016_acstmr": [8,7,0,0,56]
|
"enemy_9016_acstmr": [8,7,0,0,56]
|
||||||
},
|
},
|
||||||
"hooks": [1,39,6000,574,574,827,921,186,27,27,58,56,168,18902,2365,0,28,0,1,25,0,0,0],
|
"hooks": [1,39,6000,574,574,827,921,186,27,27,58,56,168,18902,2365,0,28,0,1,25,0,0,0],
|
||||||
"events": {"atk":595,"deploy":11,"die":27,"dmg":861,"fx":285,"heal":172,"skill":114,"spawn":38,"status":277},
|
"events": {"atk":595,"deploy":11,"die":27,"dmg":861,"engage":6,"fx":285,"heal":172,"skill":114,"spawn":38,"status":277},
|
||||||
"fx": {"anchor":4,"beam":54,"camouflage":1,"candle":2,"healAoe":36,"overload":1,"phase":1,"reborn":1,"revive":1,"shell":8,"splash":168,"substitute":2,"summon":4,"swap":2},
|
"fx": {"anchor":4,"beam":54,"camouflage":1,"candle":2,"healAoe":36,"overload":1,"phase":1,"reborn":1,"revive":1,"shell":8,"splash":168,"substitute":2,"summon":4,"swap":2},
|
||||||
"snaps": "a5dad180 d125af54 48cee8b5 3b07616c bc0eda5a e532e5f3 d3b187a2 2c98a5f8 559544bf 798c9fad 22a3c133 b3cbd348 1d516e17 4fa0428c b96b4cc9 c98f2306 628c0fee dd8030f8 3266c412 97ddd90c"
|
"snaps": "a5dad180 d125af54 48cee8b5 3b07616c bc0eda5a e532e5f3 d3b187a2 2c98a5f8 559544bf 798c9fad 22a3c133 b3cbd348 1d516e17 4fa0428c b96b4cc9 c98f2306 628c0fee dd8030f8 3266c412 97ddd90c"
|
||||||
},
|
},
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1005_yokai_2":[6,6,0,4575,62190],"enemy_1430_lrrook":[2,2,0,0,802449],"enemy_9017_achunt":[2,0,0,30041,599859]},
|
"enemies": {"enemy_1005_yokai_2":[6,6,0,4575,62190],"enemy_1430_lrrook":[2,2,0,0,802449],"enemy_9017_achunt":[2,0,0,30041,599859]},
|
||||||
"hooks": [1,48,6000,1302,1334,2676,2707,5199,12,24,114,108,24,37654,903,0,10,0,1,12,0,7,0],
|
"hooks": [1,48,6000,1302,1334,2676,2707,5199,12,24,114,108,24,37654,903,0,10,0,1,12,0,7,0],
|
||||||
"events": {"atk":1357,"deploy":38,"die":24,"dmg":2701,"fx":654,"heal":54,"skill":222,"spawn":32,"status":441},
|
"events": {"atk":1357,"deploy":38,"die":24,"dmg":2701,"engage":18,"fx":654,"heal":54,"skill":222,"spawn":32,"status":441},
|
||||||
"fx": {"aoe":27,"appear":2,"blink":13,"burst":7,"catShield":1,"charge":7,"counter":24,"crit":112,"dilemma":4,"disappear":2,"explosion":14,"frostNova":13,"garrisonGrant":1,"iceSpike":91,"overclock":2,"pull":3,"quadShot":3,"ripple":25,"sentry":7,"sermon":1,"shadowWeave":13,"shield":6,"soul":1,"sp":7,"stealth":9,"swordRain":59,"swordStorm":9,"tornado":4,"tornadoPulse":17,"weightless":3,"zone":167},
|
"fx": {"aoe":27,"appear":2,"blink":13,"burst":7,"catShield":1,"charge":7,"counter":24,"crit":112,"dilemma":4,"disappear":2,"explosion":14,"frostNova":13,"garrisonGrant":1,"iceSpike":91,"overclock":2,"pull":3,"quadShot":3,"ripple":25,"sentry":7,"sermon":1,"shadowWeave":13,"shield":6,"soul":1,"sp":7,"stealth":9,"swordRain":59,"swordStorm":9,"tornado":4,"tornadoPulse":17,"weightless":3,"zone":167},
|
||||||
"snaps": "3cd698b1 c4c10bef a30f10e2 16900e85 a9a35856 dbfa73dc 81650d2d f76c2446 eac77ab1 120c61fe 7df80570 8eeabc2d 0de7de10 f0f4eb24 cca3fbad 2faf03af 37cb63f6 ec50b27e e5066532 6987cb61"
|
"snaps": "3cd698b1 c4c10bef a30f10e2 16900e85 a9a35856 dbfa73dc 81650d2d f76c2446 eac77ab1 120c61fe 7df80570 8eeabc2d 0de7de10 f0f4eb24 cca3fbad 2faf03af 37cb63f6 ec50b27e e5066532 6987cb61"
|
||||||
},
|
},
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1249_lysdb_2":[5,5,0,7713,107495],"enemy_1430_lrrook":[2,0,2,0,69826],"enemy_9017_achunt":[1,0,0,20369,130032]},
|
"enemies": {"enemy_1249_lysdb_2":[5,5,0,7713,107495],"enemy_1430_lrrook":[2,0,2,0,69826],"enemy_9017_achunt":[1,0,0,20369,130032]},
|
||||||
"hooks": [1,21,6000,384,384,992,1392,437,8,10,26,25,0,11439,66,5,8,2,1,12,0,2,2],
|
"hooks": [1,21,6000,384,384,992,1392,437,8,10,26,25,0,11439,66,5,8,2,1,12,0,2,2],
|
||||||
"events": {"atk":407,"deploy":13,"die":8,"dmg":1388,"fx":261,"heal":427,"leak":2,"skill":54,"spawn":18,"status":408},
|
"events": {"atk":407,"deploy":13,"die":8,"dmg":1388,"engage":7,"fx":261,"heal":427,"leak":2,"skill":54,"spawn":18,"status":408},
|
||||||
"fx": {"bandage":3,"block":4,"burst":2,"charge":7,"dodge":2,"featherArrow":1,"flame":5,"garrisonGrant":1,"healField":1,"jungleSoul":3,"mote":102,"redistribute":45,"reweave":3,"riposte":2,"rockslide":2,"sandChains":3,"sandChainsCharged":2,"shieldBreak":9,"slash":50,"spGift":5,"thorns":6,"truesilver":3},
|
"fx": {"bandage":3,"block":4,"burst":2,"charge":7,"dodge":2,"featherArrow":1,"flame":5,"garrisonGrant":1,"healField":1,"jungleSoul":3,"mote":102,"redistribute":45,"reweave":3,"riposte":2,"rockslide":2,"sandChains":3,"sandChainsCharged":2,"shieldBreak":9,"slash":50,"spGift":5,"thorns":6,"truesilver":3},
|
||||||
"snaps": "0f880931 8c81facc 290a0f9b 2bb4b1a9 8e556ed9 f2194a8a d52e0a62 9b379622 a978b6ed e31e004a 73d5361b 01a346ea ff508697 13a33137 5e17b2f9 f47e3e5d 9d93e6fc ebf591d1 49f516d3 62bd96ec"
|
"snaps": "0f880931 8c81facc 290a0f9b 2bb4b1a9 8e556ed9 f2194a8a d52e0a62 9b379622 a978b6ed e31e004a 73d5361b 01a346ea ff508697 13a33137 5e17b2f9 f47e3e5d 9d93e6fc ebf591d1 49f516d3 62bd96ec"
|
||||||
},
|
},
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
"enemy_9023_acdums": [6,5,0,10453,56]
|
"enemy_9023_acdums": [6,5,0,10453,56]
|
||||||
},
|
},
|
||||||
"hooks": [1,52,3217,835,835,1876,2153,587,18,26,39,31,178,24531,946,3,19,0,1,17,0,2,0],
|
"hooks": [1,52,3217,835,835,1876,2153,587,18,26,39,31,178,24531,946,3,19,0,1,17,0,2,0],
|
||||||
"events": {"atk":950,"deploy":29,"die":26,"dmg":1987,"fx":591,"heal":214,"skill":70,"spawn":50,"status":337},
|
"events": {"atk":950,"deploy":29,"die":26,"dmg":1987,"engage":14,"fx":591,"heal":214,"skill":70,"spawn":50,"status":337},
|
||||||
"fx": {"airstrike":82,"beam":6,"blink":2,"bombard":29,"bombardShell":29,"burst":2,"copy":1,"displace":1,"dp":3,"drone":173,"droneLock":16,"drones":2,"explode":96,"finale":1,"garrisonGrant":1,"lightning":44,"link":2,"lock":29,"lure":2,"phase":12,"radiantSword":2,"slash":11,"snow":11,"split":2,"steal":4,"storm":4,"summon":10,"sunBurst":1,"talent":4,"telegraph":3,"tide":3,"wanted":3},
|
"fx": {"airstrike":82,"beam":6,"blink":2,"bombard":29,"bombardShell":29,"burst":2,"copy":1,"displace":1,"dp":3,"drone":173,"droneLock":16,"drones":2,"explode":96,"finale":1,"garrisonGrant":1,"lightning":44,"link":2,"lock":29,"lure":2,"phase":12,"radiantSword":2,"slash":11,"snow":11,"split":2,"steal":4,"storm":4,"summon":10,"sunBurst":1,"talent":4,"telegraph":3,"tide":3,"wanted":3},
|
||||||
"snaps": "0e2e327e 9068cc6a ced48e18 fdd1b2ba 7bbf69fc 61492769 1b5e7d15 aa2c6113 9484b207 1ca0f38b efde51f4"
|
"snaps": "0e2e327e 9068cc6a ced48e18 fdd1b2ba 7bbf69fc 61492769 1b5e7d15 aa2c6113 9484b207 1ca0f38b efde51f4"
|
||||||
},
|
},
|
||||||
@@ -330,7 +330,7 @@
|
|||||||
"enemy_9023_acdums": [9,9,0,4276,90]
|
"enemy_9023_acdums": [9,9,0,4276,90]
|
||||||
},
|
},
|
||||||
"hooks": [1,42,6000,359,359,677,1340,255,21,24,62,61,21,14628,1263,3,20,0,1,102,0,1,0],
|
"hooks": [1,42,6000,359,359,677,1340,255,21,24,62,61,21,14628,1263,3,20,0,1,102,0,1,0],
|
||||||
"events": {"atk":383,"deploy":16,"die":24,"dmg":627,"fx":243,"heal":56,"skill":123,"spawn":37,"status":192},
|
"events": {"atk":383,"deploy":16,"die":24,"dmg":627,"engage":8,"fx":243,"heal":56,"skill":123,"spawn":37,"status":192},
|
||||||
"fx": {"anchor":1,"aoe":2,"beam":9,"burst":1,"candle":1,"ember":2,"explode":151,"healAoe":18,"overload":4,"phase":17,"splash":21,"substitute":2,"summon":9,"swap":2,"telegraph":1,"teleport":2},
|
"fx": {"anchor":1,"aoe":2,"beam":9,"burst":1,"candle":1,"ember":2,"explode":151,"healAoe":18,"overload":4,"phase":17,"splash":21,"substitute":2,"summon":9,"swap":2,"telegraph":1,"teleport":2},
|
||||||
"snaps": "350d2037 5eb324a9 93890c27 cfd3cc69 9d47cb8d 97a81071 03889d40 e843828d 92884942 e6498744 15391702 f00b712f fcb3da1c 0b44c227 2802904f c8eaf7b3 a142e278 b3656951 bd850e24 0b8940d4"
|
"snaps": "350d2037 5eb324a9 93890c27 cfd3cc69 9d47cb8d 97a81071 03889d40 e843828d 92884942 e6498744 15391702 f00b712f fcb3da1c 0b44c227 2802904f c8eaf7b3 a142e278 b3656951 bd850e24 0b8940d4"
|
||||||
},
|
},
|
||||||
@@ -382,7 +382,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon":[4,4,0,2035,200612],"enemy_1438_dspred":[10,8,2,18391,859975],"enemy_1521_dslily":[1,0,0,89880,1019291]},
|
"enemies": {"enemy_1010_demon":[4,4,0,2035,200612],"enemy_1438_dspred":[10,8,2,18391,859975],"enemy_1521_dslily":[1,0,0,89880,1019291]},
|
||||||
"hooks": [1,64,6000,1264,1327,3121,3243,5401,46,50,131,130,9,32811,1211,12,15,2,1,49,0,28,6],
|
"hooks": [1,64,6000,1264,1327,3121,3243,5401,46,50,131,130,9,32811,1211,12,15,2,1,49,0,28,6],
|
||||||
"events": {"atk":1527,"deploy":49,"die":48,"dmg":3236,"fx":809,"heal":191,"leak":2,"skill":261,"spawn":37,"status":830},
|
"events": {"atk":1527,"deploy":49,"die":48,"dmg":3236,"engage":18,"fx":809,"heal":191,"leak":2,"skill":261,"spawn":37,"status":830},
|
||||||
"fx": {
|
"fx": {
|
||||||
"aoe": 30,
|
"aoe": 30,
|
||||||
"appear": 8,
|
"appear": 8,
|
||||||
@@ -457,7 +457,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1010_demon_2":[2,2,0,7219,107495],"enemy_1438_dspred":[5,2,3,28087,137265],"enemy_1521_dslily":[1,0,0,68045,411797]},
|
"enemies": {"enemy_1010_demon_2":[2,2,0,7219,107495],"enemy_1438_dspred":[5,2,3,28087,137265],"enemy_1521_dslily":[1,0,0,68045,411797]},
|
||||||
"hooks": [1,23,6000,567,567,959,1067,335,8,11,25,24,0,13355,129,10,8,3,1,8,0,30,0],
|
"hooks": [1,23,6000,567,567,959,1067,335,8,11,25,24,0,13355,129,10,8,3,1,8,0,30,0],
|
||||||
"events": {"atk":762,"deploy":13,"die":8,"dmg":1049,"fx":335,"heal":329,"leak":3,"skill":51,"spawn":20,"status":513},
|
"events": {"atk":762,"deploy":13,"die":8,"dmg":1049,"engage":6,"fx":335,"heal":329,"leak":3,"skill":51,"spawn":20,"status":513},
|
||||||
"fx": {"bandage":1,"block":18,"burst":30,"camouflage":1,"featherArrow":5,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":68,"redistribute":45,"reweave":3,"rockfall":18,"rockslide":4,"sandChains":6,"shieldBreak":18,"slash":46,"spGift":3,"tentacle":6,"thorns":50,"tide":5,"truesilver":3},
|
"fx": {"bandage":1,"block":18,"burst":30,"camouflage":1,"featherArrow":5,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":68,"redistribute":45,"reweave":3,"rockfall":18,"rockslide":4,"sandChains":6,"shieldBreak":18,"slash":46,"spGift":3,"tentacle":6,"thorns":50,"tide":5,"truesilver":3},
|
||||||
"snaps": "bdb41e6b d33270c9 b699515f 4c778ec3 7b987465 1dc86ced aa1b5647 d974b80c 1d71a5f3 0398ba1c cb1b4c96 23b4948c 4e179090 70afc8aa 855ad8e7 80f3d475 405e2c17 3b9e93ac 2b80137e cf30df9d"
|
"snaps": "bdb41e6b d33270c9 b699515f 4c778ec3 7b987465 1dc86ced aa1b5647 d974b80c 1d71a5f3 0398ba1c cb1b4c96 23b4948c 4e179090 70afc8aa 855ad8e7 80f3d475 405e2c17 3b9e93ac 2b80137e cf30df9d"
|
||||||
},
|
},
|
||||||
@@ -520,7 +520,7 @@
|
|||||||
"enemy_2017_csphts": [6,6,0,67,60000]
|
"enemy_2017_csphts": [6,6,0,67,60000]
|
||||||
},
|
},
|
||||||
"hooks": [1,48,2041,383,383,1035,1245,794,12,23,45,33,24,14908,784,19,14,0,1,10,0,10,44],
|
"hooks": [1,48,2041,383,383,1035,1245,794,12,23,45,33,24,14908,784,19,14,0,1,10,0,10,44],
|
||||||
"events": {"atk":517,"deploy":34,"die":23,"dmg":1136,"fx":333,"heal":286,"skill":78,"spawn":41,"status":368},
|
"events": {"atk":517,"deploy":34,"die":23,"dmg":1136,"engage":20,"fx":333,"heal":286,"skill":78,"spawn":41,"status":368},
|
||||||
"fx": {
|
"fx": {
|
||||||
"airstrike": 9,
|
"airstrike": 9,
|
||||||
"blink": 13,
|
"blink": 13,
|
||||||
@@ -601,7 +601,7 @@
|
|||||||
"enemy_5601_entlec": [2,2,0,0,2905]
|
"enemy_5601_entlec": [2,2,0,0,2905]
|
||||||
},
|
},
|
||||||
"hooks": [1,25,4545,286,286,539,734,344,13,15,49,47,84,13386,970,10,11,0,1,16,0,3,41],
|
"hooks": [1,25,4545,286,286,539,734,344,13,15,49,47,84,13386,970,10,11,0,1,16,0,3,41],
|
||||||
"events": {"atk":290,"deploy":14,"die":15,"dmg":614,"fx":184,"heal":316,"skill":96,"spawn":23,"status":146},
|
"events": {"atk":290,"deploy":14,"die":15,"dmg":614,"engage":7,"fx":184,"heal":316,"skill":96,"spawn":23,"status":146},
|
||||||
"fx": {"anchor":1,"blink":3,"bloodBattle":1,"burst":3,"candle":2,"dodge":41,"ember":1,"explode":18,"healAoe":19,"overload":2,"shieldBreak":2,"splash":84,"summon":4,"telegraph":1,"teleport":2},
|
"fx": {"anchor":1,"blink":3,"bloodBattle":1,"burst":3,"candle":2,"dodge":41,"ember":1,"explode":18,"healAoe":19,"overload":2,"shieldBreak":2,"splash":84,"summon":4,"telegraph":1,"teleport":2},
|
||||||
"snaps": "1b2c287a eaa9179b 630f3c60 4d66a11e 1bb71808 3f98dccb 22cb18a1 217a35ef 09de2906 ba0f42cc 81fbd1d0 398611ec 0abd9c6f 252f015d fe9c3866 f9bb937f"
|
"snaps": "1b2c287a eaa9179b 630f3c60 4d66a11e 1bb71808 3f98dccb 22cb18a1 217a35ef 09de2906 ba0f42cc 81fbd1d0 398611ec 0abd9c6f 252f015d fe9c3866 f9bb937f"
|
||||||
},
|
},
|
||||||
@@ -659,7 +659,7 @@
|
|||||||
"enemy_9032_aclionk": [1,0,0,34912,1157397]
|
"enemy_9032_aclionk": [1,0,0,34912,1157397]
|
||||||
},
|
},
|
||||||
"hooks": [1,50,6000,1272,1302,2358,2376,1878,26,30,122,120,0,38644,1437,4,15,2,1,21,0,5,1],
|
"hooks": [1,50,6000,1272,1302,2358,2376,1878,26,30,122,120,0,38644,1437,4,15,2,1,21,0,5,1],
|
||||||
"events": {"atk":1371,"deploy":35,"die":28,"dmg":2328,"fx":600,"heal":36,"leak":2,"skill":242,"spawn":37,"status":955},
|
"events": {"atk":1371,"deploy":35,"die":28,"dmg":2328,"engage":14,"fx":600,"heal":36,"leak":2,"skill":242,"spawn":37,"status":955},
|
||||||
"fx": {
|
"fx": {
|
||||||
"aoe": 43,
|
"aoe": 43,
|
||||||
"appear": 4,
|
"appear": 4,
|
||||||
@@ -744,7 +744,7 @@
|
|||||||
"enemy_9032_aclionk": [1,0,0,40419,102806]
|
"enemy_9032_aclionk": [1,0,0,40419,102806]
|
||||||
},
|
},
|
||||||
"hooks": [1,47,6000,190,190,281,479,239,32,34,21,21,0,7024,68,2,20,2,1,23,0,0,2],
|
"hooks": [1,47,6000,190,190,281,479,239,32,34,21,21,0,7024,68,2,20,2,1,23,0,0,2],
|
||||||
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"fx":90,"heal":34,"leak":2,"skill":44,"spawn":36,"status":187},
|
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"engage":7,"fx":90,"heal":34,"leak":2,"skill":44,"spawn":36,"status":187},
|
||||||
"fx": {"dodge":2,"equip":12,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":9,"riposte":2,"rockslide":1,"sandChains":4,"slash":24,"summon":2,"sword":1,"telegraph":11,"truesilver":4,"vest":5},
|
"fx": {"dodge":2,"equip":12,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":9,"riposte":2,"rockslide":1,"sandChains":4,"slash":24,"summon":2,"sword":1,"telegraph":11,"truesilver":4,"vest":5},
|
||||||
"snaps": "8056ac8d bae7d7dc 775827b4 0d832bb0 b68eb0ff 07afed85 063dbd3e 1ffe3d7a b60db00b cc64833f 2776a8b9 a8e2de77 5a30391e 46573a52 04e74ed7 41e6b272 5ea737fb bb532ee9 6cc70b97 f363c643"
|
"snaps": "8056ac8d bae7d7dc 775827b4 0d832bb0 b68eb0ff 07afed85 063dbd3e 1ffe3d7a b60db00b cc64833f 2776a8b9 a8e2de77 5a30391e 46573a52 04e74ed7 41e6b272 5ea737fb bb532ee9 6cc70b97 f363c643"
|
||||||
},
|
},
|
||||||
@@ -809,7 +809,7 @@
|
|||||||
"enemy_9033_acdeer": [1,1,0,35494,2000000]
|
"enemy_9033_acdeer": [1,1,0,35494,2000000]
|
||||||
},
|
},
|
||||||
"hooks": [1,49,4851,1130,1130,2879,3181,1003,21,27,76,72,200,28482,993,4,17,0,1,20,0,14,0],
|
"hooks": [1,49,4851,1130,1130,2879,3181,1003,21,27,76,72,200,28482,993,4,17,0,1,20,0,14,0],
|
||||||
"events": {"atk":1335,"deploy":32,"die":27,"dmg":2899,"fx":431,"heal":473,"skill":148,"spawn":47,"status":688},
|
"events": {"atk":1335,"deploy":32,"die":27,"dmg":2899,"engage":16,"fx":431,"heal":473,"skill":148,"spawn":47,"status":688},
|
||||||
"fx": {"airstrike":68,"beam":5,"blink":2,"burst":14,"cleanse":1,"column":40,"copy":4,"dp":4,"drone":234,"droneLock":10,"drones":2,"emergency":1,"explode":6,"garrisonGrant":1,"link":1,"phase":1,"scorchBurst":6,"shieldBreak":4,"snow":5,"split":3,"steal":4,"sunBurst":4,"talent":4,"tide":4,"wanted":3},
|
"fx": {"airstrike":68,"beam":5,"blink":2,"burst":14,"cleanse":1,"column":40,"copy":4,"dp":4,"drone":234,"droneLock":10,"drones":2,"emergency":1,"explode":6,"garrisonGrant":1,"link":1,"phase":1,"scorchBurst":6,"shieldBreak":4,"snow":5,"split":3,"steal":4,"sunBurst":4,"talent":4,"tide":4,"wanted":3},
|
||||||
"snaps": "d456f8d7 76ad512f fc04bcfe e6e90350 4d81715e b3d0e9d2 9f9304f4 9ea01822 e8064996 57183074 78e21334 5efb2070 1a9f6c3d b3ca158c e6ef5780 ea45e2c1 32576c19"
|
"snaps": "d456f8d7 76ad512f fc04bcfe e6e90350 4d81715e b3d0e9d2 9f9304f4 9ea01822 e8064996 57183074 78e21334 5efb2070 1a9f6c3d b3ca158c e6ef5780 ea45e2c1 32576c19"
|
||||||
},
|
},
|
||||||
@@ -849,7 +849,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_1061_zomshd":[2,2,0,703,35884],"enemy_1067_snslime":[5,5,0,2179,26957],"enemy_1081_sotisd":[4,4,0,1845,99533],"enemy_9033_acdeer":[1,0,0,63952,345536]},
|
"enemies": {"enemy_1061_zomshd":[2,2,0,703,35884],"enemy_1067_snslime":[5,5,0,2179,26957],"enemy_1081_sotisd":[4,4,0,1845,99533],"enemy_9033_acdeer":[1,0,0,63952,345536]},
|
||||||
"hooks": [1,32,6000,407,407,581,828,564,12,15,100,99,21,19541,1165,3,12,0,1,12,0,0,0],
|
"hooks": [1,32,6000,407,407,581,828,564,12,15,100,99,21,19541,1165,3,12,0,1,12,0,0,0],
|
||||||
"events": {"atk":407,"deploy":20,"die":15,"dmg":588,"fx":138,"heal":158,"skill":199,"spawn":25,"status":194},
|
"events": {"atk":407,"deploy":20,"die":15,"dmg":588,"engage":5,"fx":138,"heal":158,"skill":199,"spawn":25,"status":194},
|
||||||
"fx": {"anchor":3,"beam":5,"camouflage":1,"column":52,"explode":5,"healAoe":40,"overload":4,"phase":1,"splash":21,"teleport":6},
|
"fx": {"anchor":3,"beam":5,"camouflage":1,"column":52,"explode":5,"healAoe":40,"overload":4,"phase":1,"splash":21,"teleport":6},
|
||||||
"snaps": "4de66f1a 63c08f30 825591c9 d0ce31a1 7aef05fb 1ec56c35 f5134898 a3987ee3 f826ddd0 8536abb7 e61cb2af eb4dc819 ccfdf14f 98da64f1 27310d84 6f8f4bac 8f4f0950 bd66a2ba e033ff9d 33a4d212"
|
"snaps": "4de66f1a 63c08f30 825591c9 d0ce31a1 7aef05fb 1ec56c35 f5134898 a3987ee3 f826ddd0 8536abb7 e61cb2af eb4dc819 ccfdf14f 98da64f1 27310d84 6f8f4bac 8f4f0950 bd66a2ba e033ff9d 33a4d212"
|
||||||
},
|
},
|
||||||
@@ -909,7 +909,7 @@
|
|||||||
"enemy_9016_acstmr": [8,7,0,0,42]
|
"enemy_9016_acstmr": [8,7,0,0,42]
|
||||||
},
|
},
|
||||||
"hooks": [1,87,6000,808,809,2627,2815,4682,68,71,81,79,9,21642,625,21,35,2,1,69,0,8,3],
|
"hooks": [1,87,6000,808,809,2627,2815,4682,68,71,81,79,9,21642,625,21,35,2,1,69,0,8,3],
|
||||||
"events": {"atk":940,"deploy":52,"die":69,"dmg":2811,"fx":550,"heal":130,"leak":2,"skill":160,"spawn":57,"status":569},
|
"events": {"atk":940,"deploy":52,"die":69,"dmg":2811,"engage":18,"fx":550,"heal":130,"leak":2,"skill":160,"spawn":57,"status":569},
|
||||||
"fx": {
|
"fx": {
|
||||||
"aoe": 31,
|
"aoe": 31,
|
||||||
"appear": 7,
|
"appear": 7,
|
||||||
@@ -999,7 +999,7 @@
|
|||||||
"enemy_9016_acstmr": [7,7,0,0,31]
|
"enemy_9016_acstmr": [7,7,0,0,31]
|
||||||
},
|
},
|
||||||
"hooks": [1,53,6000,301,301,538,573,110,37,45,17,17,0,5624,46,4,27,8,1,33,0,0,0],
|
"hooks": [1,53,6000,301,301,538,573,110,37,45,17,17,0,5624,46,4,27,8,1,33,0,0,0],
|
||||||
"events": {"atk":354,"deploy":22,"die":37,"dmg":567,"fx":174,"heal":104,"leak":8,"skill":35,"spawn":41,"status":138},
|
"events": {"atk":354,"deploy":22,"die":37,"dmg":567,"engage":5,"fx":174,"heal":104,"leak":8,"skill":35,"spawn":41,"status":138},
|
||||||
"fx": {"bandage":1,"beam":32,"blink":10,"block":3,"charge":6,"explode":7,"featherArrow":1,"garrisonGrant":1,"healField":4,"mote":5,"phase":1,"redistribute":7,"reweave":1,"rockslide":6,"sandChains":1,"shell":7,"shieldBreak":6,"slash":56,"spGift":3,"summon":5,"telegraph":3,"thorns":5,"truesilver":3},
|
"fx": {"bandage":1,"beam":32,"blink":10,"block":3,"charge":6,"explode":7,"featherArrow":1,"garrisonGrant":1,"healField":4,"mote":5,"phase":1,"redistribute":7,"reweave":1,"rockslide":6,"sandChains":1,"shell":7,"shieldBreak":6,"slash":56,"spGift":3,"summon":5,"telegraph":3,"thorns":5,"truesilver":3},
|
||||||
"snaps": "32c4a923 512591e1 e9c907a6 e3107633 ba7c0969 3a59d469 b8cecc85 46968da9 2129d863 19b4ada6 9afd8bfd ede8ed1f 6d35ee92 820ce8ef 4981d928 d404a14b 089b661f 39f4bf14 8620255e 7016887c"
|
"snaps": "32c4a923 512591e1 e9c907a6 e3107633 ba7c0969 3a59d469 b8cecc85 46968da9 2129d863 19b4ada6 9afd8bfd ede8ed1f 6d35ee92 820ce8ef 4981d928 d404a14b 089b661f 39f4bf14 8620255e 7016887c"
|
||||||
},
|
},
|
||||||
@@ -1072,7 +1072,7 @@
|
|||||||
"enemy_9020_actrpc": [1,0,0,27400,1046182]
|
"enemy_9020_actrpc": [1,0,0,27400,1046182]
|
||||||
},
|
},
|
||||||
"hooks": [1,67,6000,1512,1512,6012,11389,1913,32,41,99,94,327,40903,1953,18,13,0,1,42,0,22,0],
|
"hooks": [1,67,6000,1512,1512,6012,11389,1913,32,41,99,94,327,40903,1953,18,13,0,1,42,0,22,0],
|
||||||
"events": {"atk":1799,"deploy":48,"die":41,"dmg":10991,"fx":1980,"heal":654,"skill":193,"spawn":49,"status":765},
|
"events": {"atk":1799,"deploy":48,"die":41,"dmg":10991,"engage":24,"fx":1980,"heal":654,"skill":193,"spawn":49,"status":765},
|
||||||
"fx": {
|
"fx": {
|
||||||
"airstrike": 113,
|
"airstrike": 113,
|
||||||
"beam": 18,
|
"beam": 18,
|
||||||
@@ -1160,7 +1160,7 @@
|
|||||||
"enemy_9020_actrpc": [1,0,0,14284,517482]
|
"enemy_9020_actrpc": [1,0,0,14284,517482]
|
||||||
},
|
},
|
||||||
"hooks": [1,40,4862,758,758,3025,6628,849,17,20,87,85,103,19739,851,11,15,0,1,52,0,2,0],
|
"hooks": [1,40,4862,758,758,3025,6628,849,17,20,87,85,103,19739,851,11,15,0,1,52,0,2,0],
|
||||||
"events": {"atk":1148,"deploy":25,"die":20,"dmg":6285,"fx":704,"heal":533,"skill":172,"spawn":28,"status":271},
|
"events": {"atk":1148,"deploy":25,"die":20,"dmg":6285,"engage":10,"fx":704,"heal":533,"skill":172,"spawn":28,"status":271},
|
||||||
"fx": {"anchor":4,"aoe":1,"beam":19,"burst":2,"candle":8,"charge":6,"dash":12,"downed":1,"ember":1,"explode":1,"healAoe":28,"link":483,"overload":3,"palsy":3,"phase":10,"reborn":1,"shieldBreak":9,"splash":103,"substitute":2,"summon":1,"swap":2,"teleport":4},
|
"fx": {"anchor":4,"aoe":1,"beam":19,"burst":2,"candle":8,"charge":6,"dash":12,"downed":1,"ember":1,"explode":1,"healAoe":28,"link":483,"overload":3,"palsy":3,"phase":10,"reborn":1,"shieldBreak":9,"splash":103,"substitute":2,"summon":1,"swap":2,"teleport":4},
|
||||||
"snaps": "d71f569c a47a1d66 df4b4783 0854c875 846091e0 9890e33e 5fdb72ff fb639060 fcd20f12 04a361dc d58cf1d0 1482a16a 606d5129 d394ce04 39d5d7f5 6adac8ac 09d55362"
|
"snaps": "d71f569c a47a1d66 df4b4783 0854c875 846091e0 9890e33e 5fdb72ff fb639060 fcd20f12 04a361dc d58cf1d0 1482a16a 606d5129 d394ce04 39d5d7f5 6adac8ac 09d55362"
|
||||||
},
|
},
|
||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"enemy_9023_acdums": [12,12,0,17083,120]
|
"enemy_9023_acdums": [12,12,0,17083,120]
|
||||||
},
|
},
|
||||||
"hooks": [1,51,6000,1128,1262,2491,2638,5656,23,25,69,65,0,29939,1498,5,18,0,1,25,0,5,0],
|
"hooks": [1,51,6000,1128,1262,2491,2638,5656,23,25,69,65,0,29939,1498,5,18,0,1,25,0,5,0],
|
||||||
"events": {"atk":1227,"deploy":31,"die":25,"dmg":2627,"fx":988,"heal":28,"skill":134,"spawn":42,"status":587},
|
"events": {"atk":1227,"deploy":31,"die":25,"dmg":2627,"engage":14,"fx":988,"heal":28,"skill":134,"spawn":42,"status":587},
|
||||||
"fx": {
|
"fx": {
|
||||||
"aoe": 6,
|
"aoe": 6,
|
||||||
"beam": 12,
|
"beam": 12,
|
||||||
@@ -1300,7 +1300,7 @@
|
|||||||
"enemy_9023_acdums": [10,10,0,38449,100]
|
"enemy_9023_acdums": [10,10,0,38449,100]
|
||||||
},
|
},
|
||||||
"hooks": [1,26,6000,606,606,1242,1839,452,13,14,27,24,0,13867,59,10,15,1,1,17,0,11,0],
|
"hooks": [1,26,6000,606,606,1242,1839,452,13,14,27,24,0,13867,59,10,15,1,1,17,0,11,0],
|
||||||
"events": {"atk":723,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":441,"leak":1,"skill":53,"spawn":25,"status":257},
|
"events": {"atk":723,"deploy":11,"die":13,"dmg":1826,"engage":6,"fx":463,"heal":441,"leak":1,"skill":53,"spawn":25,"status":257},
|
||||||
"fx": {"bandage":4,"beam":15,"burst":11,"ember":2,"explode":189,"featherArrow":7,"garrisonGrant":1,"healField":2,"jungleSoul":5,"mote":61,"phase":30,"redistribute":43,"reweave":3,"rockslide":2,"sandChainsCharged":1,"shieldBreak":15,"slash":52,"spGift":7,"summon":8,"telegraph":2,"truesilver":3},
|
"fx": {"bandage":4,"beam":15,"burst":11,"ember":2,"explode":189,"featherArrow":7,"garrisonGrant":1,"healField":2,"jungleSoul":5,"mote":61,"phase":30,"redistribute":43,"reweave":3,"rockslide":2,"sandChainsCharged":1,"shieldBreak":15,"slash":52,"spGift":7,"summon":8,"telegraph":2,"truesilver":3},
|
||||||
"snaps": "39af4287 c1c868de fadf8f46 c55be858 66f9b2a4 3ff37c1b 45adfa96 4967058e cea41925 489c48a7 bccea42b 898a4b3c 6df25753 873fc61f 1619fcb8 7afad5a1 763db01d 46db0a43 f918b4af 75a431db"
|
"snaps": "39af4287 c1c868de fadf8f46 c55be858 66f9b2a4 3ff37c1b 45adfa96 4967058e cea41925 489c48a7 bccea42b 898a4b3c 6df25753 873fc61f 1619fcb8 7afad5a1 763db01d 46db0a43 f918b4af 75a431db"
|
||||||
},
|
},
|
||||||
@@ -1339,7 +1339,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_10028_vtswd":[1,0,0,0,0],"enemy_1158_divman":[4,4,0,0,15206],"enemy_1160_hvyslr":[3,3,0,0,34560],"enemy_1161_tidmag":[3,3,0,0,20736]},
|
"enemies": {"enemy_10028_vtswd":[1,0,0,0,0],"enemy_1158_divman":[4,4,0,0,15206],"enemy_1160_hvyslr":[3,3,0,0,34560],"enemy_1161_tidmag":[3,3,0,0,20736]},
|
||||||
"hooks": [1,24,5100,13,13,48,136,293,10,14,8,8,7,11668,21,0,11,0,1,10,0,1,0],
|
"hooks": [1,24,5100,13,13,48,136,293,10,14,8,8,7,11668,21,0,11,0,1,10,0,1,0],
|
||||||
"events": {"atk":15,"bounty":1,"deploy":13,"die":14,"dmg":60,"fx":58,"heal":124,"skill":16,"spawn":23,"status":65},
|
"events": {"atk":15,"bounty":1,"deploy":13,"die":14,"dmg":60,"engage":3,"fx":58,"heal":124,"skill":16,"spawn":23,"status":65},
|
||||||
"fx": {"bombard":7,"bombardShell":7,"burst":1,"lightning":15,"lock":7,"lure":1,"scorchBurst":4,"snow":10,"storm":1,"talent":1,"tide":4},
|
"fx": {"bombard":7,"bombardShell":7,"burst":1,"lightning":15,"lock":7,"lure":1,"scorchBurst":4,"snow":10,"storm":1,"talent":1,"tide":4},
|
||||||
"snaps": "d0a32c2d 14dc64f8 d9ffb8be 0e32f941 f5f88a89 f3e84df2 97783db6 49fb05e7 d960234b ef9c8339 304bd9e6 d7e7110a 8a0636b9 840cbf99 e365c522 1d462726 178ff4dc"
|
"snaps": "d0a32c2d 14dc64f8 d9ffb8be 0e32f941 f5f88a89 f3e84df2 97783db6 49fb05e7 d960234b ef9c8339 304bd9e6 d7e7110a 8a0636b9 840cbf99 e365c522 1d462726 178ff4dc"
|
||||||
},
|
},
|
||||||
@@ -1391,7 +1391,7 @@
|
|||||||
],
|
],
|
||||||
"enemies": {"enemy_10029_vtshld":[1,0,0,0,0],"enemy_1249_lysdb_2":[3,3,0,859,53748],"enemy_1251_lysyta":[4,4,0,0,32846],"enemy_1329_cbshld":[3,3,0,8987,89580]},
|
"enemies": {"enemy_10029_vtshld":[1,0,0,0,0],"enemy_1249_lysdb_2":[3,3,0,859,53748],"enemy_1251_lysyta":[4,4,0,0,32846],"enemy_1329_cbshld":[3,3,0,8987,89580]},
|
||||||
"hooks": [1,38,6900,174,174,313,539,506,11,16,9,7,0,19291,439,5,11,0,1,11,0,0,0],
|
"hooks": [1,38,6900,174,174,313,539,506,11,16,9,7,0,19291,439,5,11,0,1,11,0,0,0],
|
||||||
"events": {"atk":274,"bounty":1,"deploy":23,"die":16,"dmg":310,"fx":164,"heal":199,"skill":16,"spawn":33,"status":89},
|
"events": {"atk":274,"bounty":1,"deploy":23,"die":16,"dmg":310,"engage":5,"fx":164,"heal":199,"skill":16,"spawn":33,"status":89},
|
||||||
"fx": {"blink":2,"copy":1,"drone":139,"droneLock":9,"drones":2,"garrisonGrant":1,"link":1,"radiantSword":1,"shieldBreak":3,"summon":1,"sunBurst":1,"talent":3},
|
"fx": {"blink":2,"copy":1,"drone":139,"droneLock":9,"drones":2,"garrisonGrant":1,"link":1,"radiantSword":1,"shieldBreak":3,"summon":1,"sunBurst":1,"talent":3},
|
||||||
"snaps": "c4705ca6 86063ce9 ea0c2b19 3be206f3 f6616193 bc8068bc b7787872 ccb11abe 5ee8f5fb d45e1507 a906e140 0eab9169 826d1e3c 03748c0a 656a7fc4 2b22fcba ee86d812 c238e284 cabebf47 115310da 7d546bc6 e010aac4 c2093c82"
|
"snaps": "c4705ca6 86063ce9 ea0c2b19 3be206f3 f6616193 bc8068bc b7787872 ccb11abe 5ee8f5fb d45e1507 a906e140 0eab9169 826d1e3c 03748c0a 656a7fc4 2b22fcba ee86d812 c238e284 cabebf47 115310da 7d546bc6 e010aac4 c2093c82"
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-49
@@ -62,7 +62,7 @@
|
|||||||
"enemy_9010_acpupp": [1,0,0,0,959]
|
"enemy_9010_acpupp": [1,0,0,0,959]
|
||||||
},
|
},
|
||||||
"hooks": [1,50,2700,404,404,632,636,325,34,34,25,21,19,14421,65,36,31,0,1,35,0,0,0],
|
"hooks": [1,50,2700,404,404,632,636,325,34,34,25,21,19,14421,65,36,31,0,1,35,0,0,0],
|
||||||
"events": {"atk":426,"bounty":4,"deploy":16,"die":34,"dmg":636,"fx":65,"heal":319,"skill":49,"spawn":47,"status":126},
|
"events": {"atk":426,"bounty":4,"deploy":16,"die":34,"dmg":636,"engage":12,"fx":65,"heal":319,"skill":49,"spawn":47,"status":126},
|
||||||
"fx": {"aoe":1,"bombard":9,"bombardShell":10,"counter":6,"crit":18,"dp":1,"ember":1,"explode":3,"heal":11,"liberate":1,"summon":1,"taunt":1,"telegraph":1,"zone":1},
|
"fx": {"aoe":1,"bombard":9,"bombardShell":10,"counter":6,"crit":18,"dp":1,"ember":1,"explode":3,"heal":11,"liberate":1,"summon":1,"taunt":1,"telegraph":1,"zone":1},
|
||||||
"snaps": "15544384 8f8ab114 83735bc4 1672a1a1 e9e7ffdb 9b9d0193 15df767d 792390ec 43783e3b"
|
"snaps": "15544384 8f8ab114 83735bc4 1672a1a1 e9e7ffdb 9b9d0193 15df767d 792390ec 43783e3b"
|
||||||
},
|
},
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"enemy_9011_acrefr": [1,0,0,0,2338]
|
"enemy_9011_acrefr": [1,0,0,0,2338]
|
||||||
},
|
},
|
||||||
"hooks": [1,62,3300,343,343,389,401,3347,45,56,13,16,31,7389,39,11,44,11,1,46,0,0,0],
|
"hooks": [1,62,3300,343,343,389,401,3347,45,56,13,16,31,7389,39,11,44,11,1,46,0,0,0],
|
||||||
"events": {"atk":361,"bounty":4,"deploy":18,"die":45,"dmg":401,"fx":46,"heal":15,"leak":11,"skill":32,"spawn":56,"status":69},
|
"events": {"atk":361,"bounty":4,"deploy":18,"die":45,"dmg":401,"engage":10,"fx":46,"heal":15,"leak":11,"skill":32,"spawn":56,"status":69},
|
||||||
"fx": {"aoe":4,"crit":20,"displace":8,"dp":2,"ember":1,"pulse":7,"revive":1,"summon":1,"taunt":2},
|
"fx": {"aoe":4,"crit":20,"displace":8,"dp":2,"ember":1,"pulse":7,"revive":1,"summon":1,"taunt":2},
|
||||||
"snaps": "7504f6da cf86e400 f495d65e e1e866ef 88f9cabd d57a8164 b1e49710 28ad6226 f40f3c34 62a776b0 2bec217d"
|
"snaps": "7504f6da cf86e400 f495d65e e1e866ef 88f9cabd d57a8164 b1e49710 28ad6226 f40f3c34 62a776b0 2bec217d"
|
||||||
},
|
},
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
"enemy_9016_acstmr": [1,1,0,0,8]
|
"enemy_9016_acstmr": [1,1,0,0,8]
|
||||||
},
|
},
|
||||||
"hooks": [1,104,3300,602,602,708,826,235,87,91,24,22,0,15820,206,28,85,0,1,94,1,0,0],
|
"hooks": [1,104,3300,602,602,708,826,235,87,91,24,22,0,15820,206,28,85,0,1,94,1,0,0],
|
||||||
"events": {"atk":685,"bounty":7,"deploy":16,"die":91,"dmg":805,"fx":108,"heal":207,"layer":1,"skill":46,"spawn":101,"status":219},
|
"events": {"atk":685,"bounty":7,"deploy":16,"die":91,"dmg":805,"engage":10,"fx":108,"heal":207,"layer":1,"skill":46,"spawn":101,"status":219},
|
||||||
"fx": {"aoe":1,"blink":1,"crit":1,"displace":6,"ember":1,"explode":10,"garrison":1,"heal":6,"pulse":45,"reveal":1,"sleep":8,"summon":24,"undying":1,"zone":2},
|
"fx": {"aoe":1,"blink":1,"crit":1,"displace":6,"ember":1,"explode":10,"garrison":1,"heal":6,"pulse":45,"reveal":1,"sleep":8,"summon":24,"undying":1,"zone":2},
|
||||||
"snaps": "554dbc21 dca8028a bfe874b1 c3ea8fde b234e0fc e2e35296 0e5e8cfd 66160212 74b71698 3535e79e 56ba7f72"
|
"snaps": "554dbc21 dca8028a bfe874b1 c3ea8fde b234e0fc e2e35296 0e5e8cfd 66160212 74b71698 3535e79e 56ba7f72"
|
||||||
},
|
},
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"enemy_2009_csaudc": [1,0,1,0,490]
|
"enemy_2009_csaudc": [1,0,1,0,490]
|
||||||
},
|
},
|
||||||
"hooks": [1,56,3296,431,431,1432,1451,182,23,50,17,17,0,10435,29,27,36,27,1,31,0,2,0],
|
"hooks": [1,56,3296,431,431,1432,1451,182,23,50,17,17,0,10435,29,27,36,27,1,31,0,2,0],
|
||||||
"events": {"atk":493,"bounty":1,"deploy":17,"die":23,"dmg":1384,"fx":20,"heal":182,"leak":27,"skill":34,"spawn":52,"status":161},
|
"events": {"atk":493,"bounty":1,"deploy":17,"die":23,"dmg":1384,"engage":11,"fx":20,"heal":182,"leak":27,"skill":34,"spawn":52,"status":161},
|
||||||
"fx": {"aoe":4,"burst":2,"camouflage":2,"dodge":3,"dollEnd":1,"liberate":1,"overload":1,"shield":2,"shieldBreak":1,"substitute":1,"summon":1,"takeoff":1},
|
"fx": {"aoe":4,"burst":2,"camouflage":2,"dodge":3,"dollEnd":1,"liberate":1,"overload":1,"shield":2,"shieldBreak":1,"substitute":1,"summon":1,"takeoff":1},
|
||||||
"snaps": "f188aea7 a6afe0e2 586473b0 64a602b4 a28a4c01 c2e6f731 0fc56c2d 9e21f4c3 795ab8db 9dbb7b5b 6f022b9b"
|
"snaps": "f188aea7 a6afe0e2 586473b0 64a602b4 a28a4c01 c2e6f731 0fc56c2d 9e21f4c3 795ab8db 9dbb7b5b 6f022b9b"
|
||||||
},
|
},
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
"enemy_2010_csdcr": [1,1,0,1414,9600]
|
"enemy_2010_csdcr": [1,1,0,1414,9600]
|
||||||
},
|
},
|
||||||
"hooks": [1,102,4200,712,712,1693,1684,767,82,89,37,36,0,28924,152,52,84,1,1,82,0,0,0],
|
"hooks": [1,102,4200,712,712,1693,1684,767,82,89,37,36,0,28924,152,52,84,1,1,82,0,0,0],
|
||||||
"events": {"atk":893,"bounty":5,"deploy":18,"die":88,"dmg":1628,"fx":69,"heal":143,"leak":1,"skill":73,"spawn":97,"status":437},
|
"events": {"atk":893,"bounty":5,"deploy":18,"die":88,"dmg":1628,"engage":7,"fx":69,"heal":143,"leak":1,"skill":73,"spawn":97,"status":437},
|
||||||
"fx": {"counter":1,"explode":3,"expose":14,"overload":2,"phase":1,"shield":8,"shieldBreak":16,"sleep":7,"summon":5,"telegraph":5,"zone":7},
|
"fx": {"counter":1,"explode":3,"expose":14,"overload":2,"phase":1,"shield":8,"shieldBreak":16,"sleep":7,"summon":5,"telegraph":5,"zone":7},
|
||||||
"snaps": "de0d6fce 6d6804e9 d7011be0 b72154cf 325314c7 efa5c8d0 ae45ceac e177f8b9 c1acb458 e7de2cbb de6e5396 c820655d de465eb1 281894a7"
|
"snaps": "de0d6fce 6d6804e9 d7011be0 b72154cf 325314c7 efa5c8d0 ae45ceac e177f8b9 c1acb458 e7de2cbb de6e5396 c820655d de465eb1 281894a7"
|
||||||
},
|
},
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
"enemy_2017_csphts": [1,1,0,1838,9600]
|
"enemy_2017_csphts": [1,1,0,1838,9600]
|
||||||
},
|
},
|
||||||
"hooks": [1,90,4200,372,372,658,640,177,77,77,22,22,0,20005,55,36,78,0,1,77,1,0,4],
|
"hooks": [1,90,4200,372,372,658,640,177,77,77,22,22,0,20005,55,36,78,0,1,77,1,0,4],
|
||||||
"events": {"atk":383,"bounty":5,"deploy":12,"die":77,"dmg":640,"fx":33,"heal":115,"layer":1,"skill":46,"spawn":90,"status":35},
|
"events": {"atk":383,"bounty":5,"deploy":12,"die":77,"dmg":640,"engage":8,"fx":33,"heal":115,"layer":1,"skill":46,"spawn":90,"status":35},
|
||||||
"fx": {"aoe":2,"buff":1,"coin":3,"dodge":9,"explode":2,"garrison":1,"phase":2,"shieldBreak":1,"sp":2,"takeoff":3,"zone":7},
|
"fx": {"aoe":2,"buff":1,"coin":3,"dodge":9,"explode":2,"garrison":1,"phase":2,"shieldBreak":1,"sp":2,"takeoff":3,"zone":7},
|
||||||
"snaps": "7e2ad48a 6bd6d5ab b9d597a4 5d6f961e 1e4c04e2 525f85c6 9bf96317 42a8fa26 f1af39d5 66b9f6f3 bca33a7f 56cc754e 896ae641 1111820a"
|
"snaps": "7e2ad48a 6bd6d5ab b9d597a4 5d6f961e 1e4c04e2 525f85c6 9bf96317 42a8fa26 f1af39d5 66b9f6f3 bca33a7f 56cc754e 896ae641 1111820a"
|
||||||
},
|
},
|
||||||
@@ -415,7 +415,7 @@
|
|||||||
"enemy_2021_syfish": [1,0,1,0,0]
|
"enemy_2021_syfish": [1,0,1,0,0]
|
||||||
},
|
},
|
||||||
"hooks": [1,195,4200,666,666,927,939,427,131,183,30,30,0,17741,21,47,171,52,1,141,0,0,0],
|
"hooks": [1,195,4200,666,666,927,939,427,131,183,30,30,0,17741,21,47,171,52,1,141,0,0,0],
|
||||||
"events": {"atk":778,"bounty":1,"deploy":22,"die":131,"dmg":926,"fx":141,"heal":1,"leak":52,"skill":60,"spawn":186,"status":73},
|
"events": {"atk":778,"bounty":1,"deploy":22,"die":131,"dmg":926,"engage":13,"fx":141,"heal":1,"leak":52,"skill":60,"spawn":186,"status":73},
|
||||||
"fx": {"aoe":2,"camouflage":1,"displace":25,"dp":3,"explode":19,"expose":1,"pull":25,"sp":1,"strike":3,"summon":46,"volley":7,"wolfShadow":3,"wolfShadowLost":5},
|
"fx": {"aoe":2,"camouflage":1,"displace":25,"dp":3,"explode":19,"expose":1,"pull":25,"sp":1,"strike":3,"summon":46,"volley":7,"wolfShadow":3,"wolfShadowLost":5},
|
||||||
"snaps": "1e865453 8da2b91c 8ffcf382 5f3baf36 c084da7c c4a17fcb d60c9513 b462beff 05a1582e 8764853d 1fa503d0 eebfaa4c b2311536 b68273bd"
|
"snaps": "1e865453 8da2b91c 8ffcf382 5f3baf36 c084da7c c4a17fcb d60c9513 b462beff 05a1582e 8764853d 1fa503d0 eebfaa4c b2311536 b68273bd"
|
||||||
},
|
},
|
||||||
@@ -483,7 +483,7 @@
|
|||||||
"enemy_2025_syufo": [13,1,9,22069,61781]
|
"enemy_2025_syufo": [13,1,9,22069,61781]
|
||||||
},
|
},
|
||||||
"hooks": [1,109,5100,476,476,825,898,341,54,104,18,22,0,6770,54,40,88,47,1,58,11,8,0],
|
"hooks": [1,109,5100,476,476,825,898,341,54,104,18,22,0,6770,54,40,88,47,1,58,11,8,0],
|
||||||
"events": {"atk":526,"bounty":4,"deploy":21,"die":57,"dmg":897,"fx":98,"heal":340,"layer":11,"leak":47,"skill":44,"spawn":100,"status":84},
|
"events": {"atk":526,"bounty":4,"deploy":21,"die":57,"dmg":897,"engage":12,"fx":98,"heal":340,"layer":11,"leak":47,"skill":44,"spawn":100,"status":84},
|
||||||
"fx": {"blink":2,"buff":1,"burst":8,"coin":15,"displace":11,"explode":16,"garrison":6,"layer":5,"pull":11,"revive":4,"shield":1,"shieldBreak":1,"strike":8,"summon":1,"volley":8},
|
"fx": {"blink":2,"buff":1,"burst":8,"coin":15,"displace":11,"explode":16,"garrison":6,"layer":5,"pull":11,"revive":4,"shield":1,"shieldBreak":1,"strike":8,"summon":1,"volley":8},
|
||||||
"snaps": "a90ffeac d57612e4 e8d10948 88e75de1 e4c4862e 99ffec22 42d6df71 787805d3 6d700ba6 662099ea 641c7acd 8c28fc25 17c9a531 45c3a979 675372ca 8171c584 4bb0fa0e"
|
"snaps": "a90ffeac d57612e4 e8d10948 88e75de1 e4c4862e 99ffec22 42d6df71 787805d3 6d700ba6 662099ea 641c7acd 8c28fc25 17c9a531 45c3a979 675372ca 8171c584 4bb0fa0e"
|
||||||
},
|
},
|
||||||
@@ -544,7 +544,7 @@
|
|||||||
"enemy_2034_sythef": [1,1,0,0,8640]
|
"enemy_2034_sythef": [1,1,0,0,8640]
|
||||||
},
|
},
|
||||||
"hooks": [1,187,4617,720,720,1321,1305,0,172,172,62,59,0,20248,103,11,171,0,1,175,0,0,0],
|
"hooks": [1,187,4617,720,720,1321,1305,0,172,172,62,59,0,20248,103,11,171,0,1,175,0,0,0],
|
||||||
"events": {"atk":899,"bounty":6,"deploy":14,"die":172,"dmg":1304,"fx":171,"skill":121,"spawn":186,"status":209},
|
"events": {"atk":899,"bounty":6,"deploy":14,"die":172,"dmg":1304,"engage":10,"fx":171,"skill":121,"spawn":186,"status":209},
|
||||||
"fx": {"crit":20,"displace":30,"dp":3,"explode":3,"phase":1,"ripple":9,"shadowWeave":28,"shield":2,"shieldBreak":1,"stealth":3,"summon":51,"telegraph":3,"volley":9,"wolfShadow":4,"wolfShadowLost":3,"zone":1},
|
"fx": {"crit":20,"displace":30,"dp":3,"explode":3,"phase":1,"ripple":9,"shadowWeave":28,"shield":2,"shieldBreak":1,"stealth":3,"summon":51,"telegraph":3,"volley":9,"wolfShadow":4,"wolfShadowLost":3,"zone":1},
|
||||||
"snaps": "74efae3f f0ba5ed6 7f080293 ce089018 72fd5b40 aa7cfc03 9cd4911e 59794743 22e30ffb a647f35c 1fac8532 af13d758 f00ae8e0 3abb265c d6a41faf b70c8a95"
|
"snaps": "74efae3f f0ba5ed6 7f080293 ce089018 72fd5b40 aa7cfc03 9cd4911e 59794743 22e30ffb a647f35c 1fac8532 af13d758 f00ae8e0 3abb265c d6a41faf b70c8a95"
|
||||||
},
|
},
|
||||||
@@ -607,7 +607,7 @@
|
|||||||
"enemy_2034_sythef_2": [1,0,1,0,0]
|
"enemy_2034_sythef_2": [1,0,1,0,0]
|
||||||
},
|
},
|
||||||
"hooks": [1,130,5100,419,419,1069,1051,388,57,119,20,21,0,7357,426,12,102,62,1,207,3,0,10],
|
"hooks": [1,130,5100,419,419,1069,1051,388,57,119,20,21,0,7357,426,12,102,62,1,207,3,0,10],
|
||||||
"events": {"atk":485,"bounty":2,"deploy":25,"die":57,"dmg":959,"fx":147,"heal":4,"layer":3,"leak":62,"skill":42,"spawn":119,"status":274},
|
"events": {"atk":485,"bounty":2,"deploy":25,"die":57,"dmg":959,"engage":12,"fx":147,"heal":4,"layer":3,"leak":62,"skill":42,"spawn":119,"status":274},
|
||||||
"fx": {"block":8,"catShield":1,"charge":1,"dilemma":1,"dodge":10,"explode":18,"explosion":2,"featherArrow":1,"flame":1,"frostNova":4,"garrison":3,"garrisonGrant":1,"overclock":1,"pull":1,"riposte":2,"rockslide":2,"shieldBreak":5,"slippery":1,"summon":11,"swordRain":48,"swordStorm":5,"telegraph":1,"thorns":6,"tornado":1,"tornadoPulse":5,"undying":7},
|
"fx": {"block":8,"catShield":1,"charge":1,"dilemma":1,"dodge":10,"explode":18,"explosion":2,"featherArrow":1,"flame":1,"frostNova":4,"garrison":3,"garrisonGrant":1,"overclock":1,"pull":1,"riposte":2,"rockslide":2,"shieldBreak":5,"slippery":1,"summon":11,"swordRain":48,"swordStorm":5,"telegraph":1,"thorns":6,"tornado":1,"tornadoPulse":5,"undying":7},
|
||||||
"snaps": "57fe2e2f 4a53b7b8 2e0d662f c2473d0c d61f8291 0f879595 34e652a4 ea9b995f 19d258e5 019f5b39 2e2a3d4d 8b38907c 2927da2c 3ad7a87f ce8d9d20 0b381f42 180cd6d8"
|
"snaps": "57fe2e2f 4a53b7b8 2e0d662f c2473d0c d61f8291 0f879595 34e652a4 ea9b995f 19d258e5 019f5b39 2e2a3d4d 8b38907c 2927da2c 3ad7a87f ce8d9d20 0b381f42 180cd6d8"
|
||||||
},
|
},
|
||||||
@@ -667,7 +667,7 @@
|
|||||||
"enemy_9011_acrefr": [12,8,0,12883,318178]
|
"enemy_9011_acrefr": [12,8,0,12883,318178]
|
||||||
},
|
},
|
||||||
"hooks": [1,101,5700,996,996,2496,2844,832,83,83,47,43,30,26684,566,37,80,0,1,84,2,0,0],
|
"hooks": [1,101,5700,996,996,2496,2844,832,83,83,47,43,30,26684,566,37,80,0,1,84,2,0,0],
|
||||||
"events": {"atk":1888,"bounty":6,"deploy":19,"die":83,"dmg":2609,"fx":288,"heal":149,"layer":2,"skill":90,"spawn":94,"status":1332},
|
"events": {"atk":1888,"bounty":6,"deploy":19,"die":83,"dmg":2609,"engage":9,"fx":288,"heal":149,"layer":2,"skill":90,"spawn":94,"status":1332},
|
||||||
"fx": {"camouflage":2,"charge":19,"counter":30,"crit":32,"dilemma":4,"displace":33,"explode":1,"garrison":2,"healField":2,"jungleSoul":3,"mote":24,"quadShot":1,"redistribute":30,"revive":1,"reweave":2,"ripple":18,"sandChains":5,"sentry":1,"sermon":1,"shadowWeave":27,"shield":6,"slash":30,"stealth":5,"summon":1,"telegraph":6,"truesilver":2},
|
"fx": {"camouflage":2,"charge":19,"counter":30,"crit":32,"dilemma":4,"displace":33,"explode":1,"garrison":2,"healField":2,"jungleSoul":3,"mote":24,"quadShot":1,"redistribute":30,"revive":1,"reweave":2,"ripple":18,"sandChains":5,"sentry":1,"sermon":1,"shadowWeave":27,"shield":6,"slash":30,"stealth":5,"summon":1,"telegraph":6,"truesilver":2},
|
||||||
"snaps": "c002e613 10a048e4 83882719 48595aaf c4331395 cfece443 01fb830e 82f827f8 d791c555 fec11375 d25c1e59 b986924f 415cc8f6 cc8bf2ac 1aec718d d2106326 d7cd1fea 2093bc04 facf1e90"
|
"snaps": "c002e613 10a048e4 83882719 48595aaf c4331395 cfece443 01fb830e 82f827f8 d791c555 fec11375 d25c1e59 b986924f 415cc8f6 cc8bf2ac 1aec718d d2106326 d7cd1fea 2093bc04 facf1e90"
|
||||||
},
|
},
|
||||||
@@ -730,7 +730,7 @@
|
|||||||
"enemy_2050_smsha": [1,1,0,2185,46080]
|
"enemy_2050_smsha": [1,1,0,2185,46080]
|
||||||
},
|
},
|
||||||
"hooks": [1,121,4854,470,470,1388,1387,830,102,108,85,83,0,16257,1775,20,100,3,1,112,1,0,3],
|
"hooks": [1,121,4854,470,470,1388,1387,830,102,108,85,83,0,16257,1775,20,100,3,1,112,1,0,3],
|
||||||
"events": {"atk":628,"bounty":5,"deploy":18,"die":105,"dmg":1379,"fx":236,"heal":222,"layer":1,"leak":3,"skill":170,"spawn":117,"status":878},
|
"events": {"atk":628,"bounty":5,"deploy":18,"die":105,"dmg":1379,"engage":10,"fx":236,"heal":222,"layer":1,"leak":3,"skill":170,"spawn":117,"status":878},
|
||||||
"fx": {"beam":6,"blink":2,"block":1,"displace":10,"dodge":3,"explode":4,"explosion":23,"featherArrow":6,"flame":3,"frostNova":37,"garrison":1,"garrisonGrant":1,"healField":2,"overclock":2,"palsy":1,"pull":2,"revive":1,"riposte":3,"rockslide":6,"shieldBreak":8,"slash":19,"summon":1,"swordStorm":1,"telegraph":3,"thorns":6,"tornado":2,"tornadoPulse":10,"truesilver":3,"zone":69},
|
"fx": {"beam":6,"blink":2,"block":1,"displace":10,"dodge":3,"explode":4,"explosion":23,"featherArrow":6,"flame":3,"frostNova":37,"garrison":1,"garrisonGrant":1,"healField":2,"overclock":2,"palsy":1,"pull":2,"revive":1,"riposte":3,"rockslide":6,"shieldBreak":8,"slash":19,"summon":1,"swordStorm":1,"telegraph":3,"thorns":6,"tornado":2,"tornadoPulse":10,"truesilver":3,"zone":69},
|
||||||
"snaps": "273dcc8e c323f708 7c1d7d8a 0f1c8a5d fa1faa7c 5973c4b4 1abb6f51 fc254e01 5e27fbd7 4c75c9e8 3d3d371b d21abedf 1215afdd c03925aa 45aadc00 eecaaba5 03eee058"
|
"snaps": "273dcc8e c323f708 7c1d7d8a 0f1c8a5d fa1faa7c 5973c4b4 1abb6f51 fc254e01 5e27fbd7 4c75c9e8 3d3d371b d21abedf 1215afdd c03925aa 45aadc00 eecaaba5 03eee058"
|
||||||
},
|
},
|
||||||
@@ -801,7 +801,7 @@
|
|||||||
"enemy_5601_entlec": [7,7,0,0,81403]
|
"enemy_5601_entlec": [7,7,0,0,81403]
|
||||||
},
|
},
|
||||||
"hooks": [1,126,5700,593,593,1848,2658,17845,100,114,25,21,16,24262,248,23,111,12,1,185,29,2,0],
|
"hooks": [1,126,5700,593,593,1848,2658,17845,100,114,25,21,16,24262,248,23,111,12,1,185,29,2,0],
|
||||||
"events": {"atk":1052,"bounty":3,"deploy":15,"die":102,"dmg":2093,"fx":286,"heal":267,"layer":29,"leak":12,"skill":46,"spawn":123,"status":721},
|
"events": {"atk":1052,"bounty":3,"deploy":15,"die":102,"dmg":2093,"engage":8,"fx":286,"heal":267,"layer":29,"leak":12,"skill":46,"spawn":123,"status":721},
|
||||||
"fx": {"anchor":3,"aoe":2,"bandage":2,"burst":2,"candle":7,"downed":2,"ember":2,"explode":1,"garrison":29,"garrisonGrant":1,"jungleSoul":2,"mote":96,"overload":2,"redistribute":38,"revive":1,"reweave":3,"sandChains":2,"sandChainsCharged":2,"spGift":35,"splash":16,"summon":1,"wake":37},
|
"fx": {"anchor":3,"aoe":2,"bandage":2,"burst":2,"candle":7,"downed":2,"ember":2,"explode":1,"garrison":29,"garrisonGrant":1,"jungleSoul":2,"mote":96,"overload":2,"redistribute":38,"revive":1,"reweave":3,"sandChains":2,"sandChainsCharged":2,"spGift":35,"splash":16,"summon":1,"wake":37},
|
||||||
"snaps": "6c317ca9 7f45f40d dad79f3d e90c8987 002195fc 2da215da 1d494876 e7b6c5b2 d1226386 4ee87a1d fa35734a 5d6d473f e88eabc4 99f28a48 c9d792a9 4eb8e136 5529c074 daac4c5a 05eacc7a"
|
"snaps": "6c317ca9 7f45f40d dad79f3d e90c8987 002195fc 2da215da 1d494876 e7b6c5b2 d1226386 4ee87a1d fa35734a 5d6d473f e88eabc4 99f28a48 c9d792a9 4eb8e136 5529c074 daac4c5a 05eacc7a"
|
||||||
},
|
},
|
||||||
@@ -862,7 +862,7 @@
|
|||||||
"enemy_2085_skzjxd": [1,0,1,0,0]
|
"enemy_2085_skzjxd": [1,0,1,0,0]
|
||||||
},
|
},
|
||||||
"hooks": [1,216,6054,531,556,1900,1900,2691,50,212,62,62,11,13131,404,65,183,161,1,66,1,5,22],
|
"hooks": [1,216,6054,531,556,1900,1900,2691,50,212,62,62,11,13131,404,65,183,161,1,66,1,5,22],
|
||||||
"events": {"atk":815,"bounty":1,"deploy":30,"die":51,"dmg":1898,"fx":182,"heal":70,"layer":1,"leak":161,"skill":124,"spawn":199,"status":686},
|
"events": {"atk":815,"bounty":1,"deploy":30,"die":51,"dmg":1898,"engage":12,"fx":182,"heal":70,"layer":1,"leak":161,"skill":124,"spawn":199,"status":686},
|
||||||
"fx": {"aoe":13,"burst":5,"charge":1,"crit":18,"dodge":22,"dollEnd":1,"extraAttack":6,"garrison":1,"healAoe":2,"iceSpike":31,"shieldBreak":42,"sp":17,"substitute":2,"summon":1,"swap":1,"weightless":1,"zone":18},
|
"fx": {"aoe":13,"burst":5,"charge":1,"crit":18,"dodge":22,"dollEnd":1,"extraAttack":6,"garrison":1,"healAoe":2,"iceSpike":31,"shieldBreak":42,"sp":17,"substitute":2,"summon":1,"swap":1,"weightless":1,"zone":18},
|
||||||
"snaps": "68e6dd3e 63da606a 50932fd2 c9d08aea 1b0529cf 4ee3f028 9481f888 e5c1490f 3b723a57 4d1edeed 22285b31 ebd626d7 b4e23d9d f84895d6 9d224ac1 2fcc6d1c 5124a24a 483d6958 d5a7245d f8f40b5f 3c97ac78"
|
"snaps": "68e6dd3e 63da606a 50932fd2 c9d08aea 1b0529cf 4ee3f028 9481f888 e5c1490f 3b723a57 4d1edeed 22285b31 ebd626d7 b4e23d9d f84895d6 9d224ac1 2fcc6d1c 5124a24a 483d6958 d5a7245d f8f40b5f 3c97ac78"
|
||||||
},
|
},
|
||||||
@@ -942,7 +942,7 @@
|
|||||||
"enemy_9006_actoxi": [30,30,0,8926,699840]
|
"enemy_9006_actoxi": [30,30,0,8926,699840]
|
||||||
},
|
},
|
||||||
"hooks": [1,139,5504,359,359,2190,3119,2138,109,119,154,152,0,15398,4871,48,108,0,1,301,2,0,0],
|
"hooks": [1,139,5504,359,359,2190,3119,2138,109,119,154,152,0,15398,4871,48,108,0,1,301,2,0,0],
|
||||||
"events": {"atk":407,"bounty":6,"deploy":28,"die":119,"dmg":2200,"fx":441,"heal":1928,"layer":2,"skill":306,"spawn":128,"status":684},
|
"events": {"atk":407,"bounty":6,"deploy":28,"die":119,"dmg":2200,"engage":11,"fx":441,"heal":1928,"layer":2,"skill":306,"spawn":128,"status":684},
|
||||||
"fx": {"anchor":5,"aoe":49,"candle":1,"ember":4,"garrison":2,"healAoe":78,"overload":3,"revive":4,"sp":101,"substitute":1,"summon":3,"swap":1,"telegraph":1,"teleport":7,"zone":181},
|
"fx": {"anchor":5,"aoe":49,"candle":1,"ember":4,"garrison":2,"healAoe":78,"overload":3,"revive":4,"sp":101,"substitute":1,"summon":3,"swap":1,"telegraph":1,"teleport":7,"zone":181},
|
||||||
"snaps": "798d3b9e 2b34ab7d 8be8f821 c3698188 979eb9f1 903039ba ed4b9fbc 40a31c8a ebd507cf b18857df 989c8b31 002c7544 b6985bf3 d204fd2e 7dd78575 a543964b 84af0388 363f0d65 7410a619"
|
"snaps": "798d3b9e 2b34ab7d 8be8f821 c3698188 979eb9f1 903039ba ed4b9fbc 40a31c8a ebd507cf b18857df 989c8b31 002c7544 b6985bf3 d204fd2e 7dd78575 a543964b 84af0388 363f0d65 7410a619"
|
||||||
},
|
},
|
||||||
@@ -1004,7 +1004,7 @@
|
|||||||
"enemy_9006_actoxi": [1,0,1,0,1334]
|
"enemy_9006_actoxi": [1,0,1,0,1334]
|
||||||
},
|
},
|
||||||
"hooks": [1,151,6900,1039,1098,8702,9061,75616,84,136,42,37,17,37922,1075,49,126,48,1,98,1,19,6],
|
"hooks": [1,151,6900,1039,1098,8702,9061,75616,84,136,42,37,17,37922,1075,49,126,48,1,98,1,19,6],
|
||||||
"events": {"atk":2514,"bounty":3,"deploy":25,"die":88,"dmg":9001,"fx":539,"heal":215,"layer":1,"leak":48,"skill":79,"spawn":143,"status":2462},
|
"events": {"atk":2514,"bounty":3,"deploy":25,"die":88,"dmg":9001,"engage":10,"fx":539,"heal":215,"layer":1,"leak":48,"skill":79,"spawn":143,"status":2462},
|
||||||
"fx": {"aoe":8,"blessing":1,"bombard":17,"bombardShell":17,"burst":19,"crit":46,"dodge":6,"explode":20,"firewall":3,"garrison":1,"iceSpike":90,"lightning":145,"lock":17,"lure":3,"revive":1,"snow":82,"soul":1,"sp":3,"storm":5,"talent":2,"telegraph":6,"tide":3,"wanted":39,"weightless":4},
|
"fx": {"aoe":8,"blessing":1,"bombard":17,"bombardShell":17,"burst":19,"crit":46,"dodge":6,"explode":20,"firewall":3,"garrison":1,"iceSpike":90,"lightning":145,"lock":17,"lure":3,"revive":1,"snow":82,"soul":1,"sp":3,"storm":5,"talent":2,"telegraph":6,"tide":3,"wanted":39,"weightless":4},
|
||||||
"snaps": "ee6304b1 a0a8db7b 068a3ee5 8ee7bd29 88542758 3332f494 ae7dd280 3f1096b4 1941c90e 8e911459 33fb0edd da20bfa6 72ea363d 84663ce3 d02717dc 820cc8be d04c4865 6b92a4d6 c868bded 468439dd e830528e 160601bb cc9b19e4"
|
"snaps": "ee6304b1 a0a8db7b 068a3ee5 8ee7bd29 88542758 3332f494 ae7dd280 3f1096b4 1941c90e 8e911459 33fb0edd da20bfa6 72ea363d 84663ce3 d02717dc 820cc8be d04c4865 6b92a4d6 c868bded 468439dd e830528e 160601bb cc9b19e4"
|
||||||
},
|
},
|
||||||
@@ -1075,7 +1075,7 @@
|
|||||||
"enemy_9007_acelem": [1,1,0,150,34560]
|
"enemy_9007_acelem": [1,1,0,150,34560]
|
||||||
},
|
},
|
||||||
"hooks": [1,196,6067,801,801,1567,1882,330,136,195,29,29,80,25530,1195,134,174,58,1,182,3,2,0],
|
"hooks": [1,196,6067,801,801,1567,1882,330,136,195,29,29,80,25530,1195,134,174,58,1,182,3,2,0],
|
||||||
"events": {"atk":856,"bounty":6,"deploy":22,"die":137,"dmg":1743,"fx":278,"heal":149,"layer":3,"leak":58,"skill":58,"spawn":187,"status":844},
|
"events": {"atk":856,"bounty":6,"deploy":22,"die":137,"dmg":1743,"engage":11,"fx":278,"heal":149,"layer":3,"leak":58,"skill":58,"spawn":187,"status":844},
|
||||||
"fx": {"airstrike":20,"beam":2,"burst":2,"displace":5,"dp":2,"drone":111,"droneLock":50,"drones":2,"emergency":3,"explode":16,"finale":2,"garrison":3,"link":2,"scorchBurst":34,"slash":12,"steal":2,"summon":2,"sunBurst":2,"talent":3,"undying":3},
|
"fx": {"airstrike":20,"beam":2,"burst":2,"displace":5,"dp":2,"drone":111,"droneLock":50,"drones":2,"emergency":3,"explode":16,"finale":2,"garrison":3,"link":2,"scorchBurst":34,"slash":12,"steal":2,"summon":2,"sunBurst":2,"talent":3,"undying":3},
|
||||||
"snaps": "8cc6173f 78a2a3f2 980bca21 b3288370 06ae8f2a 8bad204c 84b97b8f 7f51ac7a 26ee6099 7389cc55 a87ec8ab 4186a251 c48ea3af 787853e8 5e7df826 9196a07e b960104e 24a29bb9 6f13a14b 44d34e3e c29968e9"
|
"snaps": "8cc6173f 78a2a3f2 980bca21 b3288370 06ae8f2a 8bad204c 84b97b8f 7f51ac7a 26ee6099 7389cc55 a87ec8ab 4186a251 c48ea3af 787853e8 5e7df826 9196a07e b960104e 24a29bb9 6f13a14b 44d34e3e c29968e9"
|
||||||
},
|
},
|
||||||
@@ -1158,7 +1158,7 @@
|
|||||||
"enemy_9008_acbunn": [1,1,0,2434,19907]
|
"enemy_9008_acbunn": [1,1,0,2434,19907]
|
||||||
},
|
},
|
||||||
"hooks": [1,146,5863,534,534,1311,1464,508,125,132,39,34,35,25836,360,5,122,0,1,125,11,3,0],
|
"hooks": [1,146,5863,534,534,1311,1464,508,125,132,39,34,35,25836,360,5,122,0,1,125,11,3,0],
|
||||||
"events": {"atk":625,"bounty":6,"deploy":22,"die":132,"dmg":1383,"fx":266,"heal":235,"layer":11,"skill":73,"spawn":146,"status":411},
|
"events": {"atk":625,"bounty":6,"deploy":22,"die":132,"dmg":1383,"engage":13,"fx":266,"heal":235,"layer":11,"skill":73,"spawn":146,"status":411},
|
||||||
"fx": {"beam":4,"bombard":35,"bombardShell":35,"burst":3,"camouflage":2,"copy":1,"dp":5,"garrison":8,"lightning":84,"link":1,"lock":35,"lure":2,"scorchBurst":22,"snow":7,"split":5,"storm":7,"summon":2,"talent":1,"tide":5,"wanted":2},
|
"fx": {"beam":4,"bombard":35,"bombardShell":35,"burst":3,"camouflage":2,"copy":1,"dp":5,"garrison":8,"lightning":84,"link":1,"lock":35,"lure":2,"scorchBurst":22,"snow":7,"split":5,"storm":7,"summon":2,"talent":1,"tide":5,"wanted":2},
|
||||||
"snaps": "5acdc7df c55424c0 c6c7faaa 141f61c9 87b42903 b9f9f4bb bfe3c39f ff01f7c3 88dde5ed e1cffdea 1e6c86b5 b346636f 3411307f 72468689 58b5c601 2a20a3fa 91883493 3699c0bc 8385ea32 05aedbfa"
|
"snaps": "5acdc7df c55424c0 c6c7faaa 141f61c9 87b42903 b9f9f4bb bfe3c39f ff01f7c3 88dde5ed e1cffdea 1e6c86b5 b346636f 3411307f 72468689 58b5c601 2a20a3fa 91883493 3699c0bc 8385ea32 05aedbfa"
|
||||||
},
|
},
|
||||||
@@ -1229,7 +1229,7 @@
|
|||||||
"enemy_9009_acfort": [1,1,0,4299,68797]
|
"enemy_9009_acfort": [1,1,0,4299,68797]
|
||||||
},
|
},
|
||||||
"hooks": [1,125,5969,895,895,2816,3200,677,109,114,29,26,90,21712,972,84,108,0,1,109,2,5,0],
|
"hooks": [1,125,5969,895,895,2816,3200,677,109,114,29,26,90,21712,972,84,108,0,1,109,2,5,0],
|
||||||
"events": {"atk":1265,"bounty":6,"deploy":17,"die":114,"dmg":2997,"fx":541,"heal":512,"layer":2,"skill":55,"spawn":123,"status":429},
|
"events": {"atk":1265,"bounty":6,"deploy":17,"die":114,"dmg":2997,"engage":10,"fx":541,"heal":512,"layer":2,"skill":55,"spawn":123,"status":429},
|
||||||
"fx": {"airstrike":25,"aoe":7,"beam":23,"blink":2,"bombard":8,"bombardShell":8,"burst":5,"cleanse":3,"displace":9,"drone":332,"droneLock":57,"drones":3,"emergency":1,"extraAttack":4,"finale":5,"garrison":2,"garrisonGrant":1,"palsy":1,"radiantSword":3,"slash":32,"steal":3,"summon":3,"sunBurst":1,"talent":3},
|
"fx": {"airstrike":25,"aoe":7,"beam":23,"blink":2,"bombard":8,"bombardShell":8,"burst":5,"cleanse":3,"displace":9,"drone":332,"droneLock":57,"drones":3,"emergency":1,"extraAttack":4,"finale":5,"garrison":2,"garrisonGrant":1,"palsy":1,"radiantSword":3,"slash":32,"steal":3,"summon":3,"sunBurst":1,"talent":3},
|
||||||
"snaps": "381e890f 760dfa5d 93a0f72e 50b58e62 8e3a14af 0f2d2d9c 74612304 48bb381d ffbdca65 03dd2d3c d57b56b8 672398fc 57b8e194 600fba22 465c1e97 492e16d5 0bf0b101 14c19f15 612bfa83 828005d2"
|
"snaps": "381e890f 760dfa5d 93a0f72e 50b58e62 8e3a14af 0f2d2d9c 74612304 48bb381d ffbdca65 03dd2d3c d57b56b8 672398fc 57b8e194 600fba22 465c1e97 492e16d5 0bf0b101 14c19f15 612bfa83 828005d2"
|
||||||
},
|
},
|
||||||
@@ -1282,7 +1282,7 @@
|
|||||||
"enemy_2005_axetro": [1,0,0,11649,0]
|
"enemy_2005_axetro": [1,0,0,11649,0]
|
||||||
},
|
},
|
||||||
"hooks": [1,57,2700,557,556,691,741,5576,34,37,26,24,6,12712,51,37,43,3,1,34,0,2,0],
|
"hooks": [1,57,2700,557,556,691,741,5576,34,37,26,24,6,12712,51,37,43,3,1,34,0,2,0],
|
||||||
"events": {"atk":620,"bounty":3,"deploy":12,"die":34,"dmg":725,"fx":44,"heal":55,"leak":3,"skill":50,"spawn":57,"status":146},
|
"events": {"atk":620,"bounty":3,"deploy":12,"die":34,"dmg":725,"engage":12,"fx":44,"heal":55,"leak":3,"skill":50,"spawn":57,"status":146},
|
||||||
"fx": {"buff":1,"burst":2,"crit":25,"dp":2,"explode":10,"summon":2,"telegraph":2},
|
"fx": {"buff":1,"burst":2,"crit":25,"dp":2,"explode":10,"summon":2,"telegraph":2},
|
||||||
"snaps": "07710787 1b3ca690 e36a30ad 9a49b76a b9d22a50 0c289ee8 092e59ea 62398dd1 226b2339"
|
"snaps": "07710787 1b3ca690 e36a30ad 9a49b76a b9d22a50 0c289ee8 092e59ea 62398dd1 226b2339"
|
||||||
},
|
},
|
||||||
@@ -1336,7 +1336,7 @@
|
|||||||
"enemy_2085_skzjxd_2": [1,1,0,0,11700]
|
"enemy_2085_skzjxd_2": [1,1,0,0,11700]
|
||||||
},
|
},
|
||||||
"hooks": [1,45,2942,615,615,727,734,928,32,33,49,47,32,9649,110,27,29,0,1,32,0,0,0],
|
"hooks": [1,45,2942,615,615,727,734,928,32,33,49,47,32,9649,110,27,29,0,1,32,0,0,0],
|
||||||
"events": {"atk":688,"bounty":4,"deploy":13,"die":33,"dmg":726,"fx":41,"heal":32,"skill":96,"spawn":44,"status":256},
|
"events": {"atk":688,"bounty":4,"deploy":13,"die":33,"dmg":726,"engage":12,"fx":41,"heal":32,"skill":96,"spawn":44,"status":256},
|
||||||
"fx": {"crit":26,"dp":3,"explode":1,"liberate":6,"shield":4,"telegraph":1},
|
"fx": {"crit":26,"dp":3,"explode":1,"liberate":6,"shield":4,"telegraph":1},
|
||||||
"snaps": "08475251 060e1404 a77d3fa6 74ab94fb 39ec40a8 4a899aaf 8a49b1ba b7ea4461 d110804c df9fa85e"
|
"snaps": "08475251 060e1404 a77d3fa6 74ab94fb 39ec40a8 4a899aaf 8a49b1ba b7ea4461 d110804c df9fa85e"
|
||||||
},
|
},
|
||||||
@@ -1388,7 +1388,7 @@
|
|||||||
"enemy_2002_bearmi": [1,0,1,9048,2918]
|
"enemy_2002_bearmi": [1,0,1,9048,2918]
|
||||||
},
|
},
|
||||||
"hooks": [1,43,3300,363,363,888,880,767,22,33,31,31,0,12925,98,30,28,11,1,22,0,0,0],
|
"hooks": [1,43,3300,363,363,888,880,767,22,33,31,31,0,12925,98,30,28,11,1,22,0,0,0],
|
||||||
"events": {"atk":382,"bounty":2,"deploy":13,"die":22,"dmg":880,"fx":26,"heal":463,"leak":11,"skill":62,"spawn":42,"status":202},
|
"events": {"atk":382,"bounty":2,"deploy":13,"die":22,"dmg":880,"engage":10,"fx":26,"heal":463,"leak":11,"skill":62,"spawn":42,"status":202},
|
||||||
"fx": {"buff":1,"crit":1,"liberate":4,"shield":16,"zone":4},
|
"fx": {"buff":1,"crit":1,"liberate":4,"shield":16,"zone":4},
|
||||||
"snaps": "9001a773 d682bb23 8fbcd651 5df53986 07e3ac0f 0f7aa6b9 5acdf417 61e10070 400a77ca 652541a4 8bc87622"
|
"snaps": "9001a773 d682bb23 8fbcd651 5df53986 07e3ac0f 0f7aa6b9 5acdf417 61e10070 400a77ca 652541a4 8bc87622"
|
||||||
},
|
},
|
||||||
@@ -1443,7 +1443,7 @@
|
|||||||
"enemy_2048_smgrd": [1,0,0,8461,2244]
|
"enemy_2048_smgrd": [1,0,0,8461,2244]
|
||||||
},
|
},
|
||||||
"hooks": [1,52,3300,419,419,917,1086,360,32,32,20,17,0,11570,67,46,36,0,1,32,0,4,0],
|
"hooks": [1,52,3300,419,419,917,1086,360,32,32,20,17,0,11570,67,46,36,0,1,32,0,4,0],
|
||||||
"events": {"atk":590,"bounty":3,"deploy":13,"die":32,"dmg":969,"fx":58,"heal":342,"skill":37,"spawn":51,"status":290},
|
"events": {"atk":590,"bounty":3,"deploy":13,"die":32,"dmg":969,"engage":10,"fx":58,"heal":342,"skill":37,"spawn":51,"status":290},
|
||||||
"fx": {"beam":44,"buff":3,"burst":4,"explode":1,"summon":1,"takeoff":1,"telegraph":4},
|
"fx": {"beam":44,"buff":3,"burst":4,"explode":1,"summon":1,"takeoff":1,"telegraph":4},
|
||||||
"snaps": "dd24c30b 998fef35 660771ba c69dbe74 3ffd6844 5f694b3c aac3f1c1 1533a52a ac7cca8b ccd35e50 5b498d3e"
|
"snaps": "dd24c30b 998fef35 660771ba c69dbe74 3ffd6844 5f694b3c aac3f1c1 1533a52a ac7cca8b ccd35e50 5b498d3e"
|
||||||
},
|
},
|
||||||
@@ -1497,7 +1497,7 @@
|
|||||||
"enemy_1535_wlfmster": [1,0,1,8843,18291]
|
"enemy_1535_wlfmster": [1,0,1,8843,18291]
|
||||||
},
|
},
|
||||||
"hooks": [1,200,4200,794,794,1045,1112,2413,152,187,68,66,0,13905,1117,53,188,35,1,161,0,0,0],
|
"hooks": [1,200,4200,794,794,1045,1112,2413,152,187,68,66,0,13905,1117,53,188,35,1,161,0,0,0],
|
||||||
"events": {"atk":928,"bounty":3,"deploy":12,"die":152,"dmg":1066,"fx":77,"heal":172,"leak":35,"skill":134,"spawn":200,"status":544},
|
"events": {"atk":928,"bounty":3,"deploy":12,"die":152,"dmg":1066,"engage":8,"fx":77,"heal":172,"leak":35,"skill":134,"spawn":200,"status":544},
|
||||||
"fx": {"beam":3,"buff":5,"liberate":1,"summon":56,"undying":1,"zone":11},
|
"fx": {"beam":3,"buff":5,"liberate":1,"summon":56,"undying":1,"zone":11},
|
||||||
"snaps": "07ba544c 84f4ccbf 38166938 b925fca7 945f4b86 2aa6642d 96e01f04 a3185a51 b77953b3 fa7afb72 6d5be7ad a030c96f 78dbd4b6 8d3af5e8"
|
"snaps": "07ba544c 84f4ccbf 38166938 b925fca7 945f4b86 2aa6642d 96e01f04 a3185a51 b77953b3 fa7afb72 6d5be7ad a030c96f 78dbd4b6 8d3af5e8"
|
||||||
},
|
},
|
||||||
@@ -1553,7 +1553,7 @@
|
|||||||
"enemy_2021_syfish": [1,1,0,0,4608]
|
"enemy_2021_syfish": [1,1,0,0,4608]
|
||||||
},
|
},
|
||||||
"hooks": [1,81,4200,610,610,981,984,390,67,67,98,95,0,5824,750,71,66,0,1,124,1,0,0],
|
"hooks": [1,81,4200,610,610,981,984,390,67,67,98,95,0,5824,750,71,66,0,1,124,1,0,0],
|
||||||
"events": {"atk":626,"bounty":3,"deploy":12,"die":67,"dmg":984,"fx":172,"heal":331,"layer":1,"skill":195,"spawn":81,"status":233},
|
"events": {"atk":626,"bounty":3,"deploy":12,"die":67,"dmg":984,"engage":9,"fx":172,"heal":331,"layer":1,"skill":195,"spawn":81,"status":233},
|
||||||
"fx": {"block":1,"buff":1,"coin":2,"ember":57,"garrison":1,"sp":71,"summon":1,"takeoff":3,"zone":35},
|
"fx": {"block":1,"buff":1,"coin":2,"ember":57,"garrison":1,"sp":71,"summon":1,"takeoff":3,"zone":35},
|
||||||
"snaps": "a16b87b8 7f2721ce a438ac49 e52df11e 882d7026 bc1e5811 1dfd72d4 90a6dd65 80ab29f4 1b3f315f c5e5d061 a8e8097d ffc02f6e 481fc792"
|
"snaps": "a16b87b8 7f2721ce a438ac49 e52df11e 882d7026 bc1e5811 1dfd72d4 90a6dd65 80ab29f4 1b3f315f c5e5d061 a8e8097d ffc02f6e 481fc792"
|
||||||
},
|
},
|
||||||
@@ -1610,7 +1610,7 @@
|
|||||||
"enemy_9010_acpupp": [1,0,1,0,1540]
|
"enemy_9010_acpupp": [1,0,1,0,1540]
|
||||||
},
|
},
|
||||||
"hooks": [1,98,4200,342,342,1398,1472,9,37,91,26,26,0,8063,24,39,73,53,1,42,0,0,0],
|
"hooks": [1,98,4200,342,342,1398,1472,9,37,91,26,26,0,8063,24,39,73,53,1,42,0,0,0],
|
||||||
"events": {"atk":418,"bounty":3,"deploy":22,"die":38,"dmg":1471,"fx":19,"heal":9,"leak":53,"skill":52,"spawn":90,"status":54},
|
"events": {"atk":418,"bounty":3,"deploy":22,"die":38,"dmg":1471,"engage":14,"fx":19,"heal":9,"leak":53,"skill":52,"spawn":90,"status":54},
|
||||||
"fx": {"dp":3,"explode":3,"liberate":1,"palsy":1,"revive":4,"shield":1,"sp":1,"strike":1,"summon":2,"telegraph":2},
|
"fx": {"dp":3,"explode":3,"liberate":1,"palsy":1,"revive":4,"shield":1,"sp":1,"strike":1,"summon":2,"telegraph":2},
|
||||||
"snaps": "b8ad25ab 03f51213 d7dcd813 5906dc4f 89b1c824 3e67ab1e f003cf8b dc2caccb 6a54c87b 77160756 042f6532 9bdc15a6 3da4b3fe 5314774a"
|
"snaps": "b8ad25ab 03f51213 d7dcd813 5906dc4f 89b1c824 3e67ab1e f003cf8b dc2caccb 6a54c87b 77160756 042f6532 9bdc15a6 3da4b3fe 5314774a"
|
||||||
},
|
},
|
||||||
@@ -1663,7 +1663,7 @@
|
|||||||
"enemy_9006_actoxi": [12,5,5,5143,135278]
|
"enemy_9006_actoxi": [12,5,5,5143,135278]
|
||||||
},
|
},
|
||||||
"hooks": [1,67,5100,540,540,1934,1934,30,38,61,108,110,0,7239,40,30,53,21,1,40,3,0,0],
|
"hooks": [1,67,5100,540,540,1934,1934,30,38,61,108,110,0,7239,40,30,53,21,1,40,3,0,0],
|
||||||
"events": {"atk":747,"bounty":2,"deploy":14,"die":40,"dmg":1863,"fx":108,"heal":30,"layer":3,"leak":21,"skill":220,"spawn":65,"status":187},
|
"events": {"atk":747,"bounty":2,"deploy":14,"die":40,"dmg":1863,"engage":12,"fx":108,"heal":30,"layer":3,"leak":21,"skill":220,"spawn":65,"status":187},
|
||||||
"fx": {"blink":1,"buff":1,"coin":18,"explode":26,"garrison":3,"revive":2,"shield":19,"shieldBreak":1,"strike":10,"volley":7,"zone":20},
|
"fx": {"blink":1,"buff":1,"coin":18,"explode":26,"garrison":3,"revive":2,"shield":19,"shieldBreak":1,"strike":10,"volley":7,"zone":20},
|
||||||
"snaps": "757da7ba 23c3e301 44393780 f617c160 eeedb2b3 6f6317df 3ad26b55 5bd556aa a29f3948 771a5093 f9fd3ae6 9b2e6d4b 873809bc 8d2b5f1a 506cb112 f0f0a521 c25d712d"
|
"snaps": "757da7ba 23c3e301 44393780 f617c160 eeedb2b3 6f6317df 3ad26b55 5bd556aa a29f3948 771a5093 f9fd3ae6 9b2e6d4b 873809bc 8d2b5f1a 506cb112 f0f0a521 c25d712d"
|
||||||
},
|
},
|
||||||
@@ -1715,7 +1715,7 @@
|
|||||||
"enemy_9006_actoxi": [1,0,1,554,712]
|
"enemy_9006_actoxi": [1,0,1,554,712]
|
||||||
},
|
},
|
||||||
"hooks": [1,89,5100,401,401,506,522,30,30,86,28,28,0,4426,16,26,62,56,1,33,0,0,18],
|
"hooks": [1,89,5100,401,401,506,522,30,30,86,28,28,0,4426,16,26,62,56,1,33,0,0,18],
|
||||||
"events": {"atk":427,"deploy":27,"die":30,"dmg":498,"fx":84,"leak":56,"skill":56,"spawn":75,"status":77},
|
"events": {"atk":427,"deploy":27,"die":30,"dmg":498,"engage":11,"fx":84,"leak":56,"skill":56,"spawn":75,"status":77},
|
||||||
"fx": {"camouflage":1,"crit":6,"dodge":18,"dp":4,"phase":1,"pulse":17,"sentry":9,"shadowWeave":9,"shield":4,"shieldBreak":2,"summon":2,"telegraph":1,"volley":6,"wolfShadowLost":3,"zone":1},
|
"fx": {"camouflage":1,"crit":6,"dodge":18,"dp":4,"phase":1,"pulse":17,"sentry":9,"shadowWeave":9,"shield":4,"shieldBreak":2,"summon":2,"telegraph":1,"volley":6,"wolfShadowLost":3,"zone":1},
|
||||||
"snaps": "7c092213 7eeed0b1 ac8f2ed3 3858c149 e8ea91a7 8453351a 4fc7b210 32f63df8 4e74f555 5bfbe308 7746a517 c9e84d55 480b39df 046d3213 b82f6b7a f22d156a 686086b5"
|
"snaps": "7c092213 7eeed0b1 ac8f2ed3 3858c149 e8ea91a7 8453351a 4fc7b210 32f63df8 4e74f555 5bfbe308 7746a517 c9e84d55 480b39df 046d3213 b82f6b7a f22d156a 686086b5"
|
||||||
},
|
},
|
||||||
@@ -1771,7 +1771,7 @@
|
|||||||
"enemy_2002_bearmi_2": [1,1,0,1311,8800]
|
"enemy_2002_bearmi_2": [1,1,0,1311,8800]
|
||||||
},
|
},
|
||||||
"hooks": [1,106,4641,470,470,920,1638,1314,82,91,48,48,0,22203,805,35,85,9,1,82,1,4,1],
|
"hooks": [1,106,4641,470,470,920,1638,1314,82,91,48,48,0,22203,805,35,85,9,1,82,1,4,1],
|
||||||
"events": {"atk":508,"bounty":2,"deploy":19,"die":82,"dmg":1628,"fx":45,"heal":418,"layer":1,"leak":9,"skill":99,"spawn":101,"status":149},
|
"events": {"atk":508,"bounty":2,"deploy":19,"die":82,"dmg":1628,"engage":10,"fx":45,"heal":418,"layer":1,"leak":9,"skill":99,"spawn":101,"status":149},
|
||||||
"fx": {"block":11,"burst":4,"dodge":1,"dp":4,"explode":1,"expose":1,"garrison":1,"garrisonGrant":1,"phase":1,"riposte":1,"shieldBreak":10,"swordRain":5,"zone":4},
|
"fx": {"block":11,"burst":4,"dodge":1,"dp":4,"explode":1,"expose":1,"garrison":1,"garrisonGrant":1,"phase":1,"riposte":1,"shieldBreak":10,"swordRain":5,"zone":4},
|
||||||
"snaps": "d1a66fbb 1e4c3412 cfa655f4 33ae5aea be858e5a e390032f a14b2f14 f815866a e4173185 0a72a5c9 f546a0e1 e7e58926 f223a279 820a4de9 842392b8 3e71a88c"
|
"snaps": "d1a66fbb 1e4c3412 cfa655f4 33ae5aea be858e5a e390032f a14b2f14 f815866a e4173185 0a72a5c9 f546a0e1 e7e58926 f223a279 820a4de9 842392b8 3e71a88c"
|
||||||
},
|
},
|
||||||
@@ -1823,7 +1823,7 @@
|
|||||||
"enemy_2050_smsha": [1,1,0,4186,46080]
|
"enemy_2050_smsha": [1,1,0,4186,46080]
|
||||||
},
|
},
|
||||||
"hooks": [1,66,5700,1218,1218,1660,1731,18992,51,51,149,147,0,18598,114,50,47,0,1,51,3,0,0],
|
"hooks": [1,66,5700,1218,1218,1660,1731,18992,51,51,149,147,0,18598,114,50,47,0,1,51,3,0,0],
|
||||||
"events": {"atk":1455,"bounty":4,"deploy":19,"die":51,"dmg":1730,"fx":220,"heal":171,"layer":3,"skill":296,"spawn":59,"status":489},
|
"events": {"atk":1455,"bounty":4,"deploy":19,"die":51,"dmg":1730,"engage":9,"fx":220,"heal":171,"layer":3,"skill":296,"spawn":59,"status":489},
|
||||||
"fx": {"beam":3,"bloodBattle":3,"camouflage":7,"crit":63,"explode":6,"garrison":3,"mote":97,"shadowWeave":31,"shell":4,"shield":1,"spGift":1,"telegraph":1},
|
"fx": {"beam":3,"bloodBattle":3,"camouflage":7,"crit":63,"explode":6,"garrison":3,"mote":97,"shadowWeave":31,"shell":4,"shield":1,"spGift":1,"telegraph":1},
|
||||||
"snaps": "9e38c03f 5a781a2c 1b1806a8 a01c69a5 478275c7 3fdc19c1 88200e04 cf7c3703 b237f22b d89d10cf 927487a1 34b0c585 dc21e468 f291619d 6275831b f92d22ce 96ce1774 e6fcb005 f68dc57b"
|
"snaps": "9e38c03f 5a781a2c 1b1806a8 a01c69a5 478275c7 3fdc19c1 88200e04 cf7c3703 b237f22b d89d10cf 927487a1 34b0c585 dc21e468 f291619d 6275831b f92d22ce 96ce1774 e6fcb005 f68dc57b"
|
||||||
},
|
},
|
||||||
@@ -1879,7 +1879,7 @@
|
|||||||
"enemy_1539_reid": [1,0,1,7794,22227]
|
"enemy_1539_reid": [1,0,1,7794,22227]
|
||||||
},
|
},
|
||||||
"hooks": [1,96,5384,1057,1057,1143,1129,145,68,81,196,193,0,16010,2319,60,76,13,1,74,1,0,10],
|
"hooks": [1,96,5384,1057,1057,1143,1129,145,68,81,196,193,0,16010,2319,60,76,13,1,74,1,0,10],
|
||||||
"events": {"atk":1129,"bounty":3,"deploy":18,"die":68,"dmg":1111,"fx":87,"heal":137,"layer":1,"leak":13,"skill":392,"spawn":92,"status":423},
|
"events": {"atk":1129,"bounty":3,"deploy":18,"die":68,"dmg":1111,"engage":10,"fx":87,"heal":137,"layer":1,"leak":13,"skill":392,"spawn":92,"status":423},
|
||||||
"fx": {"block":5,"charge":4,"displace":19,"dodge":10,"dp":11,"explode":5,"garrison":1,"garrisonGrant":1,"riposte":10,"shieldBreak":18,"swordRain":2,"undying":1},
|
"fx": {"block":5,"charge":4,"displace":19,"dodge":10,"dp":11,"explode":5,"garrison":1,"garrisonGrant":1,"riposte":10,"shieldBreak":18,"swordRain":2,"undying":1},
|
||||||
"snaps": "70957ea7 12980bbe eb40080d c6a9d8b0 9fbf288c ce758920 f04d0223 eeb062ad 767c47e0 9f0398d9 f2c0ec15 dda8a114 61112f0b 78f7d894 0bcdbfa3 2ea72ee7 7c313f29 5ee2d05a"
|
"snaps": "70957ea7 12980bbe eb40080d c6a9d8b0 9fbf288c ce758920 f04d0223 eeb062ad 767c47e0 9f0398d9 f2c0ec15 dda8a114 61112f0b 78f7d894 0bcdbfa3 2ea72ee7 7c313f29 5ee2d05a"
|
||||||
},
|
},
|
||||||
@@ -1946,7 +1946,7 @@
|
|||||||
"enemy_2025_syufo": [1,0,1,6212,5995]
|
"enemy_2025_syufo": [1,0,1,6212,5995]
|
||||||
},
|
},
|
||||||
"hooks": [1,98,5700,904,904,3483,3735,1662,76,84,93,92,18,27936,433,79,80,8,1,80,14,3,0],
|
"hooks": [1,98,5700,904,904,3483,3735,1662,76,84,93,92,18,27936,433,79,80,8,1,80,14,3,0],
|
||||||
"events": {"atk":1408,"bounty":3,"deploy":15,"die":76,"dmg":3735,"fx":220,"heal":1410,"layer":14,"leak":8,"skill":185,"spawn":95,"status":410},
|
"events": {"atk":1408,"bounty":3,"deploy":15,"die":76,"dmg":3735,"engage":9,"fx":220,"heal":1410,"layer":14,"leak":8,"skill":185,"spawn":95,"status":410},
|
||||||
"fx": {"anchor":20,"aoe":6,"bloodBattle":7,"burst":3,"camouflage":2,"displace":23,"explode":8,"extraAttack":14,"garrison":14,"garrisonGrant":1,"mote":58,"reborn":1,"shell":4,"spGift":56,"zone":3},
|
"fx": {"anchor":20,"aoe":6,"bloodBattle":7,"burst":3,"camouflage":2,"displace":23,"explode":8,"extraAttack":14,"garrison":14,"garrisonGrant":1,"mote":58,"reborn":1,"shell":4,"spGift":56,"zone":3},
|
||||||
"snaps": "0c53a603 05d5c701 ee9f4dd1 513e9602 3c21e902 78c6425e e7c025d9 93b89490 8d6a0515 df9c19cd ecab44df 4ae9e6ce 185ecf73 739fe00a 3325d0b1 85dff7e4 fdc8bbd8 f9295138 ca8fd5c7"
|
"snaps": "0c53a603 05d5c701 ee9f4dd1 513e9602 3c21e902 78c6425e e7c025d9 93b89490 8d6a0515 df9c19cd ecab44df 4ae9e6ce 185ecf73 739fe00a 3325d0b1 85dff7e4 fdc8bbd8 f9295138 ca8fd5c7"
|
||||||
},
|
},
|
||||||
@@ -2014,7 +2014,7 @@
|
|||||||
"enemy_9011_acrefr": [1,0,1,0,941]
|
"enemy_9011_acrefr": [1,0,1,0,941]
|
||||||
},
|
},
|
||||||
"hooks": [1,198,6784,807,807,2836,2852,1721,62,192,45,45,0,17712,150,84,167,129,1,67,4,0,7],
|
"hooks": [1,198,6784,807,807,2836,2852,1721,62,192,45,45,0,17712,150,84,167,129,1,67,4,0,7],
|
||||||
"events": {"atk":890,"bounty":1,"deploy":29,"die":63,"dmg":2826,"fx":80,"heal":218,"layer":4,"leak":129,"skill":90,"spawn":182,"status":178},
|
"events": {"atk":890,"bounty":1,"deploy":29,"die":63,"dmg":2826,"engage":12,"fx":80,"heal":218,"layer":4,"leak":129,"skill":90,"spawn":182,"status":178},
|
||||||
"fx": {"aoe":3,"crit":4,"dodge":7,"dollEnd":2,"garrison":4,"healAoe":4,"hpShare":1,"palsy":1,"reborn":2,"revive":5,"shield":23,"sp":4,"substitute":2,"summon":1,"zone":17},
|
"fx": {"aoe":3,"crit":4,"dodge":7,"dollEnd":2,"garrison":4,"healAoe":4,"hpShare":1,"palsy":1,"reborn":2,"revive":5,"shield":23,"sp":4,"substitute":2,"summon":1,"zone":17},
|
||||||
"snaps": "91b58e42 37d31be2 91cd7722 7db19787 65b82336 0cfa4a88 a0321fa7 0ce2a8db 1d18b08a 9a9ab67c 288bd388 700c526a ab107dd2 c08ac737 58eb67ce 6785a665 f9b21aa2 dbd90927 5e5e89f5 6efdbfe0 b320de5e c14c84bc 64c7b44f"
|
"snaps": "91b58e42 37d31be2 91cd7722 7db19787 65b82336 0cfa4a88 a0321fa7 0ce2a8db 1d18b08a 9a9ab67c 288bd388 700c526a ab107dd2 c08ac737 58eb67ce 6785a665 f9b21aa2 dbd90927 5e5e89f5 6efdbfe0 b320de5e c14c84bc 64c7b44f"
|
||||||
},
|
},
|
||||||
@@ -2068,7 +2068,7 @@
|
|||||||
"enemy_2009_csaudc": [1,0,1,3146,22495]
|
"enemy_2009_csaudc": [1,0,1,3146,22495]
|
||||||
},
|
},
|
||||||
"hooks": [1,121,6900,2598,2598,3968,3976,7692,74,103,357,354,0,34035,4163,91,104,27,1,78,0,2,5],
|
"hooks": [1,121,6900,2598,2598,3968,3976,7692,74,103,357,354,0,34035,4163,91,104,27,1,78,0,2,5],
|
||||||
"events": {"atk":3045,"bounty":3,"deploy":14,"die":76,"dmg":3966,"fx":364,"heal":1226,"leak":27,"skill":711,"spawn":119,"status":863},
|
"events": {"atk":3045,"bounty":3,"deploy":14,"die":76,"dmg":3966,"engage":12,"fx":364,"heal":1226,"leak":27,"skill":711,"spawn":119,"status":863},
|
||||||
"fx": {"aoe":13,"burst":2,"crit":26,"dodge":5,"ember":3,"healAoe":33,"hpShare":5,"revive":1,"sp":137,"zone":139},
|
"fx": {"aoe":13,"burst":2,"crit":26,"dodge":5,"ember":3,"healAoe":33,"hpShare":5,"revive":1,"sp":137,"zone":139},
|
||||||
"snaps": "9a2f8fd8 b37c8c61 f0df4454 24e23a91 980293de cea62154 49bfebd4 fa776cbc 392a23b0 a7f5a5a9 b99c5268 81c2acf0 946acd80 2ef3c97f b5eaa4e5 7483ff45 7a64ad89 8c4728f0 dc665440 391ad73d 3dda4b2c b893d994 2f892f77"
|
"snaps": "9a2f8fd8 b37c8c61 f0df4454 24e23a91 980293de cea62154 49bfebd4 fa776cbc 392a23b0 a7f5a5a9 b99c5268 81c2acf0 946acd80 2ef3c97f b5eaa4e5 7483ff45 7a64ad89 8c4728f0 dc665440 391ad73d 3dda4b2c b893d994 2f892f77"
|
||||||
},
|
},
|
||||||
@@ -2126,7 +2126,7 @@
|
|||||||
"enemy_9007_acelem": [1,1,0,0,34560]
|
"enemy_9007_acelem": [1,1,0,0,34560]
|
||||||
},
|
},
|
||||||
"hooks": [1,143,5571,319,319,517,777,828,126,129,73,70,0,13486,398,4,125,0,1,126,2,11,0],
|
"hooks": [1,143,5571,319,319,517,777,828,126,129,73,70,0,13486,398,4,125,0,1,126,2,11,0],
|
||||||
"events": {"atk":515,"bounty":4,"deploy":18,"die":129,"dmg":777,"fx":219,"heal":13,"layer":2,"skill":143,"spawn":142,"status":585},
|
"events": {"atk":515,"bounty":4,"deploy":18,"die":129,"dmg":777,"engage":6,"fx":219,"heal":13,"layer":2,"skill":143,"spawn":142,"status":585},
|
||||||
"fx": {"bombard":3,"bombardShell":3,"burst":11,"copy":1,"expose":116,"frostNova":1,"garrison":2,"heal":3,"phase":1,"shieldBreak":1,"snow":14,"sp":59,"talent":1,"wanted":3},
|
"fx": {"bombard":3,"bombardShell":3,"burst":11,"copy":1,"expose":116,"frostNova":1,"garrison":2,"heal":3,"phase":1,"shieldBreak":1,"snow":14,"sp":59,"talent":1,"wanted":3},
|
||||||
"snaps": "378db087 da011039 a8cc5431 34ca6f87 443dbbf3 e75588cb 6ebcbad2 8bfa9f27 72a27ef6 b55125f5 2a70aeb3 b7367592 f5c90049 20a5319d c7c52b3e eb8af929 a8659bf6 edfbb948 42af11b8"
|
"snaps": "378db087 da011039 a8cc5431 34ca6f87 443dbbf3 e75588cb 6ebcbad2 8bfa9f27 72a27ef6 b55125f5 2a70aeb3 b7367592 f5c90049 20a5319d c7c52b3e eb8af929 a8659bf6 edfbb948 42af11b8"
|
||||||
},
|
},
|
||||||
@@ -2179,7 +2179,7 @@
|
|||||||
"enemy_2003_rockman": [1,0,1,2448,9451]
|
"enemy_2003_rockman": [1,0,1,2448,9451]
|
||||||
},
|
},
|
||||||
"hooks": [1,158,6900,477,477,931,1150,8737,44,148,27,26,23,15163,2600,22,130,104,1,45,0,5,0],
|
"hooks": [1,158,6900,477,477,931,1150,8737,44,148,27,26,23,15163,2600,22,130,104,1,45,0,5,0],
|
||||||
"events": {"atk":494,"bounty":2,"deploy":25,"die":44,"dmg":971,"fx":378,"heal":414,"leak":104,"skill":53,"spawn":145,"status":525},
|
"events": {"atk":494,"bounty":2,"deploy":25,"die":44,"dmg":971,"engage":9,"fx":378,"heal":414,"leak":104,"skill":53,"spawn":145,"status":525},
|
||||||
"fx": {"airstrike":2,"aoe":3,"blessing":1,"burst":5,"displace":1,"explode":17,"expose":313,"frostNova":4,"healField":2,"link":1,"phase":1,"snow":11,"sunBurst":2,"talent":4,"telegraph":2,"wanted":9},
|
"fx": {"airstrike":2,"aoe":3,"blessing":1,"burst":5,"displace":1,"explode":17,"expose":313,"frostNova":4,"healField":2,"link":1,"phase":1,"snow":11,"sunBurst":2,"talent":4,"telegraph":2,"wanted":9},
|
||||||
"snaps": "be0b8d25 30c6d0d5 05ec9606 7be7f8fd dd4716ec 9b935dce e36e5e43 8f814ab9 4d9a1660 248c489e 50d850ed 29c9b215 cad03322 1c438ab5 8546c260 4b9eadf7 a5cf0d6d 923927c5 24300fdb 0d39a3a4 0273b8b7 4d641969 c7abfcb8"
|
"snaps": "be0b8d25 30c6d0d5 05ec9606 7be7f8fd dd4716ec 9b935dce e36e5e43 8f814ab9 4d9a1660 248c489e 50d850ed 29c9b215 cad03322 1c438ab5 8546c260 4b9eadf7 a5cf0d6d 923927c5 24300fdb 0d39a3a4 0273b8b7 4d641969 c7abfcb8"
|
||||||
},
|
},
|
||||||
@@ -2254,7 +2254,7 @@
|
|||||||
"enemy_2052_smgia": [1,0,1,1324,54640]
|
"enemy_2052_smgia": [1,0,1,1324,54640]
|
||||||
},
|
},
|
||||||
"hooks": [1,170,6516,385,385,2331,2489,21704,69,163,60,57,27,9028,797,23,131,93,1,45,1,4,0],
|
"hooks": [1,170,6516,385,385,2331,2489,21704,69,163,60,57,27,9028,797,23,131,93,1,45,1,4,0],
|
||||||
"events": {"atk":507,"bounty":2,"deploy":36,"die":70,"dmg":2410,"fx":77,"heal":100,"layer":1,"leak":93,"skill":117,"spawn":153,"status":584},
|
"events": {"atk":507,"bounty":2,"deploy":36,"die":70,"dmg":2410,"engage":15,"fx":77,"heal":100,"layer":1,"leak":93,"skill":117,"spawn":153,"status":584},
|
||||||
"fx": {"airstrike":6,"aoe":9,"burst":4,"copy":6,"explode":25,"garrison":1,"heal":3,"healField":4,"link":2,"revive":1,"stone":1,"summon":15},
|
"fx": {"airstrike":6,"aoe":9,"burst":4,"copy":6,"explode":25,"garrison":1,"heal":3,"healField":4,"link":2,"revive":1,"stone":1,"summon":15},
|
||||||
"snaps": "2c514938 bcd55a92 bc0ef3cd 177f0ddc 094ad4d4 3c998e39 91164767 626c1814 ca4a4e11 7d041c72 6049bc05 f1dc407b 018f68f8 8022d1ff ecd0be38 2c92eb60 512c05fd 92c8410f 0f24013e 783f911c c0ed7cca 359704f3"
|
"snaps": "2c514938 bcd55a92 bc0ef3cd 177f0ddc 094ad4d4 3c998e39 91164767 626c1814 ca4a4e11 7d041c72 6049bc05 f1dc407b 018f68f8 8022d1ff ecd0be38 2c92eb60 512c05fd 92c8410f 0f24013e 783f911c c0ed7cca 359704f3"
|
||||||
},
|
},
|
||||||
@@ -2306,7 +2306,7 @@
|
|||||||
"enemy_2001_duckmi": [1,1,0,0,64497]
|
"enemy_2001_duckmi": [1,1,0,0,64497]
|
||||||
},
|
},
|
||||||
"hooks": [1,171,5918,881,880,4623,4636,545,160,160,28,22,58,16483,2343,92,157,0,1,163,2,1,0],
|
"hooks": [1,171,5918,881,880,4623,4636,545,160,160,28,22,58,16483,2343,92,157,0,1,163,2,1,0],
|
||||||
"events": {"atk":1469,"bounty":4,"deploy":14,"die":160,"dmg":4636,"fx":108,"heal":472,"layer":2,"skill":50,"spawn":169,"status":467},
|
"events": {"atk":1469,"bounty":4,"deploy":14,"die":160,"dmg":4636,"engage":11,"fx":108,"heal":472,"layer":2,"skill":50,"spawn":169,"status":467},
|
||||||
"fx": {"aoe":13,"burst":1,"explode":1,"extraAttack":8,"garrison":2,"garrisonGrant":4,"meltdown":1,"overload":4,"reborn":2,"shieldBreak":60,"sleepGuard":3,"sunBurst":3,"talent":3,"undying":3},
|
"fx": {"aoe":13,"burst":1,"explode":1,"extraAttack":8,"garrison":2,"garrisonGrant":4,"meltdown":1,"overload":4,"reborn":2,"shieldBreak":60,"sleepGuard":3,"sunBurst":3,"talent":3,"undying":3},
|
||||||
"snaps": "1ac9ec64 7b9548db f09bce85 8fdfbf4c b7b5cfb1 1ffd5f45 b65ed6f3 0dd60492 e327aa8a 368e4eb0 a9b2c7ac 1e89bd0e ea4b335c 50cf09b8 376cd09d e793a857 4e0fc531 33a5eca2 6e3b0cd5 7ea9fd76"
|
"snaps": "1ac9ec64 7b9548db f09bce85 8fdfbf4c b7b5cfb1 1ffd5f45 b65ed6f3 0dd60492 e327aa8a 368e4eb0 a9b2c7ac 1e89bd0e ea4b335c 50cf09b8 376cd09d e793a857 4e0fc531 33a5eca2 6e3b0cd5 7ea9fd76"
|
||||||
},
|
},
|
||||||
@@ -2372,7 +2372,7 @@
|
|||||||
"enemy_2034_sythef": [1,1,0,139,8640]
|
"enemy_2034_sythef": [1,1,0,139,8640]
|
||||||
},
|
},
|
||||||
"hooks": [1,73,5100,1445,1445,1980,1980,2325,48,54,76,73,0,16134,93,54,53,0,1,50,8,0,0],
|
"hooks": [1,73,5100,1445,1445,1980,1980,2325,48,54,76,73,0,16134,93,54,53,0,1,50,8,0,0],
|
||||||
"events": {"atk":1445,"bounty":3,"deploy":20,"die":54,"dmg":1980,"fx":176,"heal":188,"layer":8,"skill":149,"spawn":66,"status":147},
|
"events": {"atk":1445,"bounty":3,"deploy":20,"die":54,"dmg":1980,"engage":12,"fx":176,"heal":188,"layer":8,"skill":149,"spawn":66,"status":147},
|
||||||
"fx": {"aoe":32,"blink":3,"bondMilestone":1,"buff":14,"coin":63,"displace":6,"dp":28,"garrison":8,"shield":1,"volley":19,"wolfShadow":1},
|
"fx": {"aoe":32,"blink":3,"bondMilestone":1,"buff":14,"coin":63,"displace":6,"dp":28,"garrison":8,"shield":1,"volley":19,"wolfShadow":1},
|
||||||
"snaps": "7039516f 1f992cb5 0f5e59af c4b1bbc5 07a0826f 56c348a1 323da105 91eb067a 068e4335 66187a93 1732df53 b1fbe31f f69a53e8 250909f9 25729cd3 5c78fdcd 2b29dd1f"
|
"snaps": "7039516f 1f992cb5 0f5e59af c4b1bbc5 07a0826f 56c348a1 323da105 91eb067a 068e4335 66187a93 1732df53 b1fbe31f f69a53e8 250909f9 25729cd3 5c78fdcd 2b29dd1f"
|
||||||
},
|
},
|
||||||
@@ -2426,7 +2426,7 @@
|
|||||||
"enemy_9016_acstmr": [1,1,0,0,8]
|
"enemy_9016_acstmr": [1,1,0,0,8]
|
||||||
},
|
},
|
||||||
"hooks": [1,96,5100,650,650,1044,1076,731,55,87,38,37,13,10088,270,14,65,33,1,66,0,2,19],
|
"hooks": [1,96,5100,650,650,1044,1076,731,55,87,38,37,13,10088,270,14,65,33,1,66,0,2,19],
|
||||||
"events": {"atk":742,"bounty":1,"deploy":29,"die":54,"dmg":1064,"fx":117,"heal":3,"leak":33,"skill":75,"spawn":80,"status":169},
|
"events": {"atk":742,"bounty":1,"deploy":29,"die":54,"dmg":1064,"engage":12,"fx":117,"heal":3,"leak":33,"skill":75,"spawn":80,"status":169},
|
||||||
"fx": {"beam":5,"block":4,"buff":4,"burst":2,"coldWind":1,"dodge":19,"dp":5,"explode":1,"liberate":1,"overclock":1,"overload":1,"palsy":3,"revive":3,"ripple":1,"rockfall":1,"sentry":6,"shadowWeave":14,"shield":7,"shieldBreak":7,"stone":1,"swordStorm":9,"telegraph":3,"volley":11,"wake":1,"wolfShadow":2,"wolfShadowLost":3,"zone":1},
|
"fx": {"beam":5,"block":4,"buff":4,"burst":2,"coldWind":1,"dodge":19,"dp":5,"explode":1,"liberate":1,"overclock":1,"overload":1,"palsy":3,"revive":3,"ripple":1,"rockfall":1,"sentry":6,"shadowWeave":14,"shield":7,"shieldBreak":7,"stone":1,"swordStorm":9,"telegraph":3,"volley":11,"wake":1,"wolfShadow":2,"wolfShadowLost":3,"zone":1},
|
||||||
"snaps": "96990ab6 47d9fcbb b6682854 8a9c5942 60e5c9ed 6b9c44fe a5ca7551 1568032d 1be5ada7 b119dbd8 85869e7c 3af299cb d618f1e8 5d28b358 1cf9df99 ca9b5cd1 9233013f"
|
"snaps": "96990ab6 47d9fcbb b6682854 8a9c5942 60e5c9ed 6b9c44fe a5ca7551 1568032d 1be5ada7 b119dbd8 85869e7c 3af299cb d618f1e8 5d28b358 1cf9df99 ca9b5cd1 9233013f"
|
||||||
},
|
},
|
||||||
@@ -2477,7 +2477,7 @@
|
|||||||
"enemy_2010_csdcr": [1,1,0,960,9600]
|
"enemy_2010_csdcr": [1,1,0,960,9600]
|
||||||
},
|
},
|
||||||
"hooks": [1,67,5700,642,642,1055,1059,20714,53,54,28,23,62,26339,94,58,47,0,1,55,0,0,6],
|
"hooks": [1,67,5700,642,642,1055,1059,20714,53,54,28,23,62,26339,94,58,47,0,1,55,0,0,6],
|
||||||
"events": {"atk":946,"bounty":4,"deploy":20,"die":54,"dmg":1015,"fx":168,"heal":48,"skill":51,"spawn":60,"status":343},
|
"events": {"atk":946,"bounty":4,"deploy":20,"die":54,"dmg":1015,"engage":10,"fx":168,"heal":48,"skill":51,"spawn":60,"status":343},
|
||||||
"fx": {"aoe":2,"beam":4,"buff":2,"devour":3,"displace":29,"dodge":6,"ember":2,"garrisonGrant":1,"link":3,"mote":71,"overload":3,"revive":1,"reweave":3,"riposte":2,"ripple":2,"sentry":5,"shadowWeave":19,"shield":4,"snow":1,"summon":1,"wake":4},
|
"fx": {"aoe":2,"beam":4,"buff":2,"devour":3,"displace":29,"dodge":6,"ember":2,"garrisonGrant":1,"link":3,"mote":71,"overload":3,"revive":1,"reweave":3,"riposte":2,"ripple":2,"sentry":5,"shadowWeave":19,"shield":4,"snow":1,"summon":1,"wake":4},
|
||||||
"snaps": "10b0fc57 1879e30f 44354394 dddd1d0a 627a31ff cf3c19d7 47582a23 87c2e2e7 2d0f5158 d9b586e6 18b235f7 fdb8d063 12d39f7e f01978fd f7e457a8 eb5e327f fccdb560 79c7c5d0 4f791ba7"
|
"snaps": "10b0fc57 1879e30f 44354394 dddd1d0a 627a31ff cf3c19d7 47582a23 87c2e2e7 2d0f5158 d9b586e6 18b235f7 fdb8d063 12d39f7e f01978fd f7e457a8 eb5e327f fccdb560 79c7c5d0 4f791ba7"
|
||||||
},
|
},
|
||||||
@@ -2544,7 +2544,7 @@
|
|||||||
"enemy_9008_acbunn": [1,1,0,1338,13824]
|
"enemy_9008_acbunn": [1,1,0,1338,13824]
|
||||||
},
|
},
|
||||||
"hooks": [1,79,5461,554,554,1344,1361,1606,69,66,32,28,0,17722,2148,41,59,0,1,76,50,1,6],
|
"hooks": [1,79,5461,554,554,1344,1361,1606,69,66,32,28,0,17722,2148,41,59,0,1,76,50,1,6],
|
||||||
"events": {"atk":674,"bounty":4,"deploy":18,"die":66,"dmg":1181,"fx":205,"heal":174,"layer":50,"skill":60,"spawn":73,"status":314},
|
"events": {"atk":674,"bounty":4,"deploy":18,"die":66,"dmg":1181,"engage":10,"fx":205,"heal":174,"layer":50,"skill":60,"spawn":73,"status":314},
|
||||||
"fx": {"aoe":5,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":4,"dodge":6,"explode":26,"extraAttack":12,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":40,"overload":1,"revive":1,"reweave":3,"riposte":2,"rockfall":1,"shieldBreak":2,"sleepGuard":4,"snow":1,"stone":1,"swordStorm":7,"telegraph":2,"zone":40},
|
"fx": {"aoe":5,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":4,"dodge":6,"explode":26,"extraAttack":12,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":40,"overload":1,"revive":1,"reweave":3,"riposte":2,"rockfall":1,"shieldBreak":2,"sleepGuard":4,"snow":1,"stone":1,"swordStorm":7,"telegraph":2,"zone":40},
|
||||||
"snaps": "875d082b 85a4bc0b 9625d9f6 7e68be43 ddfb3f30 824ba052 277e00a7 05c18f12 c69c93d5 804b666f d57f06c7 1c059a92 2e2f05e5 8a408670 bcd9bab3 325c0f56 9eeb4763 8b918a67 86550ca0"
|
"snaps": "875d082b 85a4bc0b 9625d9f6 7e68be43 ddfb3f30 824ba052 277e00a7 05c18f12 c69c93d5 804b666f d57f06c7 1c059a92 2e2f05e5 8a408670 bcd9bab3 325c0f56 9eeb4763 8b918a67 86550ca0"
|
||||||
},
|
},
|
||||||
@@ -2600,7 +2600,7 @@
|
|||||||
"enemy_9006_actoxi": [12,7,5,15242,193791]
|
"enemy_9006_actoxi": [12,7,5,15242,193791]
|
||||||
},
|
},
|
||||||
"hooks": [1,96,5274,684,684,2293,2404,3868,69,84,18,14,17,26981,327,76,64,12,1,104,11,6,1],
|
"hooks": [1,96,5274,684,684,2293,2404,3868,69,84,18,14,17,26981,327,76,64,12,1,104,11,6,1],
|
||||||
"events": {"atk":783,"bounty":2,"deploy":29,"die":72,"dmg":2378,"fx":135,"heal":1025,"layer":11,"leak":12,"skill":32,"spawn":80,"status":452},
|
"events": {"atk":783,"bounty":2,"deploy":29,"die":72,"dmg":2378,"engage":13,"fx":135,"heal":1025,"layer":11,"leak":12,"skill":32,"spawn":80,"status":452},
|
||||||
"fx": {"aoe":6,"beam":1,"bloodBattle":2,"burst":6,"crit":9,"displace":19,"dodge":1,"dollEnd":1,"downed":2,"ember":2,"explode":1,"garrison":11,"meltdown":4,"overload":2,"reborn":2,"revive":8,"steal":1,"substitute":6,"summon":1,"swap":4,"telegraph":2,"zone":44},
|
"fx": {"aoe":6,"beam":1,"bloodBattle":2,"burst":6,"crit":9,"displace":19,"dodge":1,"dollEnd":1,"downed":2,"ember":2,"explode":1,"garrison":11,"meltdown":4,"overload":2,"reborn":2,"revive":8,"steal":1,"substitute":6,"summon":1,"swap":4,"telegraph":2,"zone":44},
|
||||||
"snaps": "841229ba 78357365 0f64454f 0905a7a5 adb1558a 12265ef9 86732a88 1d688602 fccecf7b 75a68be4 ae48bfb4 a73a37e1 623579a6 7e4ca3de f0fed966 3ff2868e 0e35991d 9bdcb440"
|
"snaps": "841229ba 78357365 0f64454f 0905a7a5 adb1558a 12265ef9 86732a88 1d688602 fccecf7b 75a68be4 ae48bfb4 a73a37e1 623579a6 7e4ca3de f0fed966 3ff2868e 0e35991d 9bdcb440"
|
||||||
},
|
},
|
||||||
@@ -2672,7 +2672,7 @@
|
|||||||
"enemy_2085_skzjxd": [1,0,1,0,44931]
|
"enemy_2085_skzjxd": [1,0,1,0,44931]
|
||||||
},
|
},
|
||||||
"hooks": [1,166,6296,1642,1642,4394,4403,6834,123,155,45,40,0,24838,2741,133,119,32,1,127,0,0,2],
|
"hooks": [1,166,6296,1642,1642,4394,4403,6834,123,155,45,40,0,24838,2741,133,119,32,1,127,0,0,2],
|
||||||
"events": {"atk":2063,"bounty":4,"deploy":45,"die":123,"dmg":4403,"fx":135,"heal":178,"leak":32,"skill":85,"spawn":152,"status":957},
|
"events": {"atk":2063,"bounty":4,"deploy":45,"die":123,"dmg":4403,"engage":31,"fx":135,"heal":178,"leak":32,"skill":85,"spawn":152,"status":957},
|
||||||
"fx": {"blink":1,"crit":24,"displace":39,"dodge":2,"explode":5,"sp":6,"substitute":4,"summon":19,"swap":3,"teleport":9,"zone":23},
|
"fx": {"blink":1,"crit":24,"displace":39,"dodge":2,"explode":5,"sp":6,"substitute":4,"summon":19,"swap":3,"teleport":9,"zone":23},
|
||||||
"snaps": "a39ea9a8 894ae924 7bac6648 7d42567b 3eb2ab4e 3ab164c7 95348b36 0f17357c d58a493f fb05401c 709ec076 b9072b05 be569992 81553045 441b0a88 5c38623c 7ed8e02b 628e26a1 52aeca40 3789a121 f5ea4e29"
|
"snaps": "a39ea9a8 894ae924 7bac6648 7d42567b 3eb2ab4e 3ab164c7 95348b36 0f17357c d58a493f fb05401c 709ec076 b9072b05 be569992 81553045 441b0a88 5c38623c 7ed8e02b 628e26a1 52aeca40 3789a121 f5ea4e29"
|
||||||
},
|
},
|
||||||
@@ -2734,7 +2734,7 @@
|
|||||||
"enemy_2001_duckmi_2": [1,1,0,0,9331]
|
"enemy_2001_duckmi_2": [1,1,0,0,9331]
|
||||||
},
|
},
|
||||||
"hooks": [1,91,5571,429,416,885,1371,9297,69,74,11,8,10,21975,747,16,68,0,1,69,2,49,0],
|
"hooks": [1,91,5571,429,416,885,1371,9297,69,74,11,8,10,21975,747,16,68,0,1,69,2,49,0],
|
||||||
"events": {"atk":525,"bounty":4,"deploy":20,"die":74,"dmg":1186,"fx":122,"heal":239,"layer":2,"skill":19,"spawn":90,"status":1442},
|
"events": {"atk":525,"bounty":4,"deploy":20,"die":74,"dmg":1186,"engage":7,"fx":122,"heal":239,"layer":2,"skill":19,"spawn":90,"status":1442},
|
||||||
"fx": {"airstrike":4,"anchor":1,"burst":49,"dp":3,"expose":6,"garrison":2,"link":2,"snow":5,"summon":2,"summonStun":2,"talent":1,"wanted":45},
|
"fx": {"airstrike":4,"anchor":1,"burst":49,"dp":3,"expose":6,"garrison":2,"link":2,"snow":5,"summon":2,"summonStun":2,"talent":1,"wanted":45},
|
||||||
"snaps": "9faf7d66 a9734f34 a9f988b1 95d9e572 07f9937f 828c97ea 0414f9f5 d8ef07d8 3d72f489 2c55520d c28e71dc e5dc9e40 b395c094 75a018c5 1f188deb 4ffca9a0 93cc70bf 4169aca2 d23979c7"
|
"snaps": "9faf7d66 a9734f34 a9f988b1 95d9e572 07f9937f 828c97ea 0414f9f5 d8ef07d8 3d72f489 2c55520d c28e71dc e5dc9e40 b395c094 75a018c5 1f188deb 4ffca9a0 93cc70bf 4169aca2 d23979c7"
|
||||||
},
|
},
|
||||||
@@ -2789,7 +2789,7 @@
|
|||||||
"enemy_2034_sythef_2": [1,0,1,0,0]
|
"enemy_2034_sythef_2": [1,0,1,0,0]
|
||||||
},
|
},
|
||||||
"hooks": [1,165,6669,205,205,496,500,100,26,162,9,9,4,3662,276,15,134,132,1,27,1,1,0],
|
"hooks": [1,165,6669,205,205,496,500,100,26,162,9,9,4,3662,276,15,134,132,1,27,1,1,0],
|
||||||
"events": {"atk":291,"bounty":1,"deploy":31,"die":30,"dmg":488,"fx":170,"heal":58,"layer":1,"leak":132,"skill":18,"spawn":149,"status":387},
|
"events": {"atk":291,"bounty":1,"deploy":31,"die":30,"dmg":488,"engage":8,"fx":170,"heal":58,"layer":1,"leak":132,"skill":18,"spawn":149,"status":387},
|
||||||
"fx": {"blessing":1,"blink":2,"burst":1,"camouflage":2,"displace":1,"explode":72,"garrison":1,"garrisonGrant":2,"link":1,"snow":7,"sunBurst":3,"talent":2,"telegraph":2,"zone":73},
|
"fx": {"blessing":1,"blink":2,"burst":1,"camouflage":2,"displace":1,"explode":72,"garrison":1,"garrisonGrant":2,"link":1,"snow":7,"sunBurst":3,"talent":2,"telegraph":2,"zone":73},
|
||||||
"snaps": "6605881c 99d27c6c d2d45669 7e4fbc70 bcf82881 4959c1cf 07251748 47e1ea9c ef3fabd2 d150c862 3083e599 7e3dcbbd 7c7253a3 191cba19 fb761362 7766cc81 56e7cf0e 85fb9079 929affb4 6525eb41 e68dc459 0c416386 3305aebb"
|
"snaps": "6605881c 99d27c6c d2d45669 7e4fbc70 bcf82881 4959c1cf 07251748 47e1ea9c ef3fabd2 d150c862 3083e599 7e3dcbbd 7c7253a3 191cba19 fb761362 7766cc81 56e7cf0e 85fb9079 929affb4 6525eb41 e68dc459 0c416386 3305aebb"
|
||||||
},
|
},
|
||||||
@@ -2848,7 +2848,7 @@
|
|||||||
"enemy_1525_blkswb": [1,0,1,4227,26880]
|
"enemy_1525_blkswb": [1,0,1,4227,26880]
|
||||||
},
|
},
|
||||||
"hooks": [1,177,6417,702,694,819,922,115,77,172,15,15,80,9761,1199,38,142,91,1,81,0,0,0],
|
"hooks": [1,177,6417,702,694,819,922,115,77,172,15,15,80,9761,1199,38,142,91,1,81,0,0,0],
|
||||||
"events": {"atk":704,"bounty":3,"deploy":32,"die":81,"dmg":837,"fx":59,"heal":26,"leak":91,"skill":30,"spawn":163,"status":180},
|
"events": {"atk":704,"bounty":3,"deploy":32,"die":81,"dmg":837,"engage":12,"fx":59,"heal":26,"leak":91,"skill":30,"spawn":163,"status":180},
|
||||||
"fx": {"airstrike":26,"anchor":1,"appear":1,"blink":2,"copy":1,"disappear":1,"ember":1,"explode":1,"garrisonGrant":2,"heal":2,"link":1,"revive":4,"strike":6,"summon":2,"summonStun":2,"sunBurst":1,"sword":3,"telegraph":1,"zone":1},
|
"fx": {"airstrike":26,"anchor":1,"appear":1,"blink":2,"copy":1,"disappear":1,"ember":1,"explode":1,"garrisonGrant":2,"heal":2,"link":1,"revive":4,"strike":6,"summon":2,"summonStun":2,"sunBurst":1,"sword":3,"telegraph":1,"zone":1},
|
||||||
"snaps": "019a26cc 168013a9 838d9fe2 d6e37c3b 03ecc5c8 af794d9a bab801d7 d430ce64 545aa402 627f3cca 90dea475 f6fbcb03 eac8545b d32ae905 60789830 dc25a684 972cf188 1da714fe 79b82ec1 826d0e8e 866ff2a7 c4064e28"
|
"snaps": "019a26cc 168013a9 838d9fe2 d6e37c3b 03ecc5c8 af794d9a bab801d7 d430ce64 545aa402 627f3cca 90dea475 f6fbcb03 eac8545b d32ae905 60789830 dc25a684 972cf188 1da714fe 79b82ec1 826d0e8e 866ff2a7 c4064e28"
|
||||||
},
|
},
|
||||||
@@ -2908,7 +2908,7 @@
|
|||||||
"enemy_2017_csphts": [1,0,1,782,1260]
|
"enemy_2017_csphts": [1,0,1,782,1260]
|
||||||
},
|
},
|
||||||
"hooks": [1,100,6410,284,284,2293,2300,22249,54,92,94,94,0,10841,608,45,86,38,1,69,11,0,0],
|
"hooks": [1,100,6410,284,284,2293,2300,22249,54,92,94,94,0,10841,608,45,86,38,1,69,11,0,0],
|
||||||
"events": {"atk":526,"deploy":11,"die":54,"dmg":2300,"fx":182,"heal":62,"layer":11,"leak":38,"skill":188,"spawn":95,"status":875},
|
"events": {"atk":526,"deploy":11,"die":54,"dmg":2300,"engage":5,"fx":182,"heal":62,"layer":11,"leak":38,"skill":188,"spawn":95,"status":875},
|
||||||
"fx": {"dollEnd":1,"explode":1,"garrison":10,"layer":1,"lightning":68,"shield":8,"slash":32,"sp":50,"storm":4,"substitute":1,"telegraph":1,"weightless":5},
|
"fx": {"dollEnd":1,"explode":1,"garrison":10,"layer":1,"lightning":68,"shield":8,"slash":32,"sp":50,"storm":4,"substitute":1,"telegraph":1,"weightless":5},
|
||||||
"snaps": "94f68fc0 699b5957 74b1283d 3b83834f a3210271 89392766 01647ce4 ef4832d2 50b1d3df 464d3e8b 3b0659d2 ff5572c0 d04959d6 1a5f4ee2 aa11dd73 b2b4d570 01351b36 6d94c52d 6e684127 cbf7b7ff 35bcef0d b1048253"
|
"snaps": "94f68fc0 699b5957 74b1283d 3b83834f a3210271 89392766 01647ce4 ef4832d2 50b1d3df 464d3e8b 3b0659d2 ff5572c0 d04959d6 1a5f4ee2 aa11dd73 b2b4d570 01351b36 6d94c52d 6e684127 cbf7b7ff 35bcef0d b1048253"
|
||||||
},
|
},
|
||||||
@@ -2953,7 +2953,7 @@
|
|||||||
"enemy_9009_acfort": [1,0,1,7851,19348]
|
"enemy_9009_acfort": [1,0,1,7851,19348]
|
||||||
},
|
},
|
||||||
"hooks": [1,71,5700,366,366,1824,1824,0,31,65,30,31,0,3900,792,3,64,34,1,33,0,0,0],
|
"hooks": [1,71,5700,366,366,1824,1824,0,31,65,30,31,0,3900,792,3,64,34,1,33,0,0,0],
|
||||||
"events": {"atk":422,"bounty":3,"deploy":7,"die":31,"dmg":1823,"fx":45,"leak":34,"skill":64,"spawn":69,"status":304},
|
"events": {"atk":422,"bounty":3,"deploy":7,"die":31,"dmg":1823,"engage":4,"fx":45,"leak":34,"skill":64,"spawn":69,"status":304},
|
||||||
"fx": {"coin":8,"dilemma":2,"explode":2,"frostNova":25,"revive":2,"shield":1,"shieldBreak":1,"summon":1,"telegraph":3},
|
"fx": {"coin":8,"dilemma":2,"explode":2,"frostNova":25,"revive":2,"shield":1,"shieldBreak":1,"summon":1,"telegraph":3},
|
||||||
"snaps": "3dfc169e 33981103 b9e7dc10 e5c76cae 6c52ad96 4e2ae444 84eaae26 656ad352 8930f4ed cb10ab9f 8d62f572 2928e41b 928d6080 4b392947 cad850f5 88878813 87bd227a 00e4cae4 a630d784"
|
"snaps": "3dfc169e 33981103 b9e7dc10 e5c76cae 6c52ad96 4e2ae444 84eaae26 656ad352 8930f4ed cb10ab9f 8d62f572 2928e41b 928d6080 4b392947 cad850f5 88878813 87bd227a 00e4cae4 a630d784"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,24 @@ test('snapshot / event wire format (DESIGN §8.2)', () => {
|
|||||||
JSON.stringify(snap); // serialisable
|
JSON.stringify(snap); // serialisable
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('one ENGAGE per unit, on its first attack that hits an enemy (the client\'s 行动开始 voice)', () => {
|
||||||
|
const h = makeBattle({
|
||||||
|
defs: { chess: { t_guard: guard({ stats: { atk: 100, maxHp: 5000 } }) }, enemies: { enemy_walker: walker({ atk: 100, hp: 3000 }) } },
|
||||||
|
units: [{ chessId: 't_guard', row: 9, col: 5 }],
|
||||||
|
enemies: [{ key: 'enemy_walker' }], content: 'generic', fieldId: 'n:p1',
|
||||||
|
});
|
||||||
|
h.run(60); // the walker (0.5 tiles/s) reaches the guard at ~9 s and is attacked from then on
|
||||||
|
const id = h.unit('t_guard').id;
|
||||||
|
const engage = h.eventsOf(EV.ENGAGE);
|
||||||
|
assert.equal(engage.length, 1, 'one event for the whole battle');
|
||||||
|
assert.deepEqual(engage[0], ['engage', id]);
|
||||||
|
// it is emitted at the unit's first attack on an enemy — an earlier 命中的攻击 is impossible
|
||||||
|
const first = h.events.findIndex((e) => e[0] === 'engage');
|
||||||
|
const earlierAtk = h.events.slice(0, first).findIndex((e) => e[0] === 'atk' && e[1] === id);
|
||||||
|
assert.equal(earlierAtk, -1, 'no attack of that unit precedes it');
|
||||||
|
assert.ok(h.events.slice(first).some((e) => e[0] === 'atk' && e[1] === id), 'the attack it belongs to');
|
||||||
|
});
|
||||||
|
|
||||||
test('determinism: same seed ⇒ identical result and event stream; different seed may differ', () => {
|
test('determinism: same seed ⇒ identical result and event stream; different seed may differ', () => {
|
||||||
const ds = getDefaultSource();
|
const ds = getDefaultSource();
|
||||||
const tpl = ds.getWave('act1autochess_05');
|
const tpl = ds.getWave('act1autochess_05');
|
||||||
|
|||||||
+192
-2
@@ -6,7 +6,7 @@ import assert from 'node:assert/strict';
|
|||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { bgmKeyFor, resolveBgm, SfxLimiter, AudioManager, normalAttackSfx, installAudio, audio, combatTrackFor, COMBAT_TRACK_SWITCH_ROUND } from '../../public/js/audio.js';
|
import { bgmKeyFor, resolveBgm, SfxLimiter, AudioManager, normalAttackSfx, installAudio, audio, combatTrackFor, COMBAT_TRACK_SWITCH_ROUND, VoiceGate, resultSpeaker, resultVoiceSlot, VOICE_PRIORITY, VOICE_COOLDOWN_MS } from '../../public/js/audio.js';
|
||||||
import { mediaUrl } from '../../public/js/media.js';
|
import { mediaUrl } from '../../public/js/media.js';
|
||||||
import { PHASE } from '../../shared/constants.js';
|
import { PHASE } from '../../shared/constants.js';
|
||||||
|
|
||||||
@@ -170,6 +170,196 @@ describe('SfxLimiter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- operator battle voice (manifest audio.voice; official priorities, audio_data.json battleVoice) ---------
|
||||||
|
|
||||||
|
describe('operator battle voice', () => {
|
||||||
|
test('resultVoiceSlot: 完美作战 / 高难 / 漏怪 / 全灭', () => {
|
||||||
|
assert.equal(resultVoiceSlot({ perfect: true }), 'resultThree');
|
||||||
|
assert.equal(resultVoiceSlot({ perfect: true, hard: true }), 'resultFour', '绝境 / 终极 报 完成高难行动');
|
||||||
|
assert.equal(resultVoiceSlot({ perfect: false, leaked: 2, killed: 10, total: 12 }), 'resultTwo');
|
||||||
|
assert.equal(resultVoiceSlot({ perfect: false, leaked: 0, killed: 0, total: 12 }), 'resultLose');
|
||||||
|
assert.equal(resultVoiceSlot({ perfect: false, leaked: 0, killed: 5, total: 5 }), 'resultThree', 'no leak ⇒ the 完美 line');
|
||||||
|
assert.equal(resultVoiceSlot(), 'resultThree');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resultSpeaker: the operators of THAT battle (survivors first), never the field on screen', () => {
|
||||||
|
// `mine.unitsEnd` of the finished own battle: operators (char_*) and the board's summon pieces (token_*, no voice)
|
||||||
|
const mine = { unitsEnd: [
|
||||||
|
{ defId: 'char_fallen', alive: false },
|
||||||
|
{ defId: 'token_1001', alive: true },
|
||||||
|
{ defId: 'char_a', alive: true },
|
||||||
|
{ defId: 'char_b', alive: true },
|
||||||
|
] };
|
||||||
|
assert.equal(resultSpeaker(mine, () => 0), 'char_a');
|
||||||
|
assert.equal(resultSpeaker(mine, () => 0.999), 'char_b', 'a drawn operator, still only the ones standing');
|
||||||
|
assert.equal(resultSpeaker(mine, () => 0.4), 'char_a');
|
||||||
|
const drawn = new Set([0, 0.2, 0.4, 0.6, 0.8, 1].map((r) => resultSpeaker(mine, () => r)));
|
||||||
|
assert.deepEqual([...drawn].sort(), ['char_a', 'char_b'], 'a fallen operator or a summon never speaks');
|
||||||
|
// the whole squad fell: only then does a fallen operator report the result
|
||||||
|
const wiped = { unitsEnd: [{ defId: 'char_fallen', alive: false }, { defId: 'char_also', alive: false }] };
|
||||||
|
assert.equal(resultSpeaker(wiped, () => 0), 'char_fallen');
|
||||||
|
assert.equal(resultSpeaker(wiped, () => 0.999), 'char_also');
|
||||||
|
// no operator on that field at all — summons only, an empty list, or a payload without `unitsEnd` (an older
|
||||||
|
// server): no line. There is deliberately NO fallback to the operators currently tracked for the field on screen —
|
||||||
|
// that fallback is what made a watched team-mate's operator say the viewer's line (review on #73).
|
||||||
|
assert.equal(resultSpeaker({ unitsEnd: [{ defId: 'token_1', alive: true }] }, () => 0), null);
|
||||||
|
assert.equal(resultSpeaker({ unitsEnd: [] }), null);
|
||||||
|
assert.equal(resultSpeaker({}), null);
|
||||||
|
assert.equal(resultSpeaker(null), null);
|
||||||
|
assert.equal(resultSpeaker(undefined), null);
|
||||||
|
assert.equal(resultSpeaker({ unitsEnd: [{ alive: true }] }, () => 0), null, 'a unit without a defId');
|
||||||
|
assert.equal(resultSpeaker({ unitsEnd: [{ defId: 'enemy_1007_slime', alive: true }] }, () => 0), null, 'an enemy');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('VoiceGate: one line at a time, a global gap, per-unit cooldowns, higher priority takes over', () => {
|
||||||
|
assert.ok(VOICE_PRIORITY.start > VOICE_PRIORITY.skill1 && VOICE_PRIORITY.skill1 > VOICE_PRIORITY.place, 'the official order');
|
||||||
|
assert.ok(VOICE_PRIORITY.resultThree > VOICE_PRIORITY.skill1 && VOICE_PRIORITY.resultThree < VOICE_PRIORITY.faceEnemy);
|
||||||
|
assert.equal(VOICE_COOLDOWN_MS.skill1, 10000, 'official SKILL_ACTIVE 10 s');
|
||||||
|
const g = new VoiceGate({ gapMs: 1000 });
|
||||||
|
assert.equal(g.request('start', 1, 0), 'play');
|
||||||
|
g.start('start', 1, 0);
|
||||||
|
assert.equal(g.request('skill1', 2, 500), 'drop', 'one voice at a time (a lower priority waits)');
|
||||||
|
assert.equal(g.request('start', 3, 500), 'drop', 'the same priority does not interrupt');
|
||||||
|
assert.equal(g.request('faceEnemy', 3, 500), 'drop', '90 < 100 + margin');
|
||||||
|
g.release();
|
||||||
|
assert.equal(g.request('place', 1, 900), 'drop', 'the global gap is not over');
|
||||||
|
assert.equal(g.request('place', 1, 1000), 'play');
|
||||||
|
g.release();
|
||||||
|
const g2 = new VoiceGate({ gapMs: 0 });
|
||||||
|
assert.equal(g2.request('skill1', 'u1', 0), 'play');
|
||||||
|
g2.start('skill1', 'u1', 0);
|
||||||
|
g2.release();
|
||||||
|
assert.equal(g2.request('skill1', 'u1', 5000), 'drop', 'the unit cooldown');
|
||||||
|
assert.equal(g2.request('skill1', 'u2', 5000), 'play', 'another unit is unaffected');
|
||||||
|
g2.release();
|
||||||
|
assert.equal(g2.request('skill1', 'u1', 10000), 'play', 'cooldown over');
|
||||||
|
g2.release();
|
||||||
|
g2.reset();
|
||||||
|
assert.equal(g2.request('skill1', 'u1', 10001), 'play', 'a new battle inherits no cooldown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AudioManager.voice: manifest slots (a drawn array), the gate, and the battle events that drive them', async () => {
|
||||||
|
const fw = fakeWindow();
|
||||||
|
const origFetch = globalThis.fetch;
|
||||||
|
const urls = [];
|
||||||
|
globalThis.fetch = async (u) => { urls.push(u); return { ok: true, arrayBuffer: async () => new ArrayBuffer(8) }; };
|
||||||
|
try {
|
||||||
|
const vm = { audio: { sfx: { ui: {}, battle: {}, units: {} }, voice: {
|
||||||
|
char_a: { start: '/v/a_start.mp3', place: ['/v/a_p1.mp3', '/v/a_p2.mp3'], skill3: '/v/a_s3.mp3', faceEnemy: '/v/a_face.mp3' },
|
||||||
|
char_b: { place: '/v/b_p1.mp3', skill1: '/v/b_s1.mp3' },
|
||||||
|
} } };
|
||||||
|
const a = new AudioManager({ win: fw.win, getManifest: () => vm });
|
||||||
|
a.voiceGate = new VoiceGate({ gapMs: 0 }); // the gap itself is covered above
|
||||||
|
a.install();
|
||||||
|
assert.equal(a.voice('char_a', 'place'), false, 'locked: nothing is requested');
|
||||||
|
assert.equal(urls.length, 0);
|
||||||
|
fw.fire('pointerdown');
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voice('char_a', 'start', { unitKey: 1 }), true);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.ok(asked(urls, '/v/a_start.mp3'));
|
||||||
|
assert.equal(a.voice('char_b', 'place', { unitKey: 2 }), false, '部署 cannot interrupt 行动出发');
|
||||||
|
assert.equal(a.voice('char_a', 'faceEnemy', { unitKey: 3 }), false, '接敌 cannot either');
|
||||||
|
a._stopVoice();
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 1 }), true, 'the channel is free again');
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.ok(asked(urls, '/v/a_p1.mp3') || asked(urls, '/v/a_p2.mp3'), 'a drawn 部署 line');
|
||||||
|
assert.equal(a.voice('char_a', 'nope', { unitKey: 1 }), false, 'unknown slot');
|
||||||
|
assert.equal(a.voice('char_zz', 'place', { unitKey: 1 }), false, 'unknown operator');
|
||||||
|
a._stopVoice(); a.voiceGate.reset();
|
||||||
|
// the battle events drive the lines: 行动出发 (the first deploy), 部署 (the rest), 行动开始, 作战中N
|
||||||
|
a.setFieldUnits([{ id: 11, side: 'ally', spine: 'char_a', kind: 'op', skillIndex: 2 }, { id: 12, side: 'ally', spine: 'char_b', kind: 'op' }]);
|
||||||
|
a.handleBattleEvents([['deploy', 11]]);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_start.mp3', 'the first operator deployed says 行动出发');
|
||||||
|
a.handleBattleEvents([['deploy', 12]]);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_start.mp3', 'a 部署 line never interrupts it (lower priority)');
|
||||||
|
a._stopVoice(); a.voiceGate.reset();
|
||||||
|
a.handleBattleEvents([['engage', 11], ['skill', 12, 1]]);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_face.mp3', '首次接敌 ⇒ 行动开始 (a 作战中 line never interrupts it)');
|
||||||
|
a._stopVoice(); a.voiceGate.reset();
|
||||||
|
a.handleBattleEvents([['skill', 11, 1]]);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_s3.mp3', '作战中N follows the unit skillIndex (2 ⇒ 作战中3)');
|
||||||
|
a._stopVoice(); a.voiceGate.reset();
|
||||||
|
a.handleBattleEvents([['skill', 12, 1]]);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/b_s1.mp3', 'a unit without skillIndex falls back to 作战中1');
|
||||||
|
a._stopVoice(); a.voiceGate.reset();
|
||||||
|
a.setFieldUnits([{ id: 21, side: 'ally', spine: 'char_a', kind: 'op' }]);
|
||||||
|
a.handleBattleEvents([['deploy', 21]]);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_start.mp3', 'a new battle starts with 行动出发 again');
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = origFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('voice: a stale callback never frees the channel the newest line holds (review on #73)', async () => {
|
||||||
|
const fw = fakeWindow();
|
||||||
|
const origFetch = globalThis.fetch;
|
||||||
|
// holds named URLs open: the first request for one waits on the promise in `held`, later ones (the manager caches a
|
||||||
|
// decoded URL) do not. `fail` answers 404, i.e. `_buffer` resolves null.
|
||||||
|
const held = new Map();
|
||||||
|
const fail = new Set();
|
||||||
|
globalThis.fetch = async (u) => {
|
||||||
|
if (fail.has(u)) return { ok: false, status: 404 };
|
||||||
|
const h = held.get(u);
|
||||||
|
if (h) { held.delete(u); await h; } // the manager caches per URL, so only the first call waits
|
||||||
|
return { ok: true, arrayBuffer: async () => new ArrayBuffer(8) };
|
||||||
|
};
|
||||||
|
const tick = () => new Promise((r) => setTimeout(r, 10));
|
||||||
|
try {
|
||||||
|
const vm = { audio: { sfx: { ui: {}, battle: {}, units: {} }, voice: {
|
||||||
|
char_a: { place: '/v/a_p1.mp3', start: '/v/a_start.mp3', faceEnemy: '/v/a_face.mp3', select: '/v/a_sel.mp3' },
|
||||||
|
} } };
|
||||||
|
const a = new AudioManager({ win: fw.win, getManifest: () => vm });
|
||||||
|
a.voiceGate = new VoiceGate({ gapMs: 0 }); // the gap itself is covered above
|
||||||
|
a.install();
|
||||||
|
fw.fire('pointerdown');
|
||||||
|
await tick();
|
||||||
|
// 部署 (20) starts with its LOAD HELD OPEN, so 行动出发 (100) takes the channel over while it is still loading
|
||||||
|
let openA;
|
||||||
|
held.set('/v/a_p1.mp3', new Promise((r) => { openA = r; }));
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 1 }), true);
|
||||||
|
assert.equal(a.voice('char_a', 'start', { unitKey: 2 }), true, 'a higher priority line takes the channel over');
|
||||||
|
await tick();
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_start.mp3', 'the replacement is on air');
|
||||||
|
openA(); // the preempted line's load finally finishes
|
||||||
|
await tick();
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_start.mp3', 'a stale callback never starts its own line');
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 3 }), false, 'and never freed the channel it lost');
|
||||||
|
|
||||||
|
// the safety timer / onended of a line that was STOPPED must not free the line that replaced it either
|
||||||
|
const ringing = a.voiceNode; // 行动出发 on air
|
||||||
|
a._stopVoice(); // the stop path releases the gate itself and moves the token on
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 4 }), true, 'the channel is free again');
|
||||||
|
await tick();
|
||||||
|
const now = a.voiceNode;
|
||||||
|
assert.equal(now?.url, '/v/a_p1.mp3');
|
||||||
|
ringing.src.onended?.(); // the stopped line's own end lands late (or its safety timer fires)
|
||||||
|
ringing.src.onended?.(); // …and a second time, like timer + onended both firing
|
||||||
|
await tick();
|
||||||
|
assert.equal(a.voiceNode, now, 'the line on air is untouched');
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 5 }), false, 'and its channel is still held');
|
||||||
|
|
||||||
|
// a load that FAILS after the takeover (404 ⇒ null buffer): same rule
|
||||||
|
fail.add('/v/a_sel.mp3');
|
||||||
|
a._stopVoice();
|
||||||
|
assert.equal(a.voice('char_a', 'select', { unitKey: 6 }), true);
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 7 }), true, 'a higher priority line takes over while it loads');
|
||||||
|
await tick();
|
||||||
|
assert.equal(a.voiceNode?.url, '/v/a_p1.mp3');
|
||||||
|
await tick();
|
||||||
|
assert.equal(a.voice('char_a', 'place', { unitKey: 8 }), false, 'the failed load kept out of the newest line\'s way');
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = origFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ---- fake Web Audio -------------------------------------------------------------------------------------
|
// ---- fake Web Audio -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function fakeWindow() {
|
function fakeWindow() {
|
||||||
@@ -209,7 +399,7 @@ describe('AudioManager', () => {
|
|||||||
assert.equal(a.unit('char_x', 'attack', 1), false);
|
assert.equal(a.unit('char_x', 'attack', 1), false);
|
||||||
a.handleBattleEvents([['atk', 1, 2, 'arrow'], 'junk', null]);
|
a.handleBattleEvents([['atk', 1, 2, 'arrow'], 'junk', null]);
|
||||||
a.setVolumes({ bgm: 5, sfx: -1, muted: true });
|
a.setVolumes({ bgm: 5, sfx: -1, muted: true });
|
||||||
assert.deepEqual(a.volumes, { bgm: 1, sfx: 0, muted: true });
|
assert.deepEqual(a.volumes, { bgm: 1, sfx: 0, voice: 0.8, muted: true });
|
||||||
assert.equal(a.unlocked, false);
|
assert.equal(a.unlocked, false);
|
||||||
});
|
});
|
||||||
test('unlocks on the first gesture, then plays BGM and SFX from the manifest', async () => {
|
test('unlocks on the first gesture, then plays BGM and SFX from the manifest', async () => {
|
||||||
|
|||||||
@@ -515,8 +515,9 @@ describe('keyboard & settings', () => {
|
|||||||
});
|
});
|
||||||
test('sanitizeSettings', () => {
|
test('sanitizeSettings', () => {
|
||||||
assert.deepEqual(sanitizeSettings(null), { ...DEFAULT_SETTINGS });
|
assert.deepEqual(sanitizeSettings(null), { ...DEFAULT_SETTINGS });
|
||||||
assert.deepEqual(sanitizeSettings({ bgm: 3, sfx: -1, muted: 'yes', damageNumbers: false, quality: 'ultra' }),
|
assert.deepEqual(sanitizeSettings({ bgm: 3, sfx: -1, voice: 2, muted: 'yes', damageNumbers: false, quality: 'ultra' }),
|
||||||
{ bgm: 1, sfx: 0, muted: false, damageNumbers: false, quality: 'high' });
|
{ bgm: 1, sfx: 0, voice: 1, muted: false, damageNumbers: false, quality: 'high' });
|
||||||
|
assert.equal(sanitizeSettings({ bgm: 0.5 }).voice, DEFAULT_SETTINGS.voice, 'a saved profile without `voice` gets the default');
|
||||||
assert.equal(sanitizeSettings({ bgm: 0.333 }).bgm, 0.33);
|
assert.equal(sanitizeSettings({ bgm: 0.333 }).bgm, 0.33);
|
||||||
assert.equal(sanitizeSettings({ quality: 'low' }).quality, 'low');
|
assert.equal(sanitizeSettings({ quality: 'low' }).quality, 'low');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -268,3 +268,91 @@ export function resolveSpec(spec, bank) {
|
|||||||
if (spec?.bank) return bank(spec.bank);
|
if (spec?.bank) return bank(spec.bank);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- operator battle voice (excel/charword_table.json) -------------------------------------------------
|
||||||
|
//
|
||||||
|
// Every playable operator has official battle lines (行动出发 / 行动开始 / 选中干员 / 部署 / 作战中 / 编入队伍 /
|
||||||
|
// 任命队长 / 结算 / 干员报到) in `voice_cn/<charId>/cn_<n>.mp3` — JP: `voice/`, EN: `voice_en/`, KR: `voice_kr/`,
|
||||||
|
// the same file names in every dump, only the folder differs. charword_table.json's `placeType` says when the game
|
||||||
|
// plays each line; its `voiceAsset` is the path under the dump.
|
||||||
|
|
||||||
|
/** Voice dump folder per language (under sound_beta_2). */
|
||||||
|
export const VOICE_DIRS = Object.freeze({ cn: 'voice_cn', jp: 'voice', en: 'voice_en', kr: 'voice_kr' });
|
||||||
|
|
||||||
|
/** Official `placeType` → the manifest's voice slot (public/js/audio.js VOICE_PRIORITY / VOICE_COOLDOWN_MS). */
|
||||||
|
export const VOICE_SLOTS = Object.freeze({
|
||||||
|
BATTLE_START: 'start', // 行动出发: 开战
|
||||||
|
BATTLE_FACE_ENEMY: 'faceEnemy', // 行动开始: 首次接敌
|
||||||
|
BATTLE_SELECT: 'select', // 选中干员1/2
|
||||||
|
BATTLE_PLACE: 'place', // 部署1/2
|
||||||
|
BATTLE_SKILL_1: 'skill1', // 作战中1-4: the equipped skill's own slot
|
||||||
|
BATTLE_SKILL_2: 'skill2',
|
||||||
|
BATTLE_SKILL_3: 'skill3',
|
||||||
|
BATTLE_SKILL_4: 'skill4',
|
||||||
|
SQUAD: 'squad', // 编入队伍
|
||||||
|
SQUAD_FIRST: 'squadFirst', // 任命队长
|
||||||
|
FOUR_STAR: 'resultFour', // 完成高难行动
|
||||||
|
THREE_STAR: 'resultThree', // 3星结束行动 (完美作战)
|
||||||
|
TWO_STAR: 'resultTwo', // 非3星结束行动
|
||||||
|
LOSE: 'resultLose', // 行动失败
|
||||||
|
GACHA: 'gacha', // 干员报到
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The slots a running battle can actually request — the only ones `public/js/audio.js` ever asks for (the 休整期 is
|
||||||
|
* silent, so nothing else is played): 行动出发 start, 首次接敌 faceEnemy, 作战中1-4 skillN, 部署 place (the deploy
|
||||||
|
* events) and 选中干员 select (the detail panel, behind its combat flag), plus the settlement lines
|
||||||
|
* resultFour / resultThree / resultTwo / resultLose (public/js/screens/game.js onResult).
|
||||||
|
* `buildPlan` plans these by default; `--voice-all` widens it to every slot of VOICE_SLOTS.
|
||||||
|
*/
|
||||||
|
export const VOICE_BATTLE_SLOTS = Object.freeze(['start', 'faceEnemy', 'select', 'place',
|
||||||
|
'skill1', 'skill2', 'skill3', 'skill4', 'resultFour', 'resultThree', 'resultTwo', 'resultLose']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slots no battle plays: the lines the official client uses in its own 养成 / 编队 UI (干员报到 gacha, 编入队伍 squad,
|
||||||
|
* 任命队长 squadFirst). `test/docs-consistency.test.js` proves the client never asks for one, so planning them only
|
||||||
|
* makes every `npm run assets` download 360 files (19.3 MB, CN dub) that no player will ever hear — they are left out
|
||||||
|
* unless `--voice-all` is passed. 部署 `place` and 选中干员 `select` deliberately stay in: the official client groups
|
||||||
|
* them with the prep lines, but a battle does play them (the deploy events; the detail panel behind its combat flag).
|
||||||
|
*/
|
||||||
|
export const VOICE_PREP_SLOTS = Object.freeze(['gacha', 'squad', 'squadFirst']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index charword_table.json into per-character voice slots, in voiceIndex order (one slot may have several lines).
|
||||||
|
* Only the base word key (`wordKey === charId`) is used: the dump has no folder for a skin variant's word key
|
||||||
|
* (`char_x_ita`, `char_x_epoque#28`, …) — those files simply do not exist upstream.
|
||||||
|
* @param {any} charword parsed excel/charword_table.json
|
||||||
|
* @param {string} [lang] voiceId prefix — the zh_CN table carries the `CN_*` lines; the other dubs share the numbering
|
||||||
|
* @param {Iterable<string>|null} [only] slot names to keep; null/omitted keeps every slot (VOICE_BATTLE_SLOTS is what
|
||||||
|
* the plan uses by default, so a battle's own lines are planned and the prep-only ones are not)
|
||||||
|
* @returns {Map<string, Record<string, string[]>>} charId → slot → voiceAsset ('char_263_skadi/CN_023')
|
||||||
|
*/
|
||||||
|
export function indexVoice(charword, lang = 'CN', only = null) {
|
||||||
|
const keep = only ? new Set(only) : null;
|
||||||
|
const out = new Map();
|
||||||
|
const words = charword?.charWords;
|
||||||
|
if (!words || typeof words !== 'object') return out;
|
||||||
|
/** @type {Map<string, Map<string, Map<string, {index:number, asset:string}>>>} */
|
||||||
|
const seen = new Map();
|
||||||
|
for (const e of Object.values(words)) {
|
||||||
|
if (!e || typeof e !== 'object') continue;
|
||||||
|
const charId = e.charId, slot = VOICE_SLOTS[e.placeType], vid = e.voiceId;
|
||||||
|
if (!charId || !slot || (keep && !keep.has(slot)) || typeof vid !== 'string' || !vid.startsWith(`${lang}_`)) continue;
|
||||||
|
if (e.wordKey !== charId) continue;
|
||||||
|
if (typeof e.voiceAsset !== 'string' || !e.voiceAsset) continue;
|
||||||
|
if (!seen.has(charId)) seen.set(charId, new Map());
|
||||||
|
const slots = seen.get(charId);
|
||||||
|
if (!slots.has(slot)) slots.set(slot, new Map());
|
||||||
|
// the same line can be listed twice (an operator's 升变 / alt records): keep its lowest voiceIndex, once
|
||||||
|
const m = slots.get(slot);
|
||||||
|
const index = Number.isFinite(e.voiceIndex) ? e.voiceIndex : 0;
|
||||||
|
const prev = m.get(vid);
|
||||||
|
if (!prev || index < prev.index) m.set(vid, { index, asset: e.voiceAsset });
|
||||||
|
}
|
||||||
|
for (const [charId, slots] of seen) {
|
||||||
|
const rec = {};
|
||||||
|
for (const [slot, m] of slots) rec[slot] = [...m.values()].sort((a, b) => a.index - b.index).map((x) => x.asset);
|
||||||
|
out.set(charId, rec);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
+15
-5
@@ -1,6 +1,7 @@
|
|||||||
// Cached upstream JSON indexes needed by the asset pipeline:
|
// Cached upstream JSON indexes needed by the asset pipeline:
|
||||||
// .cache/gamedata/excel/audio_data.json (Kengxxiao/ArknightsGameData, zh_CN)
|
// .cache/gamedata/excel/audio_data.json (Kengxxiao/ArknightsGameData, zh_CN)
|
||||||
// .cache/ark-models/models_data.json (isHarryh/Ark-Models enemy Spine index)
|
// .cache/gamedata/excel/charword_table.json (same repo: the operators' voice slots and voice assets)
|
||||||
|
// .cache/ark-models/models_data.json (isHarryh/Ark-Models enemy Spine index)
|
||||||
// Downloaded once when missing (or with --refresh-index), then reused.
|
// Downloaded once when missing (or with --refresh-index), then reused.
|
||||||
|
|
||||||
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
||||||
@@ -46,10 +47,10 @@ export async function cachedJson({ cacheFile, url, refresh = false, offline = fa
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load audio_data.json (official) and Ark-Models models_data.json.
|
* Load audio_data.json (official), charword_table.json (official voice slots) and Ark-Models models_data.json.
|
||||||
* @param {string} root project root
|
* @param {string} root project root
|
||||||
* @param {{refresh?:boolean, offline?:boolean, log?:(m:string)=>void}} [opts]
|
* @param {{refresh?:boolean, offline?:boolean, log?:(m:string)=>void}} [opts]
|
||||||
* @returns {Promise<{ audioData: any, modelsData: any }>}
|
* @returns {Promise<{ audioData: any, modelsData: any, charword: any }>}
|
||||||
*/
|
*/
|
||||||
export async function loadIndexes(root, opts = {}) {
|
export async function loadIndexes(root, opts = {}) {
|
||||||
const audioData = await cachedJson({
|
const audioData = await cachedJson({
|
||||||
@@ -59,6 +60,15 @@ export async function loadIndexes(root, opts = {}) {
|
|||||||
offline: opts.offline,
|
offline: opts.offline,
|
||||||
log: opts.log,
|
log: opts.log,
|
||||||
});
|
});
|
||||||
|
// 11 MB, and raw.githubusercontent stalls on it often: the jsDelivr mirror cachedJson() falls back to is the
|
||||||
|
// reliable path (the operator battle voice needs it, plan.mjs indexVoice).
|
||||||
|
const charword = await cachedJson({
|
||||||
|
cacheFile: join(root, '.cache', 'gamedata', 'excel', 'charword_table.json'),
|
||||||
|
url: RAW.gamedata + 'excel/charword_table.json',
|
||||||
|
refresh: opts.refresh,
|
||||||
|
offline: opts.offline,
|
||||||
|
log: opts.log,
|
||||||
|
});
|
||||||
const modelsData = await cachedJson({
|
const modelsData = await cachedJson({
|
||||||
cacheFile: join(root, '.cache', 'ark-models', 'models_data.json'),
|
cacheFile: join(root, '.cache', 'ark-models', 'models_data.json'),
|
||||||
url: RAW.arkModels + 'models_data.json',
|
url: RAW.arkModels + 'models_data.json',
|
||||||
@@ -66,5 +76,5 @@ export async function loadIndexes(root, opts = {}) {
|
|||||||
offline: opts.offline,
|
offline: opts.offline,
|
||||||
log: opts.log,
|
log: opts.log,
|
||||||
});
|
});
|
||||||
return { audioData, modelsData };
|
return { audioData, modelsData, charword };
|
||||||
}
|
}
|
||||||
+53
-3
@@ -20,10 +20,16 @@
|
|||||||
|
|
||||||
import { RAW, joinUrl, safeName, urlBase, urlDir } from './sources.mjs';
|
import { RAW, joinUrl, safeName, urlBase, urlDir } from './sources.mjs';
|
||||||
import { kindOf } from './formats.mjs';
|
import { kindOf } from './formats.mjs';
|
||||||
import { pickUnitSfx, UI_SFX, BATTLE_SFX, resolveSpec } from './audio.mjs';
|
import { pickUnitSfx, UI_SFX, BATTLE_SFX, resolveSpec, indexVoice, VOICE_DIRS, VOICE_BATTLE_SLOTS } from './audio.mjs';
|
||||||
import { literal } from './manifest.mjs';
|
import { literal } from './manifest.mjs';
|
||||||
import { EMOTE_CATALOG } from '../../shared/constants.js';
|
import { EMOTE_CATALOG } from '../../shared/constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The voice slots are read from the zh_CN charword table, whose voiceIds are always `CN_*`: the other dubs
|
||||||
|
* (`--voice-lang=jp|en|kr`) share the very same slot numbering and file names, only the dump folder differs.
|
||||||
|
*/
|
||||||
|
const VOICE_ID_LANG = 'CN';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enemies whose Spine no community dump carries: the web model is another enemy's (research 07 §5.6). Their official
|
* Enemies whose Spine no community dump carries: the web model is another enemy's (research 07 §5.6). Their official
|
||||||
* models come from the local client only (tools/local-extract ENEMY_SPINES): `localEnemySpines` adds them as the
|
* models come from the local client only (tools/local-extract ENEMY_SPINES): `localEnemySpines` adds them as the
|
||||||
@@ -127,6 +133,14 @@ function leaf(...alts) {
|
|||||||
return list.length ? { alts: list } : null;
|
return list.length ? { alts: list } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** One operator voice line: charword voiceAsset ('char_263_skadi/CN_023') → `audio/voice/<lang>/<charId>/cn_023.mp3`. */
|
||||||
|
function voiceAlt(asset, lang) {
|
||||||
|
const [charId, voiceId] = String(asset).split('/');
|
||||||
|
if (!charId || !voiceId || !/^[a-z0-9_]+$/i.test(charId) || !/^[a-z]{2}_\d+$/i.test(voiceId)) return null;
|
||||||
|
const file = `${charId}/${voiceId.toLowerCase()}.mp3`;
|
||||||
|
return alt(`audio/voice/${lang}/${file}`, joinUrl(RAW.aa2voice, `${VOICE_DIRS[lang]}/${file}`));
|
||||||
|
}
|
||||||
|
|
||||||
/** Sound path under sound_beta_2 → alternative under public/assets/audio/<sub>. */
|
/** Sound path under sound_beta_2 → alternative under public/assets/audio/<sub>. */
|
||||||
function soundAlt(path, sub = 'sfx') {
|
function soundAlt(path, sub = 'sfx') {
|
||||||
const rel = `audio/${sub}/` + path.split('/').map(safeName).join('/');
|
const rel = `audio/${sub}/` + path.split('/').map(safeName).join('/');
|
||||||
@@ -251,6 +265,11 @@ export function collectEnemyIds({ assets07, enemies05, maps05, ops03 }) {
|
|||||||
* @param {any} p.maps05 docs/research/05-maps.json
|
* @param {any} p.maps05 docs/research/05-maps.json
|
||||||
* @param {ReturnType<import('./audio.mjs').indexAudio>} p.audio indexed audio_data.json
|
* @param {ReturnType<import('./audio.mjs').indexAudio>} p.audio indexed audio_data.json
|
||||||
* @param {any} p.modelsData Ark-Models models_data.json
|
* @param {any} p.modelsData Ark-Models models_data.json
|
||||||
|
* @param {any} [p.charword] parsed excel/charword_table.json — the operators' official voice slots (voice)
|
||||||
|
* @param {string} [p.voiceLang] voice dump to plan: cn (default) | jp | en | kr
|
||||||
|
* @param {Iterable<string>|null} [p.voiceSlots] which voice slots to plan: VOICE_BATTLE_SLOTS (default) plans only the
|
||||||
|
* lines a battle can play, null plans every slot of audio.mjs VOICE_SLOTS (`--voice-all`). The prep-only slots
|
||||||
|
* (干员报到 / 编入队伍 / 任命队长) are never requested by the client and cost 360 files / 19.3 MB of downloads.
|
||||||
* @param {string[]} [p.extraEnemyIds] more enemy ids that can spawn (e.g. keys of data/enemies.json)
|
* @param {string[]} [p.extraEnemyIds] more enemy ids that can spawn (e.g. keys of data/enemies.json)
|
||||||
* @param {string[]} [p.extraTokenIds] more token ids (e.g. token_* keys of data/tokens.json)
|
* @param {string[]} [p.extraTokenIds] more token ids (e.g. token_* keys of data/tokens.json)
|
||||||
* @param {Record<string,string>} [p.extraHandbook] enemyId → handbook/model id (e.g. from data/bosses.json)
|
* @param {Record<string,string>} [p.extraHandbook] enemyId → handbook/model id (e.g. from data/bosses.json)
|
||||||
@@ -259,7 +278,9 @@ export function collectEnemyIds({ assets07, enemies05, maps05, ops03 }) {
|
|||||||
* gets `spineLocal` = { group: 'spine/enemy/<id>', ...meta } beside its web `spine`
|
* gets `spineLocal` = { group: 'spine/enemy/<id>', ...meta } beside its web `spine`
|
||||||
* @returns {{ template: any, models: Map<string, any>, notes: string[] }}
|
* @returns {{ template: any, models: Map<string, any>, notes: string[] }}
|
||||||
*/
|
*/
|
||||||
export function buildPlan({ assets07, ops03, enemies05, maps05, audio, modelsData, extraEnemyIds = [], extraTokenIds = [], extraHandbook = {}, localEnemySpines = {} }) {
|
export function buildPlan({ assets07, ops03, enemies05, maps05, audio, modelsData, charword = null, voiceLang = 'cn',
|
||||||
|
voiceSlots = VOICE_BATTLE_SLOTS,
|
||||||
|
extraEnemyIds = [], extraTokenIds = [], extraHandbook = {}, localEnemySpines = {} }) {
|
||||||
const notes = [];
|
const notes = [];
|
||||||
/** @type {Map<string, any>} */
|
/** @type {Map<string, any>} */
|
||||||
const models = new Map();
|
const models = new Map();
|
||||||
@@ -533,9 +554,38 @@ export function buildPlan({ assets07, ops03, enemies05, maps05, audio, modelsDat
|
|||||||
const sfxBattle = {};
|
const sfxBattle = {};
|
||||||
for (const [name, spec] of Object.entries(BATTLE_SFX)) { const l = soundLeaf(resolveSpec(spec, audio.bank)); if (l) sfxBattle[name] = l; else notes.push(`battle SFX ${name}: no sound`); }
|
for (const [name, spec] of Object.entries(BATTLE_SFX)) { const l = soundLeaf(resolveSpec(spec, audio.bank)); if (l) sfxBattle[name] = l; else notes.push(`battle SFX ${name}: no sound`); }
|
||||||
|
|
||||||
|
// --- 干员战斗语音 (excel/charword_table.json → audio.voice) ---------------------------------------------
|
||||||
|
// The official lines of every operator the mode can field, for the slots a battle can actually play: 行动出发 start /
|
||||||
|
// 行动开始 faceEnemy / 选中干员 select / 部署 place / 作战中1-4 skillN / 结算 result* (charword `placeType`,
|
||||||
|
// audio.mjs VOICE_BATTLE_SLOTS). A slot with several lines stays an array — the client draws one at random
|
||||||
|
// (public/js/audio.js voice). Operators without official battle voice keep no entry at all: the 17 预备干员
|
||||||
|
// (char_60x_c*, char_617_sharp2) and the mode's own 盟约·辅助干员 (char_616_pithst).
|
||||||
|
// The three prep-only slots (干员报到 gacha / 编入队伍 squad / 任命队长 squadFirst) are NOT planned by default: the
|
||||||
|
// client never requests them, and downloading them adds 360 files / 19.3 MB to every `npm run assets` — pass --voice-all for
|
||||||
|
// the complete official set (`voiceSlots: null`, reviewer note on the voice PR).
|
||||||
|
const voice = {};
|
||||||
|
for (const [charId, slots] of indexVoice(charword, VOICE_ID_LANG, voiceSlots)) {
|
||||||
|
if (!chars[charId]) continue; // only the operators this game can field (the 138-pool charIds)
|
||||||
|
const v = {};
|
||||||
|
for (const [slot, assets] of Object.entries(slots)) {
|
||||||
|
// one leaf per line (部署1 / 部署2 …): an array stays an array so the client can draw one — chaining them as
|
||||||
|
// alternatives of a single leaf would keep only the first line that landed on disk.
|
||||||
|
const lines = assets.map((a) => leaf(voiceAlt(a, voiceLang))).filter(Boolean);
|
||||||
|
if (!lines.length) continue;
|
||||||
|
v[slot] = lines.length === 1 ? lines[0] : lines;
|
||||||
|
}
|
||||||
|
if (Object.keys(v).length) voice[charId] = v;
|
||||||
|
}
|
||||||
|
if (!Object.keys(voice).length) notes.push('battle voice: charword_table.json has no slots (index missing?)');
|
||||||
|
|
||||||
const template = {
|
const template = {
|
||||||
chars, enemies, tokens, bonds, items, bands, skills, skillsById, ui, prof,
|
chars, enemies, tokens, bonds, items, bands, skills, skillsById, ui, prof,
|
||||||
audio: { bgm, bossBgm: Object.fromEntries(Object.entries(bossBgm).sort(([a], [b]) => a.localeCompare(b, 'en', { numeric: true }))), sfx: { ui: sfxUi, battle: sfxBattle, units: unitsSfx } },
|
audio: {
|
||||||
|
bgm,
|
||||||
|
bossBgm: Object.fromEntries(Object.entries(bossBgm).sort(([a], [b]) => a.localeCompare(b, 'en', { numeric: true }))),
|
||||||
|
voice,
|
||||||
|
sfx: { ui: sfxUi, battle: sfxBattle, units: unitsSfx },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return { template, models, notes };
|
return { template, models, notes };
|
||||||
}
|
}
|
||||||
+17
-7
@@ -34,7 +34,7 @@ import { dirname, join, relative, sep } from 'node:path';
|
|||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import { Downloader } from './assets/downloader.mjs';
|
import { Downloader } from './assets/downloader.mjs';
|
||||||
import { loadIndexes } from './assets/cache.mjs';
|
import { loadIndexes } from './assets/cache.mjs';
|
||||||
import { indexAudio } from './assets/audio.mjs';
|
import { indexAudio, VOICE_DIRS } from './assets/audio.mjs';
|
||||||
import { buildPlan } from './assets/plan.mjs';
|
import { buildPlan } from './assets/plan.mjs';
|
||||||
import { processModels, findLocalEnemyModels, localEnemySpineMeta, loadLocalEnemySpines, LOCAL_ENEMY_SPINES_FILE } from './assets/spine.mjs';
|
import { processModels, findLocalEnemyModels, localEnemySpineMeta, loadLocalEnemySpines, LOCAL_ENEMY_SPINES_FILE } from './assets/spine.mjs';
|
||||||
import { collectLeaves, downloadLeaves, resolveTemplate, totalBytes, contentHash, droppedEntries, MANIFEST_VERSION } from './assets/manifest.mjs';
|
import { collectLeaves, downloadLeaves, resolveTemplate, totalBytes, contentHash, droppedEntries, MANIFEST_VERSION } from './assets/manifest.mjs';
|
||||||
@@ -54,7 +54,10 @@ const HELP = `Usage: node tools/fetch-assets.mjs [options]
|
|||||||
--force re-download files even when present
|
--force re-download files even when present
|
||||||
--offline no network: post-process what is on disk and rebuild data/assets.json
|
--offline no network: post-process what is on disk and rebuild data/assets.json
|
||||||
--dry-run print the plan and exit
|
--dry-run print the plan and exit
|
||||||
--refresh-index re-download audio_data.json / models_data.json indexes
|
--refresh-index re-download the audio_data.json / charword_table.json / models_data.json indexes
|
||||||
|
--voice-lang=cn operator battle voice language: cn (default) | jp | en | kr
|
||||||
|
--voice-all plan every official voice slot, including the prep-only lines no battle plays
|
||||||
|
(干员报到 / 编入队伍 / 任命队长; 360 files / 19.3 MB more per run — off by default)
|
||||||
--prune delete files under public/assets that the manifest no longer references
|
--prune delete files under public/assets that the manifest no longer references
|
||||||
(public/assets/local/** of tools/local-extract is never deleted); implies --allow-shrink
|
(public/assets/local/** of tools/local-extract is never deleted); implies --allow-shrink
|
||||||
--allow-shrink write data/assets.json even when it loses entries the current one has
|
--allow-shrink write data/assets.json even when it loses entries the current one has
|
||||||
@@ -66,10 +69,10 @@ const HELP = `Usage: node tools/fetch-assets.mjs [options]
|
|||||||
/**
|
/**
|
||||||
* Parse CLI flags.
|
* Parse CLI flags.
|
||||||
* @param {string[]} argv
|
* @param {string[]} argv
|
||||||
* @returns {{concurrency:number, force:boolean, offline:boolean, dryRun:boolean, refreshIndex:boolean, prune:boolean, allowShrink:boolean, localSpines:boolean, help:boolean}}
|
* @returns {{concurrency:number, force:boolean, offline:boolean, dryRun:boolean, refreshIndex:boolean, prune:boolean, allowShrink:boolean, localSpines:boolean, voiceLang:string, voiceAll:boolean, help:boolean}}
|
||||||
*/
|
*/
|
||||||
export function parseArgs(argv) {
|
export function parseArgs(argv) {
|
||||||
const o = { concurrency: 16, force: false, offline: false, dryRun: false, refreshIndex: false, prune: false, allowShrink: false, localSpines: false, help: false };
|
const o = { concurrency: 16, force: false, offline: false, dryRun: false, refreshIndex: false, prune: false, allowShrink: false, localSpines: false, voiceLang: 'cn', voiceAll: false, help: false };
|
||||||
for (const a of argv) {
|
for (const a of argv) {
|
||||||
const [k, v] = a.split('=');
|
const [k, v] = a.split('=');
|
||||||
if (k === '--concurrency') o.concurrency = Math.max(1, Math.min(64, parseInt(v, 10) || 16));
|
if (k === '--concurrency') o.concurrency = Math.max(1, Math.min(64, parseInt(v, 10) || 16));
|
||||||
@@ -80,6 +83,8 @@ export function parseArgs(argv) {
|
|||||||
else if (k === '--prune') o.prune = true;
|
else if (k === '--prune') o.prune = true;
|
||||||
else if (k === '--allow-shrink') o.allowShrink = true;
|
else if (k === '--allow-shrink') o.allowShrink = true;
|
||||||
else if (k === '--local-spines') o.localSpines = true;
|
else if (k === '--local-spines') o.localSpines = true;
|
||||||
|
else if (k === '--voice-lang') { if (!VOICE_DIRS[v]) throw new Error(`unknown --voice-lang ${v} (cn | jp | en | kr)`); o.voiceLang = v; }
|
||||||
|
else if (k === '--voice-all') o.voiceAll = true;
|
||||||
else if (k === '--help' || k === '-h') o.help = true;
|
else if (k === '--help' || k === '-h') o.help = true;
|
||||||
else throw new Error(`unknown option ${a}\n${HELP}`);
|
else throw new Error(`unknown option ${a}\n${HELP}`);
|
||||||
}
|
}
|
||||||
@@ -158,6 +163,7 @@ function countStats(m, bytes, files) {
|
|||||||
skills: Object.keys(m.skills || {}).length,
|
skills: Object.keys(m.skills || {}).length,
|
||||||
ui: Object.keys(m.ui || {}).length,
|
ui: Object.keys(m.ui || {}).length,
|
||||||
sfxUnits: Object.keys(m.audio?.sfx?.units || {}).length,
|
sfxUnits: Object.keys(m.audio?.sfx?.units || {}).length,
|
||||||
|
voiceChars: Object.keys(m.audio?.voice || {}).length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +224,7 @@ async function main() {
|
|||||||
readJson('docs/research/05-enemies.json'),
|
readJson('docs/research/05-enemies.json'),
|
||||||
readJson('docs/research/05-maps.json'),
|
readJson('docs/research/05-maps.json'),
|
||||||
]);
|
]);
|
||||||
const { audioData, modelsData } = await loadIndexes(ROOT, { refresh: opts.refreshIndex && !opts.offline, offline: opts.offline, log });
|
const { audioData, modelsData, charword } = await loadIndexes(ROOT, { refresh: opts.refreshIndex && !opts.offline, offline: opts.offline, log });
|
||||||
const audio = indexAudio(audioData);
|
const audio = indexAudio(audioData);
|
||||||
// The game data built by tools/build-data.mjs (when present) may reference more
|
// The game data built by tools/build-data.mjs (when present) may reference more
|
||||||
// spawnable enemies/tokens than research lists (e.g. 机变 enemy swaps): cover them too.
|
// spawnable enemies/tokens than research lists (e.g. 机变 enemy swaps): cover them too.
|
||||||
@@ -228,7 +234,9 @@ async function main() {
|
|||||||
for (const b of Object.values(dataBosses || {})) if (b?.enemyKey && typeof b.handbookId === 'string') extraHandbook[b.enemyKey] = b.handbookId;
|
for (const b of Object.values(dataBosses || {})) if (b?.enemyKey && typeof b.handbookId === 'string') extraHandbook[b.enemyKey] = b.handbookId;
|
||||||
const localEnemySpines = await syncLocalEnemySpines(opts);
|
const localEnemySpines = await syncLocalEnemySpines(opts);
|
||||||
const plan = buildPlan({
|
const plan = buildPlan({
|
||||||
assets07, ops03, enemies05, maps05, audio, modelsData,
|
assets07, ops03, enemies05, maps05, audio, modelsData, charword, voiceLang: opts.voiceLang,
|
||||||
|
// default: only the slots a battle can play (plan.mjs VOICE_BATTLE_SLOTS); --voice-all takes the whole official set
|
||||||
|
voiceSlots: opts.voiceAll ? null : undefined,
|
||||||
extraEnemyIds: Object.keys(dataEnemies || {}),
|
extraEnemyIds: Object.keys(dataEnemies || {}),
|
||||||
extraTokenIds: Object.keys(dataTokens || {}),
|
extraTokenIds: Object.keys(dataTokens || {}),
|
||||||
extraHandbook,
|
extraHandbook,
|
||||||
@@ -238,7 +246,8 @@ async function main() {
|
|||||||
log(`[plan] ${leaves.length} files + ${plan.models.size} Spine models ` +
|
log(`[plan] ${leaves.length} files + ${plan.models.size} Spine models ` +
|
||||||
`(${Object.keys(plan.template.chars).length} chars, ${Object.keys(plan.template.enemies).length} enemies, ` +
|
`(${Object.keys(plan.template.chars).length} chars, ${Object.keys(plan.template.enemies).length} enemies, ` +
|
||||||
`${Object.keys(plan.template.tokens).length} tokens, ${Object.keys(plan.template.ui).length} UI sprites, ` +
|
`${Object.keys(plan.template.tokens).length} tokens, ${Object.keys(plan.template.ui).length} UI sprites, ` +
|
||||||
`${Object.keys(plan.template.audio.sfx.units).length} units with SFX)`);
|
`${Object.keys(plan.template.audio.sfx.units).length} units with SFX, ` +
|
||||||
|
`${Object.keys(plan.template.audio.voice).length} operators with ${opts.voiceLang.toUpperCase()} voice)`);
|
||||||
if (opts.dryRun) {
|
if (opts.dryRun) {
|
||||||
for (const n of plan.notes) log(` note: ${n}`);
|
for (const n of plan.notes) log(` note: ${n}`);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -324,6 +333,7 @@ async function main() {
|
|||||||
log(`on disk (manifest) : ${mb(s.bytes)} in ${s.files} files`);
|
log(`on disk (manifest) : ${mb(s.bytes)} in ${s.files} files`);
|
||||||
log(`chars ${s.chars} (Back model ${s.charsWithBack}) · enemies ${s.enemies} (Spine ${s.enemiesWithSpine}) · tokens ${s.tokens} (Spine ${s.tokensWithSpine}) · Spine models ${s.spineModels}`);
|
log(`chars ${s.chars} (Back model ${s.charsWithBack}) · enemies ${s.enemies} (Spine ${s.enemiesWithSpine}) · tokens ${s.tokens} (Spine ${s.tokensWithSpine}) · Spine models ${s.spineModels}`);
|
||||||
log(`bonds ${s.bonds} · items ${s.items} · bands ${s.bands} · skill icons ${s.skills} · UI ${s.ui} · units with SFX ${s.sfxUnits}`);
|
log(`bonds ${s.bonds} · items ${s.items} · bands ${s.bands} · skill icons ${s.skills} · UI ${s.ui} · units with SFX ${s.sfxUnits}`);
|
||||||
|
log(`operator battle voice: ${s.voiceChars} charIds (--voice-lang=${opts.voiceLang})`);
|
||||||
log(`fonts: ${Object.values(fonts.files).map((f) => f.woff2 || f.original).join(', ') || 'none'}`);
|
log(`fonts: ${Object.values(fonts.files).map((f) => f.woff2 || f.original).join(', ') || 'none'}`);
|
||||||
if (resolved.fallbacks.length) { log(`fallbacks used (${resolved.fallbacks.length}):`); for (const f of resolved.fallbacks.slice(0, 20)) log(` ${f}`); }
|
if (resolved.fallbacks.length) { log(`fallbacks used (${resolved.fallbacks.length}):`); for (const f of resolved.fallbacks.slice(0, 20)) log(` ${f}`); }
|
||||||
if (downloadErrors.length) log(`download errors (${downloadErrors.length}, re-run to retry): ${downloadErrors.slice(0, 10).join(', ')}`);
|
if (downloadErrors.length) log(`download errors (${downloadErrors.length}, re-run to retry): ${downloadErrors.slice(0, 10).join(', ')}`);
|
||||||
|
|||||||
Reference in new issue
Block a user