feedback5 WE: 联防 is played on its own map, the escaped template level (GitHub #41, item 3) — act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer = level_act1autochess_escaped_single / _multi name the level of the 联防 battle, and its map is the field: two road halves (cols 3–9 and 11–17, rows 9–12) joined at col 10, no water, crates or devices (until 0.1.3 the round's stage, opened to both halves). tools/build-data.mjs builds both into data/stages.json after the 11 battle stages (kind 'unite', helpers 1 / 2, weight 0, inactive, no modes, the name 联防阵地(N名玩家) the remake's own label [ASSUMED]; validation ties config.unite.templates to them); regenerated offline, a JSON compare shows only the two added records. unite.js uniteStageId picks the map of the helper count (escaped_single's routes enter at col 10, escaped_multi's at col 18 through (9,10)); match/unitePhase.js fields the 联防 battle on it (the round's stage only with data that lacks it); each helper on its prep tiles, the first of two shifted 8 columns onto the right half as before (the official halves; player_map_lr_offset 8). Client: screens/game.js draws the shown field's own map and restores the board's on leaving it (a 2-line change in a file of the R5 split; render/app.js enterBattle already loads m.field stageId); the dev mock's 联防 follows. The 联防 BGM (PR #110) is unchanged. Tests: test/match/feedback5-unite-map.test.js (both combat modes, 1 and 2 helpers, degraded data), test/data.test.js, test/render/tiles.test.js. Docs: DATA §12, META §4, DESIGN §3, PLAYING §5. Golden: fields unite-1 / unite-2 (the battle's stage → the 联防 map) and the 6 co-op match scenarios with a 联防 round (coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun); roster / bonds unchanged (tools/golden.mjs leaves the 联防 maps out of its stage list)

This commit is contained in:
sganggs committed 2026-10-05 18:17:08 +08:00
1 parent db1b44bb71
commit 8a43624935
19 files changed
+386 -174

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"act1autochess_m01":{"id":"act1autochess_m01","name":"战场#01","weight":50,"active":true,"modes":["mode_training_1","mode_single_normal","mode_single_funny","mode_multi_normal","mode_multi_funny"],"size":[19,21],"rows":["aaaaaaaaaa#aaaaaaaaaa","###IAAAAA###AAAAAI###","##Err###rrOrr###rrE##","###brbbbrfffrbbbrb###","###brbbbrfffrbbbrb###","###brrrrrfOfrrrrrb###","XXXXXXXXXXXXXXXXXXXXX","aaaaaaaaaa###########","####AAAAA##I#########","##Err###rrSrr###rrS##","###brbbbrf#brbbbrf###","###brbbbrf#brbbbrf###","###brrrrrfSbrrrrrfS##","XXXXXXXXXXXXXXXXXXXXX","#######fffffff#######","#######Sffffff#######","#######ppppppp#######","#######fffffff#######","#######Sffffff#######"],"tiles":{"a":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{"isValidHand":1}},"#":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"I":{"tileKey":"tile_telin","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"A":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"E":{"tileKey":"tile_end","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"r":{"tileKey":"tile_road","height":"LOW","buildable":"ALL","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"O":{"tileKey":"tile_telout","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"b":{"tileKey":"tile_fence_bound","height":"LOW","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"f":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"X":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"NONE","groundPassable":false,"flyPassable":false,"special":null,"bb":{}},"S":{"tileKey":"tile_start","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"p":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{"previewNotAlloed":1}}},"devices":[{"key":"trap_1112_acblzd","name":"盟约寒风","alias":null,"pos":[18,0],"dir":"UP","hidden":false,"role":"coldWind","stats":{"maxHp":100,"atk":0,"def":0,"res":0,"bat":1,"aspd":100,"blockCnt":0},"rangeGrid":null,"rangeTiles":null,"skill":{"skillId":"sktok_acblzd","name":"谢拉格寒风","level":1,"desc":"每隔一定时间对全场敌人造成一定时间的寒冷","descRaw":"每隔一定时间对全场敌人造成一定时间的<$ba.cold>寒冷</>","bb":{},"bbStr":{}},"desc":null,"active":true},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#1","pos":[9,7],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,6],[10,6],[11,6],[12,6],[13,6],[9,7],[10,7],[11,7],[12,7],[13,7],[9,8],[10,8],[11,8],[12,8],[13,8]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"damage_scale_per_stack":0.001,"max_attack_speed":300,"max_damage_scale":1.3},"bbStr":{}},"desc":null,"active":false},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#2","pos":[9,15],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,14],[10,14],[11,14],[12,14],[13,14],[9,15],[10,15],[11,15],[12,15],[13,15],[9,16],[10,16],[11,16],[12,16],[13,16]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"daLine truncated
{"act1autochess_m01":{"id":"act1autochess_m01","name":"战场#01","weight":50,"active":true,"modes":["mode_training_1","mode_single_normal","mode_single_funny","mode_multi_normal","mode_multi_funny"],"size":[19,21],"rows":["aaaaaaaaaa#aaaaaaaaaa","###IAAAAA###AAAAAI###","##Err###rrOrr###rrE##","###brbbbrfffrbbbrb###","###brbbbrfffrbbbrb###","###brrrrrfOfrrrrrb###","XXXXXXXXXXXXXXXXXXXXX","aaaaaaaaaa###########","####AAAAA##I#########","##Err###rrSrr###rrS##","###brbbbrf#brbbbrf###","###brbbbrf#brbbbrf###","###brrrrrfSbrrrrrfS##","XXXXXXXXXXXXXXXXXXXXX","#######fffffff#######","#######Sffffff#######","#######ppppppp#######","#######fffffff#######","#######Sffffff#######"],"tiles":{"a":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{"isValidHand":1}},"#":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"I":{"tileKey":"tile_telin","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"A":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"E":{"tileKey":"tile_end","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"r":{"tileKey":"tile_road","height":"LOW","buildable":"ALL","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"O":{"tileKey":"tile_telout","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"b":{"tileKey":"tile_fence_bound","height":"LOW","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"f":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"X":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"NONE","groundPassable":false,"flyPassable":false,"special":null,"bb":{}},"S":{"tileKey":"tile_start","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"p":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{"previewNotAlloed":1}}},"devices":[{"key":"trap_1112_acblzd","name":"盟约寒风","alias":null,"pos":[18,0],"dir":"UP","hidden":false,"role":"coldWind","stats":{"maxHp":100,"atk":0,"def":0,"res":0,"bat":1,"aspd":100,"blockCnt":0},"rangeGrid":null,"rangeTiles":null,"skill":{"skillId":"sktok_acblzd","name":"谢拉格寒风","level":1,"desc":"每隔一定时间对全场敌人造成一定时间的寒冷","descRaw":"每隔一定时间对全场敌人造成一定时间的<$ba.cold>寒冷</>","bb":{},"bbStr":{}},"desc":null,"active":true},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#1","pos":[9,7],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,6],[10,6],[11,6],[12,6],[13,6],[9,7],[10,7],[11,7],[12,7],[13,7],[9,8],[10,8],[11,8],[12,8],[13,8]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"damage_scale_per_stack":0.001,"max_attack_speed":300,"max_damage_scale":1.3},"bbStr":{}},"desc":null,"active":false},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#2","pos":[9,15],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,14],[10,14],[11,14],[12,14],[13,14],[9,15],[10,15],[11,15],[12,15],[13,15],[9,16],[10,16],[11,16],[12,16],[13,16]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"daLine truncated
+10 -3
View File
@@ -407,12 +407,19 @@ hidden core `h08_0X`; 联防 `act1autochess_escaped_single|multi`; training `tr0
Timing: templates have one wave with one fragment, so `time = wave.preDelay + fragment.preDelay + action.preDelay`
exactly (the builder warns if a multi-fragment template ever appears).
## 12. `stages.json` — `{ [stageId]: Stage }` (11 terrains, 8 active)
## 12. `stages.json` — `{ [stageId]: Stage }` (11 terrains, 8 active, + the 2 联防 maps)
The 11 battle stages of `stageDatasDict`, then the two **联防 maps** (GitHub #41): act2autochess constData
`escapedBattleTemplateMapSinglePlayer` / `MultiPlayer` name the level of the 联防 battle (`level_act1autochess_escaped_single`
/ `_multi`, the wave templates of the same id in `waves.json`), and its own map is the 联防 field — tile for tile the same
on both: two road halves (cols 3–9 and 11–17, rows 9–12) joined at col 10, the objective at (9,2), no devices or special
terrain. `server/match/unite.js uniteStageId` fields the 联防 battle on the one of its helper count.
| Field | Example | Meaning |
|---|---|---|
| `id`, `name` | `"act2autochess_m01"`, `"战场#05(下半) 源石流发生装置"` | player-facing name from research 05 (the official tables carry none; falls back to id). The build drops bracketed segments containing Latin letters (research notes such as `战场#01 (upper half #01)` → `战场#01`), logs a warning, and fails validation if Latin text remains |
| `weight`, `active`, `modes` | `50`, `true`, `["mode_single_normal",…]` | match-start pick weight (act1 m05–m07 weight 0) |
| `id`, `name` | `"act2autochess_m01"`, `"战场#05(下半) 源石流发生装置"` | player-facing name from research 05 (the official tables carry none; falls back to id). The build drops bracketed segments containing Latin letters (research notes such as `战场#01 (upper half #01)` → `战场#01`), logs a warning, and fails validation if Latin text remains. The 联防 maps: `联防阵地(1名玩家)` / `联防阵地(2名玩家)`, the remake's own label [ASSUMED] (no table or PRTS page names them; no screen shows it) |
| `weight`, `active`, `modes` | `50`, `true`, `["mode_single_normal",…]` | match-start pick weight (act1 m05–m07 weight 0); the 联防 maps: `0`, `false`, `[]` (never a match stage) |
| `kind`, `helpers` | `"unite"`, `1` | only on the two 联防 maps: the helper count whose 联防 field it is (= `config.unite.templates`; the build fails when they disagree) |
| `size` | `[19,21]` | |
| `rows[]` | `rows[9] = "##Errr#rrrSrrr#rrrS##"` | 19 strings, **index = row (0 = bottom)**, one glyph per col |
| `tiles[glyph]` | `{"tileKey":"tile_road","height":"LOW","buildable":"ALL","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}}` | actual tile properties of each glyph used. `buildable` is the **effective** deploy type: the level's buildableType, except a tile whose mechanism refuses deployment — 深水区 `tile_deepsea` (PRTS 深水区 地形信息 "部署类型 全部位 … 地形机制 拒绝部署(待补充)"; player report #3 after 0.1.0) — which is `NONE` and keeps the level's value in `buildableType` (`server/sim/grid.js DEPLOY_REFUSED_TILES`, shared with the builder) |
+1 -1
View File
@@ -157,7 +157,7 @@ Ownership rule for parallel agents: **only edit files assigned to you**; if you
- Tile semantics come from `data/stages.json` legend: height (LOW/HIGH), buildable (ALL/MELEE/RANGED/NONE — the **effective** deploy type: the 深水区 `tile_deepsea` refuses deployment, NONE with the level's `buildableType` ALL kept, PRTS 深水区 地形信息 "拒绝部署(待补充)"; an active 特制水上平台 makes its tile deployable in the prep deploy map only, §21.3), ground-passable, fly-only, special terrain (mire, smog, deepsea, infection). The fenced tiles (围墙 `tile_fence_bound` / 围栏 `tile_fence`, legend `b`: LOW, buildable ALL, passable to flyers only) take units, which attack normally from there but block no ground enemy (PRTS 地形机制, §21.23). Melee chess may stand only on LOW tiles with buildable ALL/MELEE — except elite 歌蕾蒂娅 carrying module HOK-Y 淡金坠饰 (`uniequip_003_glady`), who may use the ranged tiles too (owner's decision 2026-10-04; the branch trait 「可以放置于远程位」 is not read, §22.6 reversed); ranged chess on ALL/RANGED **and** on LOW ALL/MELEE tiles ("所有行动内远程干员可部署在近战位"). Tokens follow their own `position`; a summon marked `ownerRange` (狼群, 流形: "只能部署在召唤者攻击范围内") goes only on a tile of its owner's attack range — the loadout grid rotated by the owner's facing around its tile (`PlayerState.summonRange`, §21.3).
- **Facing (corrected, see research 09 §1.2):** like standard Arknights, every board piece has a direction `dir ∈ UP|RIGHT|DOWN|LEFT` chosen with the 4-direction deploy wheel after dropping it on a tile (also re-orientable in place); default `RIGHT`; persists across rounds. Range grid entries are `[dRow, dCol]` relative to facing RIGHT and are rotated: RIGHT `(dr,dc)`, UP `(dc,−dr)`, LEFT `(−dr,−dc)`, DOWN `(−dc,dr)` (row 0 = bottom); `rangeExtend` applies along +dCol before rotating — to a running skill's range too, unless that skill ignores 攻击距离 (`targeting.noRangeExtend`, 信仰搅拌机 S3, §21.16). The right-side Final Assault player is mirrored col `c→20−c` with RIGHT↔LEFT (UP/DOWN unchanged).
- **Range test:** an enemy is inside a grid range if the tile containing it (`round(y), round(x)`) is one of the range tiles; a huge enemy (data `hitArea`: 巨型单位, a 4.95 × 2.95 rectangle moved 1.0 up) if any tile its hit rectangle overlaps is one (PRTS 作战机制 "巨型BOSS单位的每一个占据的格子都可以让其本身通过格子判定"; `sim/body.js`, §19.4). Radius ranges (enemies, auras, AoE) use Euclidean distance in tiles — to a huge enemy's rectangle [ASSUMED] — except splash around a struck target, a 中点判定 on positions (PRTS 作战机制 "中点判定…案例:阻挡,酒神1天赋的1.3溅射半径"). A ranged enemy's normal attack takes an ally whose collider (radius 0.25, PRTS 作战机制 §碰撞体积 "我方干员碰撞体积基本均为以0.25格为半径的圆形") touches its range circle: centre distance ≤ `rangeRadius` + 0.25 (`constants.js ALLY_COLLIDER_RADIUS`, `ai.js enemyAttack`; §20.4).
- **Fields at runtime:** a `Battle` receives a **field rect** (rows/cols subset) and the full stage grid. Normal battles use rows 9–12 × cols 0–10; unite uses rows 9–12 × cols 0–20; boss uses rows 0–5 × cols 0–20 (solo boss uses the left half only). Pathfinding never leaves the rect.
- **Fields at runtime:** a `Battle` receives a **field rect** (rows/cols subset) and the full stage grid. Normal battles use rows 9–12 × cols 0–10; unite uses rows 9–12 × cols 0–20 — on its own map, the escaped template's (stages.json `kind: 'unite'`, `unite.js uniteStageId`; GitHub #41), not the round's stage; boss uses rows 0–5 × cols 0–20 (solo boss uses the left half only). Pathfinding never leaves the rect.
- **Speed:** `tilesPerSecond = moveSpeed × MOVE_SCALE` with `MOVE_SCALE = 0.5` (in `sim/constants.js`, tunable).
---
+4 -1
View File
@@ -663,7 +663,10 @@ helpers = `unite.js helperOrder` (research 08 §5, PRTS 卫戍协议/帮助 §
chosen by most units on the field (downed included) > an active bond > most standing units > seat; the pair ordered by
units > active bond > Σ active layers > standing > seat (LP plays no part), the first one on the right-hand field
(colOffset +8, where escaped_multi enters), the other colOffset 0; the escaped template of that size routes the leaked
enemies by slot class; helpers' operators carry
enemies by slot class, and its own map is the field (`unite.js uniteStageId`: stages.json `act1autochess_escaped_single` /
`_multi`, two road halves joined at col 10 — no water, crates or devices of the round's stage, no band map characters:
the level has no predefined ones; the round's stage only with data that lacks them; GitHub #41), each helper's pieces
on their prep tiles; helpers' operators carry
`{ hpPct, sp }` from `unitsEnd` ("阵地以其当前状态": the HP ratio and the 技力 only — a skill running at the end enters
switched off; summon pieces `{ sp }`, "召唤物仅修改技力"); an operator knocked out at the end of the helper's own
combat carries `{ down: true }` (PRTS 卫戍协议/帮助: "部署完成后…上一阶段为退场状态的干员强制退场"): deployed, then forced out
+1
View File
@@ -83,6 +83,7 @@
某回合**有人漏怪、又有人完美作战**(全歼且无漏怪)时触发:最多 2 名完美作战的队友带着自己的阵容(与官方相同,只保留作战结束时的生命比例和技力:作战结束时还开着的技能在联防开场是关着的,技力是开技能后剩下的值,通常为 0;召唤物只保留技力;作战结束时已被击倒的干员在原位倒地(阿戈尔的开场吞噬仍把它当作站着的干员计算标记与基础攻击力、阻挡数,之后它照常退场),按官方的「强制退场」以完整的再部署时间重新倒计时,时间到且部署费用足够后照常再部署——联防较短时可能来不及再部署;机变「征召」这类开战时生效的再部署时间减免同样计入),迎击其他玩家漏掉的全部敌人。盟约层数按本回合自己作战结束时达到的层数(即盟约栏显示的层数)计算,联防中不再叠加层数;这些层数在结算时只计入一次。
- 参加者按官方规则挑选:场上单位最多 > 有已激活的盟约 > 未倒下的单位最多 > 座位靠前(与 LP 无关);两人参加时,排在前面的一位(再比较激活盟约的层数总和)在右半场「率先迎敌」。
- 联防在官方的**联防专用地图**上进行(与官方相同,不是本回合的战场):一整片道路,没有水池、箱子、装置等地形;干员按休整期的位置站好。一人协防时敌人从中间的入口进入左半场;两人协防时敌人从最右侧进入,先经过右半场,再穿过中间进入左半场。
- 只有完美作战的队友能参与联防;联防中**盟约不会叠加层数**。
- 联防中击杀悬赏敌人,资金归击杀者。
+6 -3
View File
@@ -438,7 +438,9 @@ function startCombat(phase) {
for (const p of board.slice(0, 5)) {
const c = data.lookup('chess', pick(S.pool).chessId);
const r = boss ? p.row - 7 : p.row;
units.push({ id: id++, kind: 'op', side: 'ally', ownerId: 'ai_2', defId: c.chessId, name: c.name, tier: c.tier, golden: false, spine: c.charId, avatar: c.assets.avatar, x: 20 - p.col, y: r, facing: -1, maxHp: c.stats.maxHp });
// 联防: the second helper's board on the other half, shifted 8 columns (server unite.js); boss pairs: mirrored
const x = kind === 'unite' ? p.col + 8 : 20 - p.col;
units.push({ id: id++, kind: 'op', side: 'ally', ownerId: 'ai_2', defId: c.chessId, name: c.name, tier: c.tier, golden: false, spine: c.charId, avatar: c.assets.avatar, x, y: r, facing: kind === 'unite' ? 1 : -1, maxHp: c.stats.maxHp });
}
}
const enemyKeys = S.priv.nextEnemies.map((e) => e.enemyKey);
@@ -448,7 +450,7 @@ function startCombat(phase) {
const key = enemyKeys[i % enemyKeys.length];
const e = data.lookup('enemies', key);
const y = boss ? [2, 5, 3][i % 3] : [9, 12][i % 2];
const startX = boss ? (i % 2 ? 17 : 3) : 10;
const startX = boss ? (i % 2 ? 17 : 3) : kind === 'unite' ? 18 : 10; // escaped_multi: the right half's gates
enemies.push({ id: id++, kind: 'enemy', side: 'enemy', ownerId: ME, defId: key, name: e?.name, spine: key, avatar: key, x: startX + rnd() * 0.3, y, facing: -1, maxHp: e?.stats?.maxHp || 3000, hp: e?.stats?.maxHp || 3000, spawnAt: i * 0.9, dir: boss ? (i % 2 ? -1 : 1) : -1, dead: false });
}
if (boss) {
@@ -456,7 +458,8 @@ function startCombat(phase) {
const e = data.lookup('enemies', bossRec.enemyKey);
enemies.push({ id: id++, kind: 'enemy', side: 'enemy', ownerId: ME, defId: bossRec.enemyKey, name: bossRec.name, spine: bossRec.enemyKey, avatar: bossRec.enemyKey, x: 10, y: 3, facing: -1, maxHp: e?.stats?.maxHp || 1e5, hp: (e?.stats?.maxHp || 1e5) * 0.62, spawnAt: 0, dir: 0, boss: true, dead: false });
}
const field = { fieldId, kind, rect, stageId: pub.stageId, units: units.map((u) => ({ ...u })) };
// the 联防 field fights on its own map (server unite.js uniteStageId: 2 helpers → escaped_multi; GitHub #41)
const field = { fieldId, kind, rect, stageId: kind === 'unite' ? 'act1autochess_escaped_multi' : pub.stageId, units: units.map((u) => ({ ...u })) };
store.patch('match', { field });
const allyState = units.map((u) => ({ ...u, hp: u.maxHp * (0.55 + rnd() * 0.45), sp: rnd() * 20, spMax: 20 }));
const t0 = performance.now();
+4 -2
View File
@@ -362,11 +362,13 @@ function MatchScreen() {
const shownMembers = shownField ? ((Array.isArray(pub?.fields) ? pub.fields : []).find((f) => f && f.fieldId === shownField.fieldId)?.players
|| (Array.isArray(shownField.players) ? shownField.players : null)) : null;
const ownView = showPrep || (shownField ? (shownMembers ? shownMembers.includes(myId) : shownField.fieldId === ownFieldId(myId)) : !watchingOther);
// a field on a map of its own (联防: the escaped template's map, GitHub #41) shows that map; leaving it restores the board's
const fieldStage = gd.ready && shownField && shownField.stageId && shownField.stageId !== pub?.stageId ? gd.stage(shownField.stageId) : null;
useEffect(() => {
if (!view) return;
const st = ownView ? ownStage : baseStage;
const st = fieldStage || (ownView ? ownStage : baseStage);
if (st) view.setStage(st);
}, [view, ownView, ownStage, baseStage]);
}, [view, ownView, ownStage, baseStage, fieldStage]);
const staleFieldRef = useRef(null);
const enteredFieldRef = useRef(null);
const pressSel = useRef(null); // the selected piece when the current field press began
+7 -3
View File
@@ -5,7 +5,7 @@
import { PHASE, GEO } from '../../../shared/constants.js';
import { deriveSeed } from '../../sim/rng.js';
import { uniteBattleOpts, uniteSurvivors } from '../unite.js';
import { uniteBattleOpts, uniteSurvivors, uniteStageId } from '../unite.js';
import { FieldRunner, timelineAt, uniteBillBounds } from '../fields.js';
import { uniteLeft } from '../../sim/spec.js';
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
@@ -38,7 +38,11 @@ export class MatchUnite {
this.runner.start();
}
/** Battle options of the 联防 field (helpers' carried end state, the leakers' enemies). */
/**
* Battle options of the 联防 field (helpers' carried end state, the leakers' enemies) on its own map — the escaped
* template's (unite.js uniteStageId; GitHub #41), the round's stage only when the data lacks it. The field meta and the
* client-run spec carry that stageId, so every viewer draws the 联防 map.
*/
_uniteOpts(plan, limit) {
const { wave, players } = uniteBattleOpts(this, plan, limit);
return {
@@ -46,7 +50,7 @@ export class MatchUnite {
kind: 'unite',
modeId: this.modeId,
round: this.round,
stageId: this.stageId,
stageId: uniteStageId(this.gd, plan.helpers.length) ?? this.stageId,
rect: { ...GEO.UNITE_RECT },
timeLimit: limit,
players,
+21 -1
View File
@@ -5,7 +5,11 @@
// players chosen by most units on the field (downed included) > has an active bond (存疑) > most undowned units, then
// seat; with 2 helpers the one ranked first by most units > active bond > Σ active bond layers (存疑) > most undowned
// units "率先迎敌" on the RIGHT-hand field (colOffset +8, where the escaped_multi routes enter), the other keeps the
// left half (colOffset 0); a lone helper plays escaped_single on its own field. Their operators keep the HP ratio and
// left half (colOffset 0); a lone helper plays escaped_single on its own field. The field is the template's own map
// (uniteStageId: data/stages.json act1autochess_escaped_single / _multi, kind 'unite' — two road halves joined at col 10,
// the right half's gates at col 18; GitHub #41: until 0.1.3 it was the round's stage, its water, crates and devices
// included), every helper's pieces on their prep tiles ("按休整期位置部署在场"), the right-hand one shifted 8 columns
// (the official maps' halves, = the stage config's player_map_lr_offset 8). Their operators keep the HP ratio and
// the SP (技力, stored charges included) from the end of their own combat, nothing else — a skill still running then
// enters switched off (BattleResult.unitsEnd → PlayerBattleInput.units[].carryState `{ hpPct, sp }`, "阵地以其当前状态";
// community report #34 / GitHub #82: it used to restart for free). An operator knocked out at the end of its own combat (alive false) is fielded with
@@ -115,6 +119,22 @@ export function helperOrder(m, perfects, results) {
return select.sort((a, b) => S(b).units - S(a).units || (S(b).active - S(a).active) || S(b).layers - S(a).layers || S(b).standing - S(a).standing || a.seat - b.seat);
}
/**
* The map of the 联防 field (GitHub #41): act2autochess constData escapedBattleTemplateMapSinglePlayer (1 helper) /
* MultiPlayer (2 helpers) name one level for both the 联防 wave and its map — config.unite.templates[n], built into
* data/stages.json as the stage of the same id (kind 'unite'). null when the data has no such stage (degraded data:
* the caller keeps the round's stage).
* @param {import('./gamedata.js').GameData} gd
* @param {number} helperCount
* @returns {string|null}
*/
export function uniteStageId(gd, helperCount) {
const templates = gd.unite.templates || {};
const id = templates[String(helperCount)] ?? templates[helperCount] ?? null;
const st = id ? gd.stage(id) : null;
return st && st.kind === 'unite' ? id : null;
}
/** Battle options for the unite field (without data/logger, added by the match). */
export function uniteBattleOpts(m, plan, timeLimit) {
const wave = buildUniteWave(m.gd, plan.leaked, plan.helpers.length, timeLimit);
+29
View File
@@ -635,6 +635,7 @@ test('stages: player-facing names are clean Chinese "战场#NN…" labels (no re
// Regression: research 05 named act1 m01 '战场#01 (upper half #01)'; the English note reached the
// briefing BATTLEFIELD row. Names come from research only, so the build must strip such notes.
for (const s of Object.values(stages)) {
if (s.kind === 'unite') continue; // the two 联防 maps: the remake's own label, checked in the 联防 test below
assert.match(s.name, /^战场#\d{2}(?:\((?:上半|下半)\))?(?: \S.*)?$/, `${s.id}: name ${JSON.stringify(s.name)}`);
assert.doesNotMatch(s.name, /[A-Za-z]/, `${s.id}: Latin text in name ${JSON.stringify(s.name)}`);
assert.equal(s.name, s.name.trim().replace(/\s+/g, ' '), `${s.id}: stray whitespace in name`);
@@ -643,6 +644,34 @@ test('stages: player-facing names are clean Chinese "战场#NN…" labels (no re
assert.equal(stages.act2autochess_m01.name, '战场#05(下半) 源石流发生装置');
});
test('stages: the 联防 maps (GitHub #41) — the escaped template levels, one per helper count, never a match stage', () => {
// act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer → config.unite.templates { 1, 2 }
assert.deepEqual(config.unite.templates, { 1: 'act1autochess_escaped_single', 2: 'act1autochess_escaped_multi' });
for (const [n, id] of Object.entries(config.unite.templates)) {
const s = stages[id];
assert.ok(s, id);
assert.equal(s.kind, 'unite');
assert.equal(s.helpers, Number(n));
assert.equal(s.active, false);
assert.equal(s.weight, 0);
assert.deepEqual(s.modes, []);
assert.ok(!Object.values(config.modes).some((m) => m.stages.includes(id)), `${id} in no mode's stage list`);
assert.equal(s.name, `联防阵地(${n}名玩家)`);
assert.deepEqual(s.devices, [], 'no crates, platforms or other devices');
assert.deepEqual(s.special, {}, 'no water, mire, smog or infection');
// the official level map (level_act1autochess_escaped_*.json): two road halves joined at col 10, not the round's stage
assert.deepEqual(s.rows.slice(9, 13), ['##ErrrrrrrSrrrrrrrS##', '###rrrrrrr#rrrrrrr###', '###rrrrrrr#rrrrrrr###', '###rrrrrrrSrrrrrrrS##']);
for (let r = 9; r <= 12; r++) for (const c of [19, 20]) assert.equal(s.tiles[s.rows[r][c]].groundPassable, false, `${id} (${r},${c}) is no ground`);
assert.ok(s.groundPaths['9,10->9,2'] && s.groundPaths['9,18->9,2'], 'walkable from both gates');
}
// the two maps are tile for tile the same: they differ in their routes (data/waves.json — 1 helper enters at col 10,
// 2 helpers at col 18 through the checkpoint (9,10))
assert.deepEqual(stages.act1autochess_escaped_single.rows, stages.act1autochess_escaped_multi.rows);
assert.deepEqual(waves.act1autochess_escaped_single.routes.map((r) => r.start), [[9, 10], [9, 10], [12, 10], [9, 10], [9, 10], [9, 10], [9, 10], [9, 10]]);
assert.deepEqual(waves.act1autochess_escaped_multi.routes.map((r) => r.start), [[9, 18], [9, 18], [12, 18], [9, 18], [9, 18], [9, 18], [9, 18], [9, 18]]);
assert.deepEqual(waves.act1autochess_escaped_multi.routes[3].checkpoints, [[9, 10]]);
});
test('official spot checks (hard-coded values from the zh_CN client data)', () => {
const st = (id) => { const s = chess[id].stats; return [s.maxHp, s.atk, s.def, s.res, s.blockCnt, s.cost]; };
assert.deepEqual(st('chess_char_6_11_b'), [1893, 492, 141, 0, 1, 15]); // 缪尔赛思 精锐 (E2 60 + module Lv3)
+1 -1
View File
@@ -16,7 +16,7 @@ through.
|---|---|
| `roster` | 49 battles: every visible chess record (normal and elite) with every selectable skill and module (DESIGN §16), 12 operators per battle on a real stage (all 11 in turn) against the round's real wave three times over, every non-leader enemy kind of `data/enemies.json` as extra spawns (half of them bounties), placeable summons on the board, every equipment item, band, battle-side 机变 card and stage map card in turn, bonds from the board |
| `bonds` | 46 battles: every bond at its activation threshold (1 layer) and at its top tier (999 layers) |
| `fields` | 22 battles: every Final Assault / Hidden Core leader on a pair and a solo template (shared pool, 200 s cap) and the 联防 field with 1 and 2 helpers (carried HP / SP, a knocked-out operator, two leakers' enemies) |
| `fields` | 22 battles: every Final Assault / Hidden Core leader on a pair and a solo template (shared pool, 200 s cap) and the 联防 field with 1 and 2 helpers on its own map (carried HP / SP, a knocked-out operator, two leakers' enemies) |
| `matches` | 16 bot-only matches to the end in virtual time: solo 标准 / 险境 / 绝境 / 终极 × 2 seeds, co-op 2 / 3 / 4, one server-run combat match, two runs boosted to the Hidden Core |
Battles go through the production BattleSpec path (`server/sim/spec.js`, as browsers and the server's headless fields
+39 -44
View File
@@ -1277,7 +1277,7 @@
"about": "联防 1 helper(s), 11 leaked: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b token_10017_skadi2_dedant",
"fast": true,
"kind": "unite",
"stageId": "act1autochess_m02",
"stageId": "act1autochess_escaped_single",
"modeId": "mode_multi_normal",
"round": 7,
"seed": 2621626757,
@@ -1289,34 +1289,33 @@
"total": 10,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 24,
"rngDraws": 7,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":10,"total":10,"perfect":true,"leaks":0,"leaksUncounted":1,"coins":3,"dmg":70502,"bossDmg":0,"heal":7510,"deaths":0,"layerGains":{}}},
"players": {"p1":{"killed":10,"total":10,"perfect":true,"leaks":0,"leaksUncounted":1,"coins":3,"dmg":70502,"bossDmg":0,"heal":4602,"deaths":2,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3430,1,2,0,0,0,0,0,0],
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3430,1,2,1,912,242,242,1,0],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",1616.45,1,1,0,0,0,0,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,23,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",2352,1,4,3,17564,0,0,4,1],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,7,1,7345,0,0,1,1],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",62.12,1,5,1,632,6072,7510,0,4],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1442,1,8,4,29637,0,0,0,1],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,6,1,15325,0,0,8,1],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,22,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",2352,1,4,2,15306,0,0,8,1],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,7,3,25379,126,0,5,1],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",311.72,1,23,0,2871,4383,4359,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1442,1,25,4,26035,1404,0,0,1],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,6,0,0,0,0,0,0],
["p1",9,"token","token_10017_skadi2_dedant",null,null,100,0,9,0,0,0,0,0,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,22,0,0,0,0,0,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,24,0,0,0,0,0,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,25,0,0,0,0,0,null]
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,24,0,0,0,0,0,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,1,26,0,0,0,0,0,null]
],
"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],
"events": {"atk":15,"bounty":1,"deploy":13,"die":14,"dmg":60,"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},
"snaps": "d0a32c2d 14dc64f8 d9ffb8be 0e32f941 f5f88a89 f3e84df2 97783db6 49fb05e7 d960234b ef9c8339 304bd9e6 d7e7110a 8a0636b9 840cbf99 e365c522 1d462726 178ff4dc"
"enemies": {"enemy_10028_vtswd":[1,0,0,0,0],"enemy_1158_divman":[4,4,0,322,15206],"enemy_1160_hvyslr":[3,3,0,242,34560],"enemy_1161_tidmag":[3,3,0,1596,20736]},
"hooks": [1,25,5100,25,25,59,129,243,12,15,6,5,1,9905,13,1,11,0,1,12,0,1,0],
"events": {"atk":31,"bounty":1,"deploy":14,"die":15,"dmg":79,"fx":40,"heal":73,"skill":11,"spawn":22,"status":39},
"fx": {"bombard":1,"bombardShell":1,"burst":1,"camouflage":1,"lightning":14,"lock":1,"lure":1,"snow":14,"storm":1,"talent":1,"tide":3,"wanted":1},
"snaps": "55586847 bbe93dd8 5bb5e558 d2646601 7210f915 de297199 991f7dc7 b11d3b0a 9572d24f ca2b027d ff63e562 b8302479 9eb55590 bb0bba8a 5b8d04c8 cab0ad61 6aa45251"
},
"unite-2": {
"about": "联防 2 helper(s), 11 leaked: chess_char_6_15_b chess_char_6_17_b chess_char_6_09_b chess_char_6_11_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b token_10030_mlyss_wtrman | chess_char_6_19_b chess_char_5_01_b chess_char_5_05_b chess_char_5_06_b chess_char_6_18_b chess_char_6_20_b chess_char_5_02_b chess_char_5_03_b",
"fast": true,
"kind": "unite",
"stageId": "act1autochess_m07",
"stageId": "act1autochess_escaped_multi",
"modeId": "mode_multi_hard",
"round": 11,
"seed": 1995382690,
@@ -1335,34 +1334,30 @@
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":11672,"bossDmg":0,"heal":10253,"deaths":0,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4608,1,36,3,32640,4897,0,21,1],
["p1",2,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",3593,1,39,0,19926,4671,0,15,1],
["p1",3,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,37,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,13,0,0,0,0,0,0],
["p1",5,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2944.54,1,16,5,74645,0,0,15,1],
["p1",6,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2192.85,1,18,0,0,5746,0,0,0],
["p1",7,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3323.25,1,20,0,0,0,5334,72,1],
["p1",8,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,11,0,9334,0,0,9,1],
["p1",9,"token","token_10030_mlyss_wtrman",null,null,2062,1,14,0,0,0,0,0,1],
["p2",10,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,6,0,0,0,0,0,0],
["p2",11,"op","chess_char_5_01_b",1,"uniequip_002_excu2",1272.7,1,10,0,0,0,0,0,0],
["p2",12,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",5905,1,38,0,0,0,0,0,0],
["p2",13,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",776.63,1,19,0,0,0,0,0,0],
["p2",14,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,8,1,11672,0,0,0,1],
["p2",15,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1631.88,1,12,0,0,0,10253,25,2],
["p2",16,"op","chess_char_5_02_b",2,"uniequip_002_titi",1554.12,1,17,0,0,0,0,0,0],
["p2",17,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",1589.76,1,21,0,0,0,0,0,0],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null],
["p1","~1","token","token_10019_nearl2_sword",null,null,5720.79,0,35,1,27958,279,0,4,null]
["p1",1,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4608,1,32,3,32640,4897,0,21,1],
["p1",2,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",3593,1,35,0,19926,4671,0,15,1],
["p1",3,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,33,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,9,0,0,0,0,0,0],
["p1",5,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2944.54,1,12,5,74645,0,0,15,1],
["p1",6,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2192.85,1,14,0,0,5746,0,0,0],
["p1",7,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3323.25,1,16,0,0,0,5334,72,1],
["p1",8,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,7,0,9334,0,0,9,1],
["p1",9,"token","token_10030_mlyss_wtrman",null,null,2062,1,10,0,0,0,0,0,1],
["p2",10,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,2,0,0,0,0,0,0],
["p2",11,"op","chess_char_5_01_b",1,"uniequip_002_excu2",1272.7,1,6,0,0,0,0,0,0],
["p2",12,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",5905,1,34,0,0,0,0,0,0],
["p2",13,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",776.63,1,15,0,0,0,0,0,0],
["p2",14,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,4,1,11672,0,0,0,1],
["p2",15,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1631.88,1,8,0,0,0,10253,25,2],
["p2",16,"op","chess_char_5_02_b",2,"uniequip_002_titi",1554.12,1,13,0,0,0,0,0,0],
["p2",17,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",1589.76,1,17,0,0,0,0,0,0],
["p1","~1","token","token_10019_nearl2_sword",null,null,5720.79,0,31,1,27958,279,0,4,null]
],
"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],
"events": {"atk":274,"bounty":1,"deploy":23,"die":16,"dmg":310,"fx":164,"heal":199,"skill":16,"spawn":33,"status":89},
"hooks": [1,34,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":29,"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},
"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": "87a560e8 640035c2 5898951a 4fffc393 5fbfbb48 8057eb1f 65fee83d d6de2bd5 344fef15 098709ef d750651d 48527fe0 694f89a9 8cb5eb2b 0c505568 9d4f9db2 d08c6080 68e58b19 6359a7b8 b2e13748 89150785 b720e743 1be1b2d3"
}
}
}
+95 -102
View File
@@ -592,7 +592,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":250,"bossDamage":291655,"dmgDealt":2959725,"gold":139,"itemsEquipped":12,"kills":331,"leaks":10,"lpLost":8,"merges":5,"perfectRounds":10,"refreshes":23},
"stats": {"activatedLayers":250,"bossDamage":291655,"dmgDealt":2960725,"gold":139,"itemsEquipped":12,"kills":331,"leaks":10,"lpLost":8,"merges":5,"perfectRounds":10,"refreshes":23},
"lineup": ["3_19_b@11,7[2_04_e_b]","5_12_a@11,8","4_10_a@10,5[1_02_e_b+2_01_e_a]","6_06_a@9,4","1_08_b@9,5","6_16_a@9,7","5_20_a@9,8[3_05_e_a+3_02_e_b]","3_13_b@9,9"],
"bonds": ["arcaneShip 2/86","miraShip 1/12","preciShip 0/10","sargonShip 0/10","siracusaShip 1/110","skillfulShip 1/36","soloShip 1/6","suntShip 1/0","swiftShip 0/32"]
},
@@ -664,22 +664,22 @@
{
"round": 8,
"ai_0": [1,25,11,0,0,4,8,"50b7d162",55,"kazimierzShip 1/16, kjeragShip 0/2, raidShip 1/20, skillfulShip 1/6, soloShip 1/3, steadShip 2/10, swiftShip 0/4",9,9,0,61],
"ai_1": [1,23,13,0,0,4,8,"bdd566ef",15,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 2/15, sargonShip 0/4, siracusaShip 1/0, skillfulShip 1/0, soloShip 1/0, swiftShip 0/2",4,9,5,66]
"ai_1": [1,21,13,0,0,4,8,"bdd566ef",15,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 2/15, sargonShip 0/4, siracusaShip 1/0, skillfulShip 1/0, soloShip 1/0, swiftShip 0/2",4,9,5,66]
},
{
"round": 9,
"ai_0": [1,25,12,0,2,4,8,"468d37f7",70,"kazimierzShip 1/19, kjeragShip 0/2, raidShip 1/28, skillfulShip 0/9, soloShip 1/3, steadShip 2/12, suntShip 1/0, swiftShip 1/8",23,23,0,73],
"ai_1": [1,18,12,0,1,4,8,"f01fb873",35,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/24, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/7",18,23,5,78]
"ai_1": [1,19,12,0,1,4,8,"f01fb873",35,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/24, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/7",18,23,5,78]
},
{
"round": 10,
"ai_0": [1,23,14,0,1,5,8,"eb530a8d",82,"kazimierzShip 1/25, kjeragShip 0/2, raidShip 1/28, skillfulShip 0/9, soloShip 1/3, steadShip 2/12, suntShip 1/0, swiftShip 1/14",6,8,2,88],
"ai_1": [1,14,13,0,0,5,8,"ad2dc598",57,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/37, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/2, soloShip 1/0, suntShip 1/0, swiftShip 1/14",4,8,4,91]
"ai_1": [1,15,13,0,0,5,8,"ad2dc598",57,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/37, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/2, soloShip 1/0, suntShip 1/0, swiftShip 1/14",4,8,4,91]
},
{
"round": 11,
"ai_0": [1,13,13,0,1,5,8,"cf13d02e",107,"kazimierzShip 1/33, kjeragShip 0/2, raidShip 1/33, skillfulShip 0/9, soloShip 1/3, steadShip 2/12, suntShip 1/0, swiftShip 1/26",10,27,17,100],
"ai_1": [1,4,12,0,0,5,8,"60f9ee96",81,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/46, sargonShip 0/4, siracusaShip 1/10, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",6,27,21,102]
"ai_1": [1,5,12,0,0,5,8,"60f9ee96",81,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/46, sargonShip 0/4, siracusaShip 1/10, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",6,27,21,102]
},
{
"round": 12,
@@ -703,7 +703,7 @@
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2518112,"gold":126,"itemsEquipped":8,"kills":203,"leaks":87,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2489122,"gold":126,"itemsEquipped":8,"kills":204,"leaks":87,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
"lineup": [],
"bonds": ["kazimierzShip 0/51","kjeragShip 0/2","miraShip 0/7","preciShip 0/8","raidShip 0/34","skillfulShip 0/9","soloShip 0/3","steadShip 0/17","swiftShip 0/38","yanShip 0/6"]
},
@@ -717,7 +717,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1382343,"gold":114,"itemsEquipped":11,"kills":164,"leaks":69,"lpLost":32,"merges":3,"perfectRounds":5,"refreshes":19},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1382343,"gold":114,"itemsEquipped":11,"kills":164,"leaks":69,"lpLost":31,"merges":3,"perfectRounds":5,"refreshes":19},
"lineup": [],
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/55","sargonShip 0/4","siracusaShip 0/10","skillfulShip 0/6","swiftShip 0/23"]
}
@@ -794,13 +794,13 @@
{
"round": 10,
"ai_0": [1,3,12,0,0,5,8,"f8b9abf3",197,"deputShip 2/36, indomShip 1/8, kazimierzShip 0/2, kjeragShip 1/35, skillfulShip 1/57, soloShip 1/20, steadShip 1/33, suntShip 1/8, swiftShip 0/20",18,18,0,92],
"ai_1": [1,17,12,0,0,5,8,"6f5bb81b",124,"deputShip 2/40, indomShip 1/5, kazimierzShip 0/7, skillfulShip 1/26, soloShip 1/20, suntShip 0/16, swiftShip 0/21, victoriaShip 1/33",3,18,15,85],
"ai_2": [1,3,12,0,2,5,8,"b6b17abc",137,"deputShip 0/10, miraShip 1/4, siracusaShip 2/82, soloShip 1/22, suntShip 1/6, swiftShip 1/23, yanShip 0/1",8,18,10,86]
"ai_1": [1,16,12,0,0,5,8,"6f5bb81b",124,"deputShip 2/40, indomShip 1/5, kazimierzShip 0/7, skillfulShip 1/26, soloShip 1/20, suntShip 0/16, swiftShip 0/21, victoriaShip 1/33",3,18,15,85],
"ai_2": [1,2,12,0,2,5,8,"b6b17abc",137,"deputShip 0/10, miraShip 1/4, siracusaShip 2/82, soloShip 1/22, suntShip 1/6, swiftShip 1/23, yanShip 0/1",8,18,10,86]
},
{
"round": 11,
"ai_0": [0,0,12,0,0,5,0,"811c9dc5",0,"deputShip 0/38, indomShip 0/10, kazimierzShip 0/2, kjeragShip 0/41, skillfulShip 0/65, soloShip 0/22, steadShip 0/42, suntShip 0/10, swiftShip 0/26",22,25,3,103],
"ai_1": [1,7,12,0,0,5,8,"a9a4015c",177,"deputShip 2/45, indomShip 1/28, kazimierzShip 0/7, skillfulShip 1/30, soloShip 1/24, steadShip 1/8, suntShip 0/16, swiftShip 0/21, victoriaShip 1/42",7,25,18,98],
"ai_1": [1,6,12,0,0,5,8,"a9a4015c",177,"deputShip 2/45, indomShip 1/28, kazimierzShip 0/7, skillfulShip 1/30, soloShip 1/24, steadShip 1/8, suntShip 0/16, swiftShip 0/21, victoriaShip 1/42",7,25,18,98],
"ai_2": [0,0,14,0,0,5,0,"811c9dc5",0,"deputShip 0/10, miraShip 0/7, preciShip 0/5, siracusaShip 0/104, soloShip 0/22, suntShip 0/6, swiftShip 0/28, yanShip 0/1",9,25,16,100]
},
{
@@ -821,7 +821,7 @@
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 2,
"reward": 213,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4040284,"gold":103,"itemsEquipped":14,"kills":258,"leaks":32,"lpLost":23,"merges":3,"perfectRounds":4,"refreshes":10},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3843209,"gold":103,"itemsEquipped":14,"kills":256,"leaks":32,"lpLost":23,"merges":3,"perfectRounds":4,"refreshes":10},
"lineup": [],
"bonds": ["deputShip 0/38","indomShip 0/10","kazimierzShip 0/2","kjeragShip 0/41","skillfulShip 0/65","soloShip 0/22","steadShip 0/42","suntShip 0/10","swiftShip 0/26"]
},
@@ -835,7 +835,7 @@
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1816653,"gold":111,"itemsEquipped":10,"kills":244,"leaks":113,"lpLost":48,"merges":3,"perfectRounds":5,"refreshes":17},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1816653,"gold":111,"itemsEquipped":10,"kills":244,"leaks":113,"lpLost":49,"merges":3,"perfectRounds":5,"refreshes":17},
"lineup": [],
"bonds": ["deputShip 0/48","indomShip 0/54","kazimierzShip 0/11","skillfulShip 0/32","soloShip 0/26","steadShip 0/8","suntShip 0/16","swiftShip 0/27","victoriaShip 0/49"]
},
@@ -849,7 +849,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 213,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2018115,"gold":100,"itemsEquipped":7,"kills":214,"leaks":56,"lpLost":34,"merges":3,"perfectRounds":3,"refreshes":22},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2018115,"gold":100,"itemsEquipped":7,"kills":214,"leaks":56,"lpLost":35,"merges":3,"perfectRounds":3,"refreshes":22},
"lineup": [],
"bonds": ["deputShip 0/10","miraShip 0/7","preciShip 0/5","siracusaShip 0/104","soloShip 0/22","suntShip 0/6","swiftShip 0/28","yanShip 0/1"]
}
@@ -972,7 +972,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1696544,"gold":140,"itemsEquipped":25,"kills":325,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1671565,"gold":140,"itemsEquipped":25,"kills":325,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
"lineup": ["6_08_a@11,6[3_03_e_a+3_02_e_a]","6_12_a@11,7[4_08_e_a+6_07_e_b]","2_14_b@10,3","2_17_b@10,4[4_02_e_b+5_01_e_a]","2_08_a@9,3[4_04_e_a+1_01_e_b]","6_06_a@9,4[3_08_e_a+3_01_e_b]","1_06_b@9,8","5_02_b@9,9[5_02_e_a+3_06_e_b]"],
"bonds": ["deputShip 1/61","emptyShip 1/6","kjeragShip 0/7","preciShip 2/158","sargonShip 1/172","steadShip 1/88"]
},
@@ -1000,7 +1000,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":282,"bossDamage":72960,"dmgDealt":1633867,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
"stats": {"activatedLayers":282,"bossDamage":72960,"dmgDealt":1646854,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
"lineup": ["4_25_a@12,3","1_07_b@11,7[1_01_e_a+1_05_e_a]","5_20_a@11,8[3_02_e_a+2_01_e_b]","6_18_a@10,3[2_01_e_b+4_03_e_b]","4_10_a@10,7[4_03_e_b]","3_18_b@9,4[3_07_e_b+3_11_e_a]","6_05_a@9,8","2_16_b@9,9[5_01_e_a+4_02_e_a]"],
"bonds": ["sargonShip 0/8","siracusaShip 2/258","swiftShip 1/24","yanShip 0/1"]
},
@@ -1030,109 +1030,102 @@
"combat": "client",
"boost": null,
"setup": {"stageId":"act2autochess_m01","bossId":"boss_1","hiddenBossId":"boss_8","factions":["DOT","INVISIBLE","REFLECTION"]},
"end": {"victory":false,"roundsPassed":11,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":117800},
"rng": {"setup":25,"shop":460,"waves":0,"draft":65,"bots":18743,"meta":57},
"end": {"victory":false,"roundsPassed":10,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":110050},
"rng": {"setup":25,"shop":504,"waves":0,"draft":65,"bots":17663,"meta":57},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{
"round": 1,
"ai_0": [1,45,4,0,0,1,2,"cf5de4b4",0,"kazimierzShip 0/2, swiftShip 0/2",5,5,0,4],
"ai_1": [1,26,4,0,0,1,2,"59374416",0,"kazimierzShip 0/2, sargonShip 0/2, swiftShip 0/2",5,5,0,4],
"ai_2": [1,28,4,0,0,1,2,"eb0c1458",0,"deputShip 0/2, victoriaShip 0/2",2,5,3,4],
"ai_2": [1,25,4,0,0,1,2,"eb0c1458",0,"deputShip 0/2, victoriaShip 0/2",2,5,3,4],
"ai_3": [1,26,4,0,0,1,2,"ba03322a",0,"soloShip 1/0",5,5,0,4]
},
{
"round": 2,
"ai_0": [1,45,5,0,0,1,4,"62de86cd",4,"kazimierzShip 0/4, swiftShip 1/4",8,8,0,9],
"ai_1": [1,26,5,0,0,1,4,"a2ec5116",5,"kazimierzShip 0/5, sargonShip 0/2, soloShip 1/0, swiftShip 1/5",8,8,0,9],
"ai_2": [1,28,5,0,0,1,3,"70d74d61",0,"deputShip 0/4, kjeragShip 0/2, soloShip 1/0, victoriaShip 0/6",7,8,1,9],
"ai_2": [1,24,5,0,0,1,3,"70d74d61",0,"deputShip 0/4, kjeragShip 0/2, soloShip 1/0, victoriaShip 0/6",7,8,1,9],
"ai_3": [1,26,5,0,0,1,4,"9e98d014",0,"sargonShip 0/2, soloShip 1/0",8,8,0,9]
},
{
"round": 3,
"ai_0": [1,45,6,0,1,1,7,"a7d37d87",4,"arcaneShip 0/2, kazimierzShip 0/4, preciShip 0/2, swiftShip 1/4",9,9,0,15],
"ai_1": [1,26,6,0,1,1,7,"bf8932c2",5,"kazimierzShip 0/5, kjeragShip 0/2, sargonShip 0/4, soloShip 1/0, steadShip 0/2, swiftShip 1/5",11,11,0,15],
"ai_2": [1,28,6,0,1,1,6,"7363c3d8",0,"deputShip 0/4, kazimierzShip 0/2, kjeragShip 0/8, soloShip 1/0, steadShip 0/4, swiftShip 0/2, victoriaShip 0/8, yanShip 0/1",9,9,0,15],
"ai_2": [1,24,6,0,1,1,6,"7363c3d8",0,"deputShip 0/4, kazimierzShip 0/2, kjeragShip 0/8, soloShip 1/0, steadShip 0/4, swiftShip 0/2, victoriaShip 0/8, yanShip 0/1",9,9,0,15],
"ai_3": [1,26,6,0,2,1,7,"5051466d",1,"arcaneShip 0/2, preciShip 0/2, sargonShip 0/2, soloShip 1/1",9,9,0,15]
},
{
"round": 4,
"ai_0": [1,45,8,0,1,2,8,"822a03c7",6,"arcaneShip 0/8, kazimierzShip 0/4, preciShip 0/8, swiftShip 1/6",10,10,0,23],
"ai_1": [1,26,8,0,1,2,8,"f1865800",7,"arcaneShip 1/2, kazimierzShip 0/5, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, soloShip 1/0, steadShip 0/2, swiftShip 1/5",11,12,1,21],
"ai_2": [1,28,8,0,3,2,8,"38880204",9,"deputShip 1/7, kazimierzShip 0/2, kjeragShip 0/10, soloShip 1/2, steadShip 0/4, swiftShip 0/2, victoriaShip 0/10, yanShip 0/4",10,10,0,22],
"ai_3": [1,26,9,0,0,2,8,"00097ea6",4,"arcaneShip 1/3, preciShip 0/2, sargonShip 0/2, siracusaShip 1/0, soloShip 1/1",6,10,4,24]
"ai_1": [1,25,8,0,1,2,8,"f1865800",7,"arcaneShip 1/2, kazimierzShip 0/5, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, soloShip 1/0, steadShip 0/2, swiftShip 1/5",11,12,1,21],
"ai_2": [1,24,8,0,1,2,8,"38880204",9,"deputShip 1/7, kazimierzShip 0/2, kjeragShip 0/10, soloShip 1/2, steadShip 0/4, swiftShip 0/2, victoriaShip 0/10, yanShip 0/4",10,10,0,22],
"ai_3": [1,24,9,0,0,2,8,"00097ea6",4,"arcaneShip 1/3, preciShip 0/2, sargonShip 0/2, siracusaShip 1/0, soloShip 1/1",6,10,4,24]
},
{
"round": 5,
"ai_0": [1,35,9,0,0,3,8,"e59cd7fb",8,"arcaneShip 0/8, kazimierzShip 0/4, preciShip 0/8, swiftShip 1/8",5,17,12,32],
"ai_1": [1,16,9,0,0,3,8,"0a827c34",8,"arcaneShip 1/2, kazimierzShip 0/5, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, soloShip 1/0, steadShip 0/2, swiftShip 1/6",5,17,12,30],
"ai_2": [1,21,11,0,0,3,8,"26900631",22,"deputShip 1/10, kazimierzShip 0/4, kjeragShip 0/10, skillfulShip 0/2, soloShip 1/4, steadShip 0/4, swiftShip 1/8, victoriaShip 0/10, yanShip 0/4",10,17,7,32],
"ai_3": [1,16,9,0,0,3,8,"2cd3b560",5,"arcaneShip 1/4, preciShip 0/2, sargonShip 0/2, siracusaShip 1/0, soloShip 1/1",2,17,15,33]
"ai_1": [1,15,9,0,0,3,8,"0a827c34",8,"arcaneShip 1/2, kazimierzShip 0/5, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, soloShip 1/0, steadShip 0/2, swiftShip 1/6",5,17,12,30],
"ai_2": [1,14,9,0,0,3,8,"6b698137",20,"deputShip 1/12, kazimierzShip 0/2, kjeragShip 0/12, soloShip 1/4, steadShip 0/4, swiftShip 1/4, victoriaShip 0/12, yanShip 0/4",7,17,10,31],
"ai_3": [1,14,9,0,0,3,8,"2cd3b560",5,"arcaneShip 1/4, preciShip 0/2, sargonShip 0/2, siracusaShip 1/0, soloShip 1/1",2,17,15,33]
},
{
"round": 6,
"ai_0": [1,35,9,0,0,3,8,"844b219a",33,"arcaneShip 0/8, kazimierzShip 0/4, kjeragShip 0/5, preciShip 2/20, suntShip 1/0, swiftShip 1/13",14,14,0,41],
"ai_1": [1,16,9,0,1,3,8,"3e9da7ea",26,"arcaneShip 1/2, kazimierzShip 1/11, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, siracusaShip 0/6, soloShip 1/0, steadShip 0/2, swiftShip 1/13",11,14,3,40],
"ai_2": [1,21,9,0,0,3,8,"524a7cff",51,"deputShip 1/12, egirShip 0/2, kazimierzShip 0/4, kjeragShip 0/12, raidShip 0/2, skillfulShip 0/4, soloShip 1/6, steadShip 2/18, swiftShip 1/15, victoriaShip 0/10, yanShip 0/4",14,14,0,41],
"ai_3": [1,16,10,0,0,3,8,"ae19c686",28,"arcaneShip 2/9, deputShip 1/2, preciShip 0/2, sargonShip 0/2, siracusaShip 1/11, soloShip 1/6",8,14,6,45]
"ai_0": [1,35,9,0,2,3,8,"03ec72c2",23,"arcaneShip 0/8, kazimierzShip 0/4, kjeragShip 0/5, preciShip 1/15, swiftShip 1/8",13,14,1,40],
"ai_1": [1,14,9,0,0,3,8,"390c0bbe",27,"arcaneShip 1/2, kazimierzShip 1/9, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, siracusaShip 0/6, soloShip 1/0, steadShip 0/2, swiftShip 1/16",13,14,1,39],
"ai_2": [1,14,9,0,0,3,8,"259dd8c0",49,"deputShip 1/14, egirShip 0/2, kazimierzShip 0/2, kjeragShip 0/14, raidShip 0/2, soloShip 1/8, steadShip 2/21, swiftShip 1/6, victoriaShip 0/12, yanShip 0/4",14,14,0,40],
"ai_3": [1,13,10,0,0,3,8,"22246bd2",20,"arcaneShip 2/7, preciShip 0/2, sargonShip 0/2, siracusaShip 1/9, skillfulShip 1/0, soloShip 1/4",11,14,3,45]
},
{
"round": 7,
"ai_0": [1,25,10,0,0,4,8,"fa19dc02",65,"arcaneShip 0/8, kazimierzShip 1/14, kjeragShip 0/5, preciShip 2/27, suntShip 1/0, swiftShip 1/24",7,21,14,52],
"ai_1": [1,6,11,0,0,4,8,"bad83463",33,"arcaneShip 2/2, kazimierzShip 1/18, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, steadShip 0/2, swiftShip 1/13",5,21,16,51],
"ai_2": [1,18,10,0,2,4,8,"3c8cf7f7",81,"deputShip 1/16, egirShip 0/4, kazimierzShip 0/4, kjeragShip 0/12, raidShip 0/4, skillfulShip 0/4, soloShip 1/10, steadShip 2/36, swiftShip 1/19, victoriaShip 0/12, yanShip 0/4",18,21,3,51],
"ai_3": [1,6,11,0,0,4,8,"7ba63894",45,"arcaneShip 2/14, deputShip 1/4, preciShip 0/2, sargonShip 0/2, siracusaShip 1/19, soloShip 1/8",2,21,19,56]
"ai_0": [1,25,12,0,0,4,8,"81d63726",45,"arcaneShip 0/8, kazimierzShip 1/14, kjeragShip 0/5, preciShip 1/17, suntShip 1/0, swiftShip 1/14",6,21,15,53],
"ai_1": [1,4,10,0,0,4,8,"f1d0d89f",42,"arcaneShip 0/2, deputShip 1/2, kazimierzShip 1/19, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, siracusaShip 0/6, soloShip 1/2, steadShip 0/2, swiftShip 1/19",5,21,16,50],
"ai_2": [1,13,10,0,0,4,8,"1cee0d77",78,"deputShip 1/16, egirShip 0/4, kazimierzShip 0/2, kjeragShip 0/16, raidShip 0/4, soloShip 1/12, steadShip 2/42, swiftShip 1/8, victoriaShip 0/12, yanShip 0/4",20,21,1,51],
"ai_3": [1,3,11,0,0,4,8,"cae1e8d2",28,"arcaneShip 2/7, preciShip 0/2, sargonShip 0/2, siracusaShip 1/15, skillfulShip 1/2, soloShip 1/4",3,21,18,55]
},
{
"round": 8,
"ai_0": [1,18,11,0,0,4,8,"78106ce5",96,"arcaneShip 2/11, kazimierzShip 1/22, kjeragShip 0/5, preciShip 2/34, suntShip 1/0, swiftShip 1/29",2,9,7,63],
"ai_1": [0,0,11,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",0,9,9,62],
"ai_2": [1,15,13,0,0,4,8,"eff86771",116,"deputShip 1/20, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/2, raidShip 0/6, skillfulShip 0/4, soloShip 1/14, steadShip 2/49, swiftShip 1/33, victoriaShip 0/14, yanShip 0/4",6,9,3,64],
"ai_3": [0,0,12,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",2,9,7,68]
"ai_0": [1,19,11,0,0,4,8,"cdf4cd04",43,"arcaneShip 2/8, kazimierzShip 1/18, kjeragShip 0/10, preciShip 1/17, skillfulShip 0/5, suntShip 1/0, swiftShip 0/14",3,9,6,63],
"ai_1": [0,0,11,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/31, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, skillfulShip 0/4, soloShip 0/2, steadShip 0/2, swiftShip 0/34",1,9,8,62],
"ai_2": [1,9,11,0,0,4,8,"40e6f752",131,"deputShip 1/20, egirShip 0/4, indomShip 0/2, kazimierzShip 0/2, kjeragShip 1/23, raidShip 0/4, skillfulShip 0/5, soloShip 1/16, steadShip 1/52, suntShip 1/2, swiftShip 0/8, victoriaShip 1/18, visiShip 0/2, yanShip 0/12",5,9,4,62],
"ai_3": [0,0,12,0,0,4,0,"811c9dc5",0,"arcaneShip 0/13, deputShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/25, skillfulShip 0/4, soloShip 0/8",0,9,9,67]
},
{
"round": 9,
"ai_0": [1,18,12,0,1,4,8,"a359f7ee",124,"arcaneShip 1/11, indomShip 1/2, kazimierzShip 1/34, kjeragShip 0/5, preciShip 2/43, suntShip 1/0, swiftShip 1/34",6,19,13,76],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",null,null,null,62],
"ai_2": [1,15,12,0,1,4,8,"8df12b45",146,"deputShip 0/22, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/2, preciShip 0/2, raidShip 0/6, skillfulShip 1/6, soloShip 1/14, steadShip 1/54, suntShip 1/0, swiftShip 1/47, victoriaShip 1/25, yanShip 0/13",19,19,0,76],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",null,null,null,68]
"ai_0": [1,9,12,0,0,4,8,"4cd81c83",77,"arcaneShip 1/8, kazimierzShip 1/34, kjeragShip 1/10, preciShip 2/25, skillfulShip 0/5, suntShip 1/0, swiftShip 0/14",3,19,16,74],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/31, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, skillfulShip 0/4, soloShip 0/2, steadShip 0/2, swiftShip 0/34",null,null,null,62],
"ai_2": [1,9,12,0,2,4,8,"614f615e",185,"deputShip 1/26, egirShip 0/4, indomShip 1/10, investShip 0/2, kazimierzShip 0/2, kjeragShip 1/29, raidShip 0/4, skillfulShip 1/9, soloShip 1/18, steadShip 1/63, suntShip 0/2, swiftShip 0/10, victoriaShip 1/30, visiShip 0/2, yanShip 0/12",19,19,0,74],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/13, deputShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/25, skillfulShip 0/4, soloShip 0/8",null,null,null,67]
},
{
"round": 10,
"ai_0": [1,18,13,0,0,5,8,"1c690635",148,"arcaneShip 1/11, indomShip 1/4, kazimierzShip 1/42, kjeragShip 0/5, preciShip 2/52, suntShip 1/0, swiftShip 1/39",16,25,9,88],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",null,null,null,62],
"ai_2": [1,15,13,0,0,5,8,"0e4a12ab",176,"deputShip 0/24, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/4, preciShip 0/2, raidShip 0/6, skillfulShip 1/10, soloShip 1/14, steadShip 1/60, suntShip 1/0, swiftShip 1/61, victoriaShip 1/31, yanShip 0/15",25,25,0,89],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",null,null,null,68]
"ai_0": [1,5,12,0,0,5,8,"51650018",102,"arcaneShip 1/8, kazimierzShip 1/46, kjeragShip 1/15, preciShip 2/33, skillfulShip 0/10, suntShip 1/0, swiftShip 0/14",16,25,9,86],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/31, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, skillfulShip 0/4, soloShip 0/2, steadShip 0/2, swiftShip 0/34",null,null,null,62],
"ai_2": [1,9,14,0,2,5,8,"76f0edb3",236,"deputShip 1/30, egirShip 0/4, indomShip 1/22, investShip 0/4, kazimierzShip 0/2, kjeragShip 1/35, raidShip 0/4, skillfulShip 1/11, soloShip 1/20, steadShip 1/72, suntShip 0/2, swiftShip 0/12, victoriaShip 1/46, visiShip 0/4, yanShip 0/12",25,25,0,90],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/13, deputShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/25, skillfulShip 0/4, soloShip 0/8",null,null,null,67]
},
{
"round": 11,
"ai_0": [1,8,12,0,1,5,8,"88de4e65",216,"arcaneShip 1/11, indomShip 0/4, kazimierzShip 1/68, kjeragShip 0/5, preciShip 2/74, raidShip 1/12, suntShip 1/0, swiftShip 1/51",13,25,12,100],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",null,null,null,62],
"ai_2": [1,5,12,0,0,5,8,"052cd291",171,"deputShip 0/24, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/16, lateranoShip 0/4, preciShip 0/4, raidShip 0/6, skillfulShip 1/12, soloShip 1/14, steadShip 0/62, suntShip 1/0, swiftShip 1/75, victoriaShip 1/37, yanShip 1/33",4,25,21,100],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",null,null,null,68]
},
{
"round": 12,
"ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"arcaneShip 0/11, indomShip 0/4, kazimierzShip 0/84, kjeragShip 0/5, preciShip 0/83, raidShip 0/24, swiftShip 0/56",14,47,33,114],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",null,null,null,62],
"ai_2": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/26, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/18, lateranoShip 0/4, preciShip 0/6, raidShip 0/6, skillfulShip 0/14, soloShip 0/14, steadShip 0/62, swiftShip 0/89, victoriaShip 0/45, yanShip 0/35",10,47,37,113],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",null,null,null,68]
"ai_0": [0,0,12,0,0,5,0,"811c9dc5",0,"arcaneShip 0/14, kazimierzShip 0/54, kjeragShip 0/15, preciShip 0/42, skillfulShip 0/10, swiftShip 0/19",8,25,17,99],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/31, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, skillfulShip 0/4, soloShip 0/2, steadShip 0/2, swiftShip 0/34",null,null,null,62],
"ai_2": [0,0,14,0,0,5,0,"811c9dc5",0,"deputShip 0/34, egirShip 0/4, indomShip 0/34, investShip 0/6, kazimierzShip 0/2, kjeragShip 0/41, raidShip 0/4, skillfulShip 0/13, soloShip 0/22, steadShip 0/82, suntShip 0/2, swiftShip 0/14, victoriaShip 0/62, visiShip 0/6, yanShip 0/12",14,25,11,102],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/13, deputShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/25, skillfulShip 0/4, soloShip 0/8",null,null,null,67]
}
],
"players": {
"ai_0": {
"alive": false,
"victory": false,
"roundsPassed": 11,
"eliminatedRound": 12,
"roundsPassed": 10,
"eliminatedRound": 11,
"lp": 0,
"bandId": "band_sarkazb",
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2971324,"gold":114,"itemsEquipped":8,"kills":109,"leaks":100,"lpLost":47,"merges":5,"perfectRounds":5,"refreshes":9},
"reward": 213,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1580977,"gold":99,"itemsEquipped":11,"kills":86,"leaks":76,"lpLost":50,"merges":2,"perfectRounds":4,"refreshes":12},
"lineup": [],
"bonds": ["arcaneShip 0/11","indomShip 0/4","kazimierzShip 0/84","kjeragShip 0/5","preciShip 0/83","raidShip 0/24","swiftShip 0/56"]
"bonds": ["arcaneShip 0/14","kazimierzShip 0/54","kjeragShip 0/15","preciShip 0/42","skillfulShip 0/10","swiftShip 0/19"]
},
"ai_1": {
"alive": false,
@@ -1144,23 +1137,23 @@
"title": null,
"trophies": 1,
"reward": 149,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":650327,"gold":62,"itemsEquipped":3,"kills":57,"leaks":41,"lpLost":29,"merges":1,"perfectRounds":3,"refreshes":5},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":794538,"gold":62,"itemsEquipped":5,"kills":59,"leaks":38,"lpLost":30,"merges":2,"perfectRounds":3,"refreshes":5},
"lineup": [],
"bonds": ["arcaneShip 0/2","kazimierzShip 0/25","kjeragShip 0/7","preciShip 0/2","sargonShip 0/8","siracusaShip 0/12","skillfulShip 0/5","steadShip 0/2","swiftShip 0/19"]
"bonds": ["arcaneShip 0/2","deputShip 0/6","kazimierzShip 0/31","kjeragShip 0/2","preciShip 0/2","sargonShip 0/8","siracusaShip 0/6","skillfulShip 0/4","soloShip 0/2","steadShip 0/2","swiftShip 0/34"]
},
"ai_2": {
"alive": false,
"victory": false,
"roundsPassed": 11,
"eliminatedRound": 12,
"roundsPassed": 10,
"eliminatedRound": 11,
"lp": 0,
"bandId": "band_bldsk",
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4175013,"gold":113,"itemsEquipped":11,"kills":170,"leaks":75,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":9},
"reward": 213,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3143544,"gold":102,"itemsEquipped":10,"kills":146,"leaks":30,"lpLost":29,"merges":2,"perfectRounds":5,"refreshes":14},
"lineup": [],
"bonds": ["deputShip 0/26","egirShip 0/6","kazimierzShip 0/4","kjeragShip 0/18","lateranoShip 0/4","preciShip 0/6","raidShip 0/6","skillfulShip 0/14","soloShip 0/14","steadShip 0/62","swiftShip 0/89","victoriaShip 0/45","yanShip 0/35"]
"bonds": ["deputShip 0/34","egirShip 0/4","indomShip 0/34","investShip 0/6","kazimierzShip 0/2","kjeragShip 0/41","raidShip 0/4","skillfulShip 0/13","soloShip 0/22","steadShip 0/82","suntShip 0/2","swiftShip 0/14","victoriaShip 0/62","visiShip 0/6","yanShip 0/12"]
},
"ai_3": {
"alive": false,
@@ -1169,12 +1162,12 @@
"eliminatedRound": 8,
"lp": 0,
"bandId": "band_cathy",
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 1,
"reward": 149,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":627044,"gold":68,"itemsEquipped":10,"kills":45,"leaks":51,"lpLost":27,"merges":1,"perfectRounds":3,"refreshes":6},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":535076,"gold":67,"itemsEquipped":9,"kills":44,"leaks":49,"lpLost":32,"merges":0,"perfectRounds":3,"refreshes":8},
"lineup": [],
"bonds": ["arcaneShip 0/18","deputShip 0/6","miraShip 0/4","preciShip 0/4","sargonShip 0/2","siracusaShip 0/31","skillfulShip 0/2","soloShip 0/10"]
"bonds": ["arcaneShip 0/13","deputShip 0/4","preciShip 0/4","sargonShip 0/2","siracusaShip 0/25","skillfulShip 0/4","soloShip 0/8"]
}
}
},
@@ -1188,8 +1181,8 @@
"combat": "server",
"boost": null,
"setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":6,"bossPoolLeft":0,"virtualMs":121400},
"rng": {"setup":25,"shop":448,"waves":0,"draft":34,"bots":18063,"meta":84},
"end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":602074,"virtualMs":121050},
"rng": {"setup":25,"shop":420,"waves":0,"draft":34,"bots":17815,"meta":82},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{
@@ -1230,62 +1223,62 @@
{
"round": 8,
"ai_0": [1,19,11,0,0,4,8,"7c223b06",66,"arcaneShip 1/6, emptyShip 1/0, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/36, soloShip 1/11, steadShip 0/2, swiftShip 1/6",23,23,0,64],
"ai_1": [1,17,12,0,0,4,8,"2243ec88",26,"arcaneShip 1/8, indomShip 1/1, kjeragShip 0/4, preciShip 0/2, siracusaShip 1/10, soloShip 1/0, steadShip 2/7",18,23,5,61]
"ai_1": [1,16,12,0,0,4,8,"2243ec88",26,"arcaneShip 1/8, indomShip 1/1, kjeragShip 0/4, preciShip 0/2, siracusaShip 1/10, soloShip 1/0, steadShip 2/7",18,23,5,61]
},
{
"round": 9,
"ai_0": [1,19,12,0,1,4,8,"6dfc43a6",78,"arcaneShip 2/12, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/42, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",16,16,0,77],
"ai_1": [1,17,12,0,0,4,8,"bbe510e7",82,"arcaneShip 1/16, indomShip 1/10, kjeragShip 0/8, preciShip 1/22, siracusaShip 1/20, soloShip 1/0, steadShip 1/14, victoriaShip 0/8, visiShip 0/8",15,15,0,74]
"ai_1": [1,16,12,0,0,4,8,"bbe510e7",82,"arcaneShip 1/16, indomShip 1/10, kjeragShip 0/8, preciShip 1/22, siracusaShip 1/20, soloShip 1/0, steadShip 1/14, victoriaShip 0/8, visiShip 0/8",15,15,0,74]
},
{
"round": 10,
"ai_0": [1,18,13,0,0,5,8,"e24edc84",82,"arcaneShip 2/14, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/44, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",23,24,1,89],
"ai_1": [1,17,12,0,0,5,8,"f2b57087",100,"arcaneShip 1/16, indomShip 1/12, kjeragShip 0/8, miraShip 0/3, preciShip 1/35, siracusaShip 1/20, soloShip 1/0, steadShip 1/17, suntShip 1/0, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",23,23,0,86]
"ai_1": [1,16,12,0,0,5,8,"f2b57087",100,"arcaneShip 1/16, indomShip 1/12, kjeragShip 0/8, miraShip 0/3, preciShip 1/35, siracusaShip 1/20, soloShip 1/0, steadShip 1/17, suntShip 1/0, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",23,23,0,86]
},
{
"round": 11,
"ai_0": [1,17,12,0,0,5,8,"65c25a4f",94,"arcaneShip 2/20, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/50, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",22,23,1,101],
"ai_1": [1,13,12,0,0,5,8,"b5188f04",124,"arcaneShip 2/26, indomShip 1/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/54, siracusaShip 1/30, soloShip 1/0, steadShip 0/17, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",19,23,4,99]
"ai_1": [1,12,12,0,0,5,8,"b5188f04",124,"arcaneShip 2/26, indomShip 1/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/54, siracusaShip 1/30, soloShip 1/0, steadShip 0/17, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",19,23,4,99]
},
{
"round": 12,
"ai_0": [1,7,12,0,0,5,8,"b6fcbbbb",106,"arcaneShip 2/26, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/9, siracusaShip 1/54, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",23,46,23,114],
"ai_1": [1,3,12,0,0,6,8,"f5a033f5",153,"arcaneShip 2/26, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/78, siracusaShip 1/30, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",27,46,19,111]
"ai_1": [1,2,12,0,0,6,8,"f5a033f5",153,"arcaneShip 2/26, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/78, siracusaShip 1/30, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",27,46,19,111]
},
{
"round": 13,
"ai_0": [1,7,12,0,0,6,8,"3d99f43b",110,"arcaneShip 2/28, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/11, siracusaShip 1/54, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",14,39,25,124],
"ai_1": [1,3,12,0,0,6,8,"e9f74be3",189,"arcaneShip 2/34, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/92, siracusaShip 1/44, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",39,39,0,124]
"ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/28, indomShip 0/8, kjeragShip 0/7, preciShip 0/11, siracusaShip 0/54, soloShip 0/11, steadShip 0/2, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",14,39,25,124],
"ai_1": [1,2,12,0,0,6,8,"e9f74be3",189,"arcaneShip 2/34, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/92, siracusaShip 1/44, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",39,39,0,124]
}
],
"players": {
"ai_0": {
"alive": true,
"victory": true,
"roundsPassed": 14,
"eliminatedRound": null,
"lp": 4,
"alive": false,
"victory": false,
"roundsPassed": 12,
"eliminatedRound": 13,
"lp": 0,
"bandId": "band_doberm",
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":122,"bossDamage":108916,"dmgDealt":2898036,"gold":136,"itemsEquipped":15,"kills":232,"leaks":65,"lpLost":23,"merges":4,"perfectRounds":7,"refreshes":18},
"lineup": ["5_10_a@11,3[5_02_e_a+1_02_e_b]","3_10_b@11,6","6_12_a@11,7[3_06_e_a+1_05_e_b]","1_17_a@10,3","4_10_a@10,7[1_02_e_b+1_05_e_b]","1_08_b@10,8[3_11_e_a+4_04_e_b]","4_02_a@9,8","5_14_a@9,9[4_10_e_a+4_05_e_a]"],
"bonds": ["arcaneShip 2/34","emptyShip 1/0","indomShip 0/8","kjeragShip 0/7","miraShip 1/0","preciShip 1/11","siracusaShip 1/60","soloShip 1/11","steadShip 0/2","suntShip 1/0","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"]
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 280,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2606442,"gold":124,"itemsEquipped":13,"kills":213,"leaks":65,"lpLost":33,"merges":4,"perfectRounds":7,"refreshes":15},
"lineup": [],
"bonds": ["arcaneShip 0/28","indomShip 0/8","kjeragShip 0/7","preciShip 0/11","siracusaShip 0/54","soloShip 0/11","steadShip 0/2","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8"]
},
"ai_1": {
"alive": true,
"victory": true,
"roundsPassed": 14,
"victory": false,
"roundsPassed": 13,
"eliminatedRound": null,
"lp": 2,
"lp": 0,
"bandId": "band_malkie",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":200,"bossDamage":566084,"dmgDealt":4442196,"gold":137,"itemsEquipped":22,"kills":254,"leaks":39,"lpLost":25,"merges":4,"perfectRounds":9,"refreshes":19},
"lineup": ["5_23_a@11,3","1_17_a@11,6[5_07_e_a+1_02_e_b]","5_12_a@11,7[3_01_e_a+5_09_e_a]","5_02_a@10,3[3_06_e_a+4_08_e_b]","4_10_a@10,7[1_01_e_a+1_05_e_b]","2_16_b@10,8[3_11_e_a+3_08_e_b]","6_03_a@9,8[5_03_e_b]","1_08_b@9,9"],
"bonds": ["arcaneShip 2/42","indomShip 0/14","kazimierzShip 0/8","kjeragShip 0/16","miraShip 0/3","preciShip 1/92","siracusaShip 1/54","soloShip 1/0","steadShip 0/19","suntShip 1/0","swiftShip 0/8","victoriaShip 0/8","visiShip 0/8","yanShip 1/12"]
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 3,
"reward": 320,
"stats": {"activatedLayers":215,"bossDamage":72926,"dmgDealt":3055823,"gold":137,"itemsEquipped":23,"kills":231,"leaks":39,"lpLost":26,"merges":4,"perfectRounds":9,"refreshes":19},
"lineup": ["5_02_a@11,3[4_08_e_b]","5_12_a@11,6[3_01_e_a+5_03_e_a]","1_17_b@11,7[5_07_e_a]","6_03_a@11,8[1_02_e_b+3_06_e_b]","4_10_a@10,7[1_01_e_a+1_05_e_b]","2_16_b@10,8[3_11_e_a+3_08_e_b]","5_23_a@9,3","1_08_a@9,9"],
"bonds": ["arcaneShip 2/48","indomShip 0/14","kazimierzShip 0/8","kjeragShip 0/16","miraShip 1/3","preciShip 1/96","siracusaShip 1/54","soloShip 1/0","steadShip 0/19","suntShip 1/0","swiftShip 0/8","victoriaShip 0/8","visiShip 0/8","yanShip 1/14"]
}
}
},
+129
View File
@@ -0,0 +1,129 @@
// GitHub #41 (item 3): the 联防 field was the round's own stage — its water, crates and devices included — opened to both
// halves. Official: act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer = level_act1autochess_
// escaped_single / _multi, the level of the 联防 battle and its map (two road halves joined at col 10). One helper →
// escaped_single (enemies enter at col 10), two helpers → escaped_multi (enemies enter at col 18 and pass (9,10)); the
// helpers' pieces stand on their prep tiles ("按休整期位置部署在场"), the first of two shifted 8 columns onto the right half
// ("率先迎敌(即位于右侧阵地)"). data/stages.json holds both maps (kind 'unite'); server/match/unite.js uniteStageId.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { GEO, PHASE } from '../../shared/constants.js';
import { Battle } from '../../server/sim/Battle.js';
import { createBattleFromSpec } from '../../server/sim/spec.js';
import { DataSource } from '../../server/sim/simdata.js';
import { uniteStageId } from '../../server/match/unite.js';
import { FakeBattle } from './fakeBattle.js';
import { DATA, makeMatch, give, chessOfTier, legalTileFor, checkInvariants } from './harness.js';
/** A real battle that only ends by its time limit (the check follows the enemies, whatever the helpers do). */
class NoFinish extends Battle {
constructor(o) { super({ ...o, autoFinish: false }); }
}
/**
* Co-op on 战场#01 (its row 9 is fenced off at cols 5–7: "##Err###rrSrr###rrS##"): p_0 leaks 3 enemies, the other
* players are perfect — 1 helper with 2 humans, 2 helpers with 3. Each helper fields one ranged operator in its corner.
*/
function scenario({ humans, clientCombat }) {
const h = makeMatch({
mode: 'coop', humans, seed: 4101 + humans, fake: true, clientCombat,
script: (b) => (b.kind === 'normal' ? { leaks: { p_0: 3 } } : {}),
}).start();
const m = h.m;
h.toPrep(1);
h.setStage('act1autochess_m01');
const ranged = chessOfTier(1, (c) => c.position === 'RANGED').filter((x) => m.pool.has(x));
const helpers = [];
for (let i = 1; i < humans; i++) {
const ps = h.ps(`p_${i}`);
const id = ranged[i];
helpers.push({ ps, piece: give(m, ps, id, 'board', legalTileFor(m, ps, id)) });
}
h.drive(() => m.phase === PHASE.UNITE);
return { h, m, helpers };
}
/** The 联防 field's spec / options as the match built them, and a real battle over them. */
function uniteField(m, clientCombat) {
if (clientCombat) {
const f = m.fields[0];
return { opts: f.spec, battle: createBattleFromSpec(f.spec, new DataSource(DATA, null), { BattleClass: NoFinish, recordEvents: false }) };
}
const u = FakeBattle.instances.find((b) => b.kind === 'unite');
return { opts: u.opts, battle: new NoFinish({ ...u.opts, data: m.ds, logger: { warn() {}, error() {}, info() {}, debug() {} } }) };
}
for (const clientCombat of [true, false]) {
test(`联防 with 1 helper (${clientCombat ? 'client-side combat' : 'server-run'}): escaped_single, not the round's stage — enemies enter at col 10 and walk row 9`, () => {
const { m, helpers } = scenario({ humans: 2, clientCombat });
assert.deepEqual(m.unitePlan.helpers.map((p) => p.playerId), ['p_1']);
const { opts, battle: b } = uniteField(m, clientCombat);
assert.equal(opts.stageId, 'act1autochess_escaped_single');
assert.equal(uniteStageId(m.gd, 1), 'act1autochess_escaped_single');
assert.deepEqual(opts.rect, GEO.UNITE_RECT, 'the whole 19×21 map\'s field rows (both halves are road on it)');
assert.equal(b.stage.id, 'act1autochess_escaped_single');
assert.deepEqual(b.stage.devices, [], 'no crates or devices of 战场#01');
// the map: row 9 is road from the gate (9,10) to the objective (9,2); on 战场#01 cols 5–7 are fenced off
for (let c = 3; c <= 9; c++) assert.ok(b.grid.groundPassable(9, c), `(9,${c}) is road`);
assert.equal(m.stage.rows[9].slice(5, 8), '###', '战场#01 itself has no ground there');
for (const c of [19, 20]) assert.ok(!b.grid.groundPassable(9, c), `(9,${c}) is no ground`);
// the routes of escaped_single: every one starts at col 10
assert.ok(opts.routes.every((r) => (r.start ?? [r.startPosition?.row, r.startPosition?.col])[1] === 10));
// the helper's piece stands on its prep tile
const { ps, piece } = helpers[0];
const [r, c] = [...ps.board.entries()].find(([, p]) => p === piece)[0].split(',').map(Number);
b.step();
const u = b.allyUnits.find((x) => x.uid === piece.uid && x.ownerId === 'p_1');
assert.deepEqual([u.tileR, u.tileC], [r, c]);
// a walker crosses the tiles 战场#01 fences off
const crossed = new Set();
while (b.time < 60 && !b.finished) {
b.step();
for (const e of b.enemies) if (e.alive && e.motion !== 'FLY') crossed.add(`${Math.round(e.y)},${Math.round(e.x)}`);
}
assert.ok(['9,5', '9,6', '9,7'].some((k) => crossed.has(k)), `a walker on row 9 cols 5–7 (${[...crossed].sort().join(' ')})`);
assert.equal(b.errorCount || 0, 0);
checkInvariants(m);
m.dispose();
});
}
test('联防 with 2 helpers: escaped_multi — the first helper on the right half (col + 8), enemies enter at col 18 through (9,10); the client draws the field\'s map', () => {
const { h, m, helpers } = scenario({ humans: 3, clientCombat: false });
const order = m.unitePlan.helpers.map((p) => p.playerId);
assert.equal(order.length, 2);
const { opts, battle: b } = uniteField(m, false);
assert.equal(opts.stageId, 'act1autochess_escaped_multi');
assert.equal(b.stage.id, 'act1autochess_escaped_multi');
assert.deepEqual(opts.players.map((p) => [p.playerId, p.colOffset]), [[order[0], 8], [order[1], 0]]);
assert.ok(opts.routes.every((r) => r.start[1] === 18), 'every route enters at col 18');
assert.ok(opts.routes.filter((r) => r.motion === 'WALK').every((r) => r.checkpoints.some(([rr, cc]) => rr === 9 && cc === 10)), 'walkers pass (9,10)');
b.step();
for (const { ps, piece } of helpers) {
const [r, c] = [...ps.board.entries()].find(([, p]) => p === piece)[0].split(',').map(Number);
const u = b.allyUnits.find((x) => x.uid === piece.uid && x.ownerId === ps.playerId);
const off = ps.playerId === order[0] ? 8 : 0;
assert.deepEqual([u.tileR, u.tileC], [r, c + off], `${ps.playerId}: its prep tile${off ? ' on the right half' : ''}`);
assert.equal(b.stage.rows[r][c + off], 'r', 'a road tile of the 联防 map');
}
// what a watching browser receives: the m.field of the 联防 carries the map it is drawn on
m.handle('p_0', { t: 'g.watch', fieldId: 'u' });
const meta = h.lastTo('p_0', 'm.field');
assert.equal(meta && meta.stageId, 'act1autochess_escaped_multi');
assert.equal(m.stageId, 'act1autochess_m01', 'the match stage (m.public stageId, the boards) stays the round\'s');
checkInvariants(m);
m.dispose();
});
test('degraded data without the 联防 maps: the field keeps the round\'s stage', () => {
const stages = Object.fromEntries(Object.entries(DATA.stages).filter(([, s]) => s.kind !== 'unite'));
const h = makeMatch({ mode: 'coop', humans: 2, seed: 4199, fake: true, data: { ...DATA, stages }, script: (b) => (b.kind === 'normal' ? { leaks: { p_0: 2 } } : {}) }).start();
const m = h.m;
h.toPrep(1);
assert.equal(uniteStageId(m.gd, 1), null);
const ps = h.ps('p_1');
const id = chessOfTier(1, (c) => c.position === 'RANGED').find((x) => m.pool.has(x));
give(m, ps, id, 'board', legalTileFor(m, ps, id));
h.drive(() => m.phase === PHASE.UNITE);
assert.equal(FakeBattle.instances.find((b) => b.kind === 'unite').opts.stageId, m.stageId);
m.dispose();
});
+2
View File
@@ -223,6 +223,7 @@ describe('#7 audit: every stage × deploy field (server = official level data =
// buildableType (PRTS 深水区 地形信息 "地形机制:拒绝部署(待补充)" — player report #3 after 0.1.0, 战场#08's pool)
const ROLE = { trap_1105_accrate: 'block', trap_032_mound: 'block', trap_1106_achplat: 'platform', trap_040_canoe: 'water' };
for (const [id, st] of Object.entries(DATA.stages)) {
if (st.kind === 'unite') continue; // the 联防 maps: nobody deploys or prepares on them (no bench, no boss halves)
const lv = JSON.parse(readFileSync(levelFile(id), 'utf8'));
const map = lv.mapData.map, tiles = lv.mapData.tiles, H = map.length;
const dev = new Map();
@@ -259,6 +260,7 @@ describe('#7 audit: every stage × deploy field (server = official level data =
test('carry-over: a tile legal on the normal board is legal (same class) on both boss halves of every active stage', () => {
for (const [id, st] of Object.entries(DATA.stages)) {
if (st.kind === 'unite') continue; // the 联防 maps have no boss field
const n = buildDeployMap(st);
for (const field of ['bossL', 'bossR']) {
const b = buildDeployMap(st, { field });
+2 -1
View File
@@ -12,7 +12,8 @@ import { bossFieldPlacement } from '../../server/match/finalAssault.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const STAGES = JSON.parse(readFileSync(path.join(ROOT, 'data/stages.json'), 'utf8'));
const stageList = Object.values(STAGES.stages || STAGES);
// the battle stages (the two 联防 maps, kind 'unite', have no preview pen: nobody prepares on them)
const stageList = Object.values(STAGES.stages || STAGES).filter((s) => s.kind !== 'unite');
const stage = (STAGES.stages || STAGES).act2autochess_m01;
const E = (enemyKey, count, gate, t, extra = {}) => ({ enemyKey, count, gate, t, fly: false, elite: false, boss: false, source: 'wave', ...extra });
+3 -1
View File
@@ -26,9 +26,11 @@ describe('parseStage', () => {
assert.ok(t.alpha > 0 && t.alpha <= 1);
}
}
// the own board is always drawn and playable; hand slots are raised benches
// the own board is always drawn and playable; hand slots are raised benches (the two 联防 maps, kind 'unite', have
// no bench: nobody prepares on them)
for (let r = 9; r <= 12; r++) for (let c = 3; c <= 8; c++) assert.ok(g[r][c].drawn, `${id} board ${r},${c}`);
for (let c = 0; c < 10; c++) {
if (st.kind === 'unite') { assert.equal(g[7][c].glyph, '#', `${id} no hand ${c}`); continue; }
assert.equal(g[7][c].glyph, 'a', `${id} hand ${c}`);
assert.equal(g[7][c].h, TILE_H.bench);
}
+21 -5
View File
@@ -2449,15 +2449,26 @@ function stageDisplayName(stageId, raw) {
return name;
}
/**
* The 联防 maps' names: the tables and PRTS name neither map, so the label is the remake's own [ASSUMED] (PRTS
* 卫戍协议/帮助 §联防阶段 calls the joined field "一处阵地"); no screen shows it today.
*/
const UNITE_STAGE_NAMES = { 1: '联防阵地(1名玩家)', 2: '联防阵地(2名玩家)' };
/**
* Build data/stages.json: 19×21 terrain grids (row 0 = bottom), legend, devices, special terrain
* parameters, deployable tiles and helper ground paths.
* parameters, deployable tiles and helper ground paths — the 11 battle stages of stageDatasDict, then the two 联防
* maps: act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer name the level of the 联防 battle
* (level_act1autochess_escaped_single / _multi), whose own map is the 联防 field — a road, not the round's stage (GitHub
* #41). They carry `kind: 'unite'` and `helpers` (1 / 2), weight 0, no modes, and are never drawn as a match stage.
*/
function buildStages(ctx, modesById) {
const researchStages = ctx.research.maps?.stages || {};
const out = {};
for (const stageId of ctx.stageIds) {
const sd = ctx.act.stageDatasDict[stageId];
const unite = [['escapedBattleTemplateMapSinglePlayer', 1], ['escapedBattleTemplateMapMultiPlayer', 2]]
.filter(([k]) => ctx.act.constData[k]).map(([k, helpers]) => ({ stageId: templateIdOf(ctx.act.constData[k]), helpers }));
for (const { stageId, helpers } of [...ctx.stageIds.map((id) => ({ stageId: id, helpers: 0 })), ...unite]) {
const sd = helpers ? null : ctx.act.stageDatasDict[stageId];
const lv = ctx.levels[stageId];
const map = lv.mapData?.map || [];
const tiles = lv.mapData?.tiles || [];
@@ -2554,10 +2565,11 @@ function buildStages(ctx, modesById) {
const p2 = officialPath(gridStage, activeBlocking, a, b);
if (p2) groundPathsWithDevices[k] = p2;
}
const modes = [...(sd.mode || [])];
const modes = [...(sd?.mode || [])];
const o = lv.options || {};
out[stageId] = {
id: stageId, name: stageDisplayName(stageId, researchStages[stageId]?.name), weight: sd.weight, active: sd.weight > 0,
id: stageId, name: helpers ? UNITE_STAGE_NAMES[helpers] : stageDisplayName(stageId, researchStages[stageId]?.name),
weight: sd ? sd.weight : 0, active: sd ? sd.weight > 0 : false, ...(helpers ? { kind: 'unite', helpers } : {}),
modes, size: [H, W], rows, tiles: glyphTiles,
devices, mapChars, special, runes, globalBuffs,
deployTiles: { normal: deployIn(9, 12, 2, 10), bossLeft: deployIn(1, 5, 2, 10), bossRight: deployIn(1, 5, 10, 18) },
@@ -3444,6 +3456,10 @@ function validateAll(f) {
if (s.rows.some((r) => r.includes('?'))) err(`stage ${s.id}: unknown tile glyph`);
if (s.name !== s.id && /[A-Za-z]/.test(s.name)) err(`stage ${s.id}: Latin text in player-facing name "${s.name}"`);
}
// the 联防 field of 1 / 2 helpers is the map of its template (server/match/unite.js uniteStageId)
for (const [n, id] of Object.entries(config.unite.templates)) {
if (stages[id]?.kind !== 'unite' || stages[id].helpers !== Number(n) || stages[id].active) err(`unite template ${id}: no inactive 联防 stage for ${n} helper(s)`);
}
for (const m of Object.values(config.modes)) {
for (const [r, rd] of Object.entries(m.rounds)) {
const tpls = rd.template ? [rd.template] : Object.values(rd.bossTemplates || {});
+10 -5
View File
@@ -22,8 +22,9 @@
// turn; bonds from the board (bondsMeta.computeBonds) with 0–120 layers
// bonds 46 battles: every bond at its activation threshold (1 layer) and at its top tier (999 layers), 8 operators
// fields 22 battles: every Final Assault / Hidden Core leader on its pair and its solo template (a shared boss pool,
// ended at 200 game s) and 联防 fields (1 and 2 helpers, carried HP / SP, a knocked-out operator, two
// leakers' enemies with a summoned-only kind and a bounty)
// ended at 200 game s) and 联防 fields (1 and 2 helpers on the 联防 map of their count — boards laid out on a
// battle stage —, carried HP / SP, a knocked-out operator, two leakers' enemies with a summoned-only kind and
// a bounty)
// matches 16 bot-only matches (solo 标准 / 险境 / 绝境 / 终极 ×2 seeds, co-op 2 / 3 / 4, one server-run combat match,
// two with LP and layers raised at the first prep so they reach the Hidden Core) run to the end in virtual
// time with the match's default bot rehearsal
@@ -57,6 +58,7 @@ import { buildBattleSpec, createBattleFromSpec } from '../server/sim/spec.js';
import { createRng, deriveSeed } from '../server/sim/rng.js';
import { GameData } from '../server/match/gamedata.js';
import { setupMatchWaves, buildNormalWave, buildBossWave, buildUniteWave, isFlyKey, routeByMotion } from '../server/match/waves.js';
import { uniteStageId } from '../server/match/unite.js';
import { buildDeployMap, positionClass, canPlace, ownerRangeKeys, tileKey } from '../server/match/board.js';
import { computeBonds, bondSnapshot } from '../server/match/bondsMeta.js';
import { Match } from '../server/match/Match.js';
@@ -230,7 +232,8 @@ const ENEMY_COLS = Object.freeze(['spawned', 'killed', 'leaked', 'dmg', 'taken']
const CHESS = Object.values(data.chess).sort((a, b) => byId(a.chessId, b.chessId));
const VISIBLE = CHESS.filter((c) => c.visible);
const STAGES = Object.keys(data.stages).sort(byId);
// the battle stages (the two 联防 maps, stages.json kind 'unite', are the 联防 fields' own: server/match/unite.js uniteStageId)
const STAGES = Object.keys(data.stages).filter((id) => data.stages[id].kind !== 'unite').sort(byId);
const BANDS = Object.keys(data.bands).sort(byId);
const EQUIPS = Object.values(data.items).filter((i) => i.itemType === 'EQUIP').map((i) => i.id).sort(byId);
const BOSSES = Object.keys(data.bosses).sort((a, b) => Number(a.replace(/\D/g, '')) - Number(b.replace(/\D/g, '')));
@@ -586,7 +589,9 @@ export function fieldScenarios() {
const modeId = helpers === 1 ? 'mode_multi_normal' : 'mode_multi_hard';
const gd = gdFor(modeId);
const round = helpers === 1 ? 7 : 11;
const stageId = STAGES[(i * 5) % STAGES.length];
// the helpers' boards are laid out on the round's stage; the 联防 battle runs on its own map (GitHub #41)
const boardStageId = STAGES[(i * 5) % STAGES.length];
const stageId = uniteStageId(gd, helpers) ?? boardStageId;
const seed = deriveSeed(20261005, `unite:${helpers}`);
const src = normalWave(gd, round, seed);
const leakers = helpers === 1 ? ['p2', 'p3'] : ['p3', 'p4'];
@@ -601,7 +606,7 @@ export function fieldScenarios() {
let uid = 1;
const players = [];
for (let h = 0; h < helpers; h++) {
const t = team(gd, stageId, 'normal', uid, 8, helpers > 1 && h === 0 ? 8 : 0);
const t = team(gd, boardStageId, 'normal', uid, 8, helpers > 1 && h === 0 ? 8 : 0);
uid = t.nextUid;
// carried end state of the helper's own battle: HP ratio + SP, one knocked out, summons with SP only
t.units.forEach((u, k) => {